Redesign RSS subsystem

This commit is contained in:
Vladimir Golovnev (Glassez)
2017-03-07 16:10:42 +03:00
parent 090a2edc1a
commit 989a70fe60
64 changed files with 5116 additions and 4727 deletions

View File

@@ -641,8 +641,8 @@ void AddNewTorrentDialog::accept()
if (m_contentModel)
params.filePriorities = m_contentModel->model()->getFilePriorities();
params.addPaused = !ui->startTorrentCheckBox->isChecked();
params.createSubfolder = ui->createSubfolderCheckBox->isChecked();
params.addPaused = TriStateBool(!ui->startTorrentCheckBox->isChecked());
params.createSubfolder = !ui->startTorrentCheckBox->isChecked();
QString savePath = ui->savePathComboBox->itemData(ui->savePathComboBox->currentIndex()).toString();
if (ui->comboTTM->currentIndex() != 1) { // 0 is Manual mode and 1 is Automatic mode. Handle all non 1 values as manual mode.

View File

@@ -2,7 +2,6 @@ INCLUDEPATH += $$PWD
include(lineedit/lineedit.pri)
include(properties/properties.pri)
include(rss/rss.pri)
include(powermanagement/powermanagement.pri)
unix:!macx:dbus: include(qtnotify/qtnotify.pri)
@@ -52,7 +51,12 @@ HEADERS += \
$$PWD/cookiesdialog.h \
$$PWD/categoryfiltermodel.h \
$$PWD/categoryfilterwidget.h \
$$PWD/banlistoptions.h
$$PWD/banlistoptions.h \
$$PWD/rss/rsswidget.h \
$$PWD/rss/articlelistwidget.h \
$$PWD/rss/feedlistwidget.h \
$$PWD/rss/automatedrssdownloader.h \
$$PWD/rss/htmlbrowser.h
SOURCES += \
$$PWD/mainwindow.cpp \
@@ -95,7 +99,12 @@ SOURCES += \
$$PWD/cookiesdialog.cpp \
$$PWD/categoryfiltermodel.cpp \
$$PWD/categoryfilterwidget.cpp \
$$PWD/banlistoptions.cpp
$$PWD/banlistoptions.cpp \
$$PWD/rss/rsswidget.cpp \
$$PWD/rss/articlelistwidget.cpp \
$$PWD/rss/feedlistwidget.cpp \
$$PWD/rss/automatedrssdownloader.cpp \
$$PWD/rss/htmlbrowser.cpp
win32|macx {
HEADERS += $$PWD/programupdater.h
@@ -123,6 +132,8 @@ FORMS += \
$$PWD/search/pluginsourcedlg.ui \
$$PWD/search/searchtab.ui \
$$PWD/cookiesdialog.ui \
$$PWD/banlistoptions.ui
$$PWD/banlistoptions.ui \
$$PWD/rss/rsswidget.ui \
$$PWD/rss/automatedrssdownloader.ui
RESOURCES += $$PWD/about.qrc

View File

@@ -64,6 +64,8 @@
#include "base/bittorrent/session.h"
#include "base/bittorrent/sessionstatus.h"
#include "base/bittorrent/torrenthandle.h"
#include "base/rss/rss_folder.h"
#include "base/rss/rss_session.h"
#include "application.h"
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
@@ -86,7 +88,7 @@
#include "transferlistfilterswidget.h"
#include "propertieswidget.h"
#include "statusbar.h"
#include "rss_imp.h"
#include "rss/rsswidget.h"
#include "about_imp.h"
#include "optionsdlg.h"
#include "trackerlogin.h"
@@ -296,7 +298,7 @@ MainWindow::MainWindow(QWidget *parent)
// View settings
m_ui->actionTopToolBar->setChecked(pref->isToolbarDisplayed());
m_ui->actionSpeedInTitleBar->setChecked(pref->speedInTitleBar());
m_ui->actionRSSReader->setChecked(pref->isRSSEnabled());
m_ui->actionRSSReader->setChecked(pref->isRSSWidgetEnabled());
m_ui->actionSearchWidget->setChecked(pref->isSearchEnabled());
m_ui->actionExecutionLogs->setChecked(isExecutionLogEnabled());
@@ -600,14 +602,19 @@ void MainWindow::on_actionLock_triggered()
hide();
}
void MainWindow::handleRSSUnreadCountUpdated(int count)
{
m_tabs->setTabText(m_tabs->indexOf(m_rssWidget), tr("RSS (%1)").arg(count));
}
void MainWindow::displayRSSTab(bool enable)
{
if (enable) {
// RSS tab
if (!m_rssWidget) {
m_rssWidget = new RSSImp(m_tabs);
connect(m_rssWidget, SIGNAL(updateRSSCount(int)), this, SLOT(updateRSSTabLabel(int)));
int indexTab = m_tabs->addTab(m_rssWidget, tr("RSS (%1)").arg(0));
m_rssWidget = new RSSWidget(m_tabs);
connect(m_rssWidget.data(), &RSSWidget::unreadCountUpdated, this, &MainWindow::handleRSSUnreadCountUpdated);
int indexTab = m_tabs->addTab(m_rssWidget, tr("RSS (%1)").arg(RSS::Session::instance()->rootFolder()->unreadCount()));
m_tabs->setTabIcon(indexTab, GuiIconProvider::instance()->getIcon("application-rss+xml"));
}
}
@@ -616,11 +623,6 @@ void MainWindow::displayRSSTab(bool enable)
}
}
void MainWindow::updateRSSTabLabel(int count)
{
m_tabs->setTabText(m_tabs->indexOf(m_rssWidget), tr("RSS (%1)").arg(count));
}
void MainWindow::displaySearchTab(bool enable)
{
Preferences::instance()->setSearchEnabled(enable);
@@ -685,6 +687,10 @@ void MainWindow::cleanup()
{
writeSettings();
// delete RSSWidget explicitly to avoid crash in
// handleRSSUnreadCountUpdated() at application shutdown
delete m_rssWidget;
delete m_executableWatcher;
if (m_systrayCreator)
m_systrayCreator->stop();
@@ -1502,7 +1508,7 @@ void MainWindow::on_actionSpeedInTitleBar_triggered()
void MainWindow::on_actionRSSReader_triggered()
{
Preferences::instance()->setRSSEnabled(m_ui->actionRSSReader->isChecked());
Preferences::instance()->setRSSWidgetVisible(m_ui->actionRSSReader->isChecked());
displayRSSTab(m_ui->actionRSSReader->isChecked());
}

View File

@@ -44,7 +44,7 @@ class QTimer;
class downloadFromURL;
class SearchWidget;
class RSSImp;
class RSSWidget;
class about;
class OptionsDialog;
class TransferListWidget;
@@ -138,7 +138,6 @@ private slots:
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
void handleUpdateCheckFinished(bool updateAvailable, QString newVersion, bool invokedByUser);
#endif
void updateRSSTabLabel(int count);
#ifdef Q_OS_WIN
void pythonDownloadSuccess(const QString &url, const QString &filePath);
@@ -151,6 +150,7 @@ private slots:
void downloadFromURLList(const QStringList &urlList);
void updateAltSpeedsBtn(bool alternative);
void updateNbTorrents();
void handleRSSUnreadCountUpdated(int count);
void on_actionSearchWidget_triggered();
void on_actionRSSReader_triggered();
@@ -211,7 +211,7 @@ private:
QList<QPair<BitTorrent::TorrentHandle *, QString >> m_unauthenticatedTrackers; // Still needed?
// GUI related
bool m_posInitialized;
QTabWidget *m_tabs;
QPointer<QTabWidget> m_tabs;
StatusBar *m_statusBar;
QPointer<OptionsDialog> m_options;
QPointer<about> m_aboutDlg;
@@ -235,7 +235,7 @@ private:
QAction *m_prioSeparatorMenu;
QSplitter *m_splitter;
QPointer<SearchWidget> m_searchWidget;
QPointer<RSSImp> m_rssWidget;
QPointer<RSSWidget> m_rssWidget;
QPointer<ExecutionLog> m_executionLog;
// Power Management
PowerManagement *m_pwr;

View File

@@ -54,6 +54,8 @@
#include "base/net/portforwarder.h"
#include "base/net/proxyconfigurationmanager.h"
#include "base/preferences.h"
#include "base/rss/rss_autodownloader.h"
#include "base/rss/rss_session.h"
#include "base/scanfoldersmodel.h"
#include "base/torrentfileguard.h"
#include "base/unicodestrings.h"
@@ -61,6 +63,7 @@
#include "base/utils/random.h"
#include "addnewtorrentdialog.h"
#include "advancedsettings.h"
#include "rss/automatedrssdownloader.h"
#include "banlistoptions.h"
#include "guiiconprovider.h"
#include "scanfoldersdelegate.h"
@@ -84,6 +87,7 @@ OptionsDialog::OptionsDialog(QWidget *parent)
m_ui->tabSelection->item(TAB_CONNECTION)->setIcon(GuiIconProvider::instance()->getIcon("network-wired"));
m_ui->tabSelection->item(TAB_DOWNLOADS)->setIcon(GuiIconProvider::instance()->getIcon("folder-download"));
m_ui->tabSelection->item(TAB_SPEED)->setIcon(GuiIconProvider::instance()->getIcon("speedometer", "chronometer"));
m_ui->tabSelection->item(TAB_RSS)->setIcon(GuiIconProvider::instance()->getIcon("rss-config", "application-rss+xml"));
#ifndef DISABLE_WEBUI
m_ui->tabSelection->item(TAB_WEBUI)->setIcon(GuiIconProvider::instance()->getIcon("network-server"));
#else
@@ -335,6 +339,15 @@ OptionsDialog::OptionsDialog(QWidget *parent)
connect(m_ui->DNSUsernameTxt, SIGNAL(textChanged(QString)), SLOT(enableApplyButton()));
connect(m_ui->DNSPasswordTxt, SIGNAL(textChanged(QString)), SLOT(enableApplyButton()));
#endif
// RSS tab
connect(m_ui->checkRSSEnable, &QCheckBox::toggled, this, &OptionsDialog::enableApplyButton);
connect(m_ui->checkRSSAutoDownloaderEnable, &QCheckBox::toggled, this, &OptionsDialog::enableApplyButton);
connect(m_ui->spinRSSRefreshInterval, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged)
, this, &OptionsDialog::enableApplyButton);
connect(m_ui->spinRSSMaxArticlesPerFeed, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &OptionsDialog::enableApplyButton);
connect(m_ui->btnEditRules, &QPushButton::clicked, [this]() { AutomatedRssDownloader(this).exec(); });
// Disable apply Button
applyButton->setEnabled(false);
// Tab selection mechanism
@@ -500,6 +513,11 @@ void OptionsDialog::saveOptions()
app->setFileLoggerEnabled(m_ui->checkFileLog->isChecked());
// End General preferences
RSS::Session::instance()->setRefreshInterval(m_ui->spinRSSRefreshInterval->value());
RSS::Session::instance()->setMaxArticlesPerFeed(m_ui->spinRSSMaxArticlesPerFeed->value());
RSS::Session::instance()->setProcessingEnabled(m_ui->checkRSSEnable->isChecked());
RSS::AutoDownloader::instance()->setProcessingEnabled(m_ui->checkRSSAutoDownloaderEnable->isChecked());
auto session = BitTorrent::Session::instance();
// Downloads preferences
@@ -712,6 +730,11 @@ void OptionsDialog::loadOptions()
m_ui->comboFileLogAgeType->setCurrentIndex(app->fileLoggerAgeType());
// End General preferences
m_ui->checkRSSEnable->setChecked(RSS::Session::instance()->isProcessingEnabled());
m_ui->checkRSSAutoDownloaderEnable->setChecked(RSS::AutoDownloader::instance()->isProcessingEnabled());
m_ui->spinRSSRefreshInterval->setValue(RSS::Session::instance()->refreshInterval());
m_ui->spinRSSMaxArticlesPerFeed->setValue(RSS::Session::instance()->maxArticlesPerFeed());
auto session = BitTorrent::Session::instance();
// Downloads preferences

View File

@@ -68,6 +68,7 @@ private:
TAB_CONNECTION,
TAB_SPEED,
TAB_BITTORRENT,
TAB_RSS,
TAB_WEBUI,
TAB_ADVANCED
};

View File

@@ -45,7 +45,7 @@
<enum>QListView::IconMode</enum>
</property>
<property name="currentRow">
<number>0</number>
<number>-1</number>
</property>
<item>
<property name="text">
@@ -72,6 +72,11 @@
<string>BitTorrent</string>
</property>
</item>
<item>
<property name="text">
<string>RSS</string>
</property>
</item>
<item>
<property name="text">
<string>Web UI</string>
@@ -117,8 +122,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>497</width>
<height>880</height>
<width>470</width>
<height>783</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_9">
@@ -673,8 +678,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>586</width>
<height>1118</height>
<width>470</width>
<height>994</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
@@ -1358,8 +1363,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>457</width>
<height>672</height>
<width>470</width>
<height>619</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_20">
@@ -1838,8 +1843,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>388</width>
<height>452</height>
<width>487</width>
<height>542</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
@@ -2225,8 +2230,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>574</width>
<height>534</height>
<width>487</width>
<height>542</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_7">
@@ -2598,6 +2603,125 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="tabRSSPage">
<layout class="QVBoxLayout" name="verticalLayout_25">
<item>
<widget class="QGroupBox" name="groupRSSReader">
<property name="title">
<string>RSS Reader</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_26">
<item>
<widget class="QCheckBox" name="checkRSSEnable">
<property name="text">
<string>Enable fetching RSS feeds</string>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" name="gridLayout_5">
<item row="0" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>Feeds refresh interval:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_12">
<property name="text">
<string>Maximum number of articles per feed:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="spinRSSRefreshInterval">
<property name="suffix">
<string> min</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>999999</number>
</property>
<property name="value">
<number>5</number>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="spinRSSMaxArticlesPerFeed">
<property name="maximum">
<number>9999</number>
</property>
<property name="value">
<number>100</number>
</property>
</widget>
</item>
<item row="1" column="2">
<spacer name="horizontalSpacer_6">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupRSSAutoDownloader">
<property name="title">
<string>RSS Torrent Auto Downloader</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_21">
<item>
<widget class="QCheckBox" name="checkRSSAutoDownloaderEnable">
<property name="text">
<string>Enable auto downloading of RSS torrents</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnEditRules">
<property name="text">
<string>Edit auto downloading rules...</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer_5">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="tabWebuiPage">
<layout class="QVBoxLayout" name="tabWebuiPageLayout">
<property name="leftMargin">
@@ -2622,8 +2746,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>438</width>
<height>543</height>
<width>487</width>
<height>542</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_23">

View File

@@ -0,0 +1,117 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2017 Vladimir Golovnev <glassez@yandex.ru>
*
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*/
#include "articlelistwidget.h"
#include <QListWidgetItem>
#include "base/rss/rss_article.h"
#include "base/rss/rss_item.h"
ArticleListWidget::ArticleListWidget(QWidget *parent)
: QListWidget(parent)
{
setContextMenuPolicy(Qt::CustomContextMenu);
setSelectionMode(QAbstractItemView::ExtendedSelection);
}
RSS::Article *ArticleListWidget::getRSSArticle(QListWidgetItem *item) const
{
Q_ASSERT(item);
return reinterpret_cast<RSS::Article *>(item->data(Qt::UserRole).value<quintptr>());
}
QListWidgetItem *ArticleListWidget::mapRSSArticle(RSS::Article *rssArticle) const
{
return m_rssArticleToListItemMapping.value(rssArticle);
}
void ArticleListWidget::setRSSItem(RSS::Item *rssItem, bool unreadOnly)
{
// Clear the list first
clear();
if (m_rssItem)
m_rssItem->disconnect(this);
m_unreadOnly = unreadOnly;
m_rssItem = rssItem;
if (!m_rssItem) return;
m_rssItem = rssItem;
connect(m_rssItem, &RSS::Item::newArticle, this, &ArticleListWidget::handleArticleAdded);
connect(m_rssItem, &RSS::Item::articleRead, this, &ArticleListWidget::handleArticleRead);
connect(m_rssItem, &RSS::Item::articleAboutToBeRemoved, this, &ArticleListWidget::handleArticleAboutToBeRemoved);
foreach (auto article, rssItem->articles()) {
if (!(m_unreadOnly && article->isRead()))
addItem(createItem(article));
}
}
void ArticleListWidget::handleArticleAdded(RSS::Article *rssArticle)
{
if (!(m_unreadOnly && rssArticle->isRead()))
addItem(createItem(rssArticle));
}
void ArticleListWidget::handleArticleRead(RSS::Article *rssArticle)
{
if (m_unreadOnly) {
delete m_rssArticleToListItemMapping.take(rssArticle);
}
else {
auto item = mapRSSArticle(rssArticle);
item->setData(Qt::ForegroundRole, QColor("grey"));
item->setData(Qt::DecorationRole, QIcon(":/icons/sphere.png"));
}
}
void ArticleListWidget::handleArticleAboutToBeRemoved(RSS::Article *rssArticle)
{
delete m_rssArticleToListItemMapping.take(rssArticle);
}
QListWidgetItem *ArticleListWidget::createItem(RSS::Article *article)
{
Q_ASSERT(article);
QListWidgetItem *item = new QListWidgetItem;
item->setData(Qt::DisplayRole, article->title());
item->setData(Qt::UserRole, reinterpret_cast<quintptr>(article));
if (article->isRead()) {
item->setData(Qt::ForegroundRole, QColor("grey"));
item->setData(Qt::DecorationRole, QIcon(":/icons/sphere.png"));
}
else {
item->setData(Qt::ForegroundRole, QColor("blue"));
item->setData(Qt::DecorationRole, QIcon(":/icons/sphere2.png"));
}
m_rssArticleToListItemMapping.insert(article, item);
return item;
}

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt4 and libtorrent.
* Copyright (C) 2010 Christophe Dumez
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2017 Vladimir Golovnev <glassez@yandex.ru>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -24,35 +24,43 @@
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*
* Contact : chris@qbittorrent.org
*/
#ifndef RSSSETTINGSDLG_H
#define RSSSETTINGSDLG_H
#ifndef ARTICLELISTWIDGET_H
#define ARTICLELISTWIDGET_H
#include <QDialog>
#include <QHash>
#include <QListWidget>
QT_BEGIN_NAMESPACE
namespace Ui {
class RssSettingsDlg;
namespace RSS
{
class Article;
class Item;
}
QT_END_NAMESPACE
class RssSettingsDlg : public QDialog
class ArticleListWidget: public QListWidget
{
Q_OBJECT
public:
explicit RssSettingsDlg(QWidget *parent = 0);
~RssSettingsDlg();
explicit ArticleListWidget(QWidget *parent);
protected slots:
void on_buttonBox_accepted();
RSS::Article *getRSSArticle(QListWidgetItem *item) const;
QListWidgetItem *mapRSSArticle(RSS::Article *rssArticle) const;
void setRSSItem(RSS::Item *rssItem, bool unreadOnly = false);
private slots:
void handleArticleAdded(RSS::Article *rssArticle);
void handleArticleRead(RSS::Article *rssArticle);
void handleArticleAboutToBeRemoved(RSS::Article *rssArticle);
private:
Ui::RssSettingsDlg *ui;
QListWidgetItem *createItem(RSS::Article *article);
RSS::Item *m_rssItem = nullptr;
bool m_unreadOnly = false;
QHash<RSS::Article *, QListWidgetItem *> m_rssArticleToListItemMapping;
};
#endif // RSSSETTINGS_H
#endif // ARTICLELISTWIDGET_H

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,7 @@
/*
* Bittorrent Client using Qt4 and libtorrent.
* Copyright (C) 2010 Christophe Dumez
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2017 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -24,99 +25,85 @@
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*
* Contact : chris@qbittorrent.org
*/
#ifndef AUTOMATEDRSSDOWNLOADER_H
#define AUTOMATEDRSSDOWNLOADER_H
#include <QDialog>
#include <QHideEvent>
#include <QHash>
#include <QPair>
#include <QSet>
#include <QShortcut>
#include <QShowEvent>
#include <QString>
#include <QWeakPointer>
#include "base/rss/rssdownloadrule.h"
#include "base/rss/rss_autodownloadrule.h"
QT_BEGIN_NAMESPACE
namespace Ui
{
class AutomatedRssDownloader;
}
QT_END_NAMESPACE
namespace Rss
{
class DownloadRuleList;
class Manager;
}
QT_BEGIN_NAMESPACE
class QListWidgetItem;
class QRegularExpression;
QT_END_NAMESPACE
class QShortcut;
namespace RSS
{
class Feed;
}
class AutomatedRssDownloader: public QDialog
{
Q_OBJECT
public:
explicit AutomatedRssDownloader(const QWeakPointer<Rss::Manager> &manager, QWidget *parent = 0);
~AutomatedRssDownloader();
bool isRssDownloaderEnabled() const;
protected:
virtual void showEvent(QShowEvent *event) override;
virtual void hideEvent(QHideEvent *event) override;
protected slots:
void loadSettings();
void saveSettings();
void loadRulesList();
void handleRuleCheckStateChange(QListWidgetItem *rule_item);
void handleFeedCheckStateChange(QListWidgetItem *feed_item);
void updateRuleDefinitionBox(QListWidgetItem *selected = 0);
void clearRuleDefinitionBox();
void saveEditedRule();
void loadFeedList();
void updateFeedList(QListWidgetItem *selected = 0);
explicit AutomatedRssDownloader(QWidget *parent = nullptr);
~AutomatedRssDownloader() override;
private slots:
void displayRulesListMenu(const QPoint &pos);
void on_addRuleBtn_clicked();
void on_removeRuleBtn_clicked();
void on_browseSP_clicked();
void on_exportBtn_clicked();
void on_importBtn_clicked();
void handleRuleCheckStateChange(QListWidgetItem *ruleItem);
void handleFeedCheckStateChange(QListWidgetItem *feedItem);
void displayRulesListMenu();
void renameSelectedRule();
void updateMatchingArticles();
void updateRuleDefinitionBox();
void updateFieldsToolTips(bool regex);
void updateMustLineValidity();
void updateMustNotLineValidity();
void updateEpisodeFilterValidity();
void onFinished(int result);
void handleRuleDefinitionChanged();
void handleRuleAdded(const QString &ruleName);
void handleRuleRenamed(const QString &ruleName, const QString &oldRuleName);
void handleRuleChanged(const QString &ruleName);
void handleRuleAboutToBeRemoved(const QString &ruleName);
void handleProcessingStateChanged(bool enabled);
private:
Rss::DownloadRulePtr getCurrentRule() const;
void loadSettings();
void saveSettings();
void createRuleItem(const RSS::AutoDownloadRule &rule);
void initCategoryCombobox();
void addFeedArticlesToTree(const Rss::FeedPtr &feed, const QStringList &articles);
void disconnectRuleFeedSlots();
void connectRuleFeedSlots();
void clearRuleDefinitionBox();
void updateEditedRule();
void updateMatchingArticles();
void saveEditedRule();
void loadFeedList();
void updateFeedList();
void addFeedArticlesToTree(RSS::Feed *feed, const QStringList &articles);
private:
Ui::AutomatedRssDownloader *ui;
QWeakPointer<Rss::Manager> m_manager;
QListWidgetItem *m_editedRule;
Rss::DownloadRuleList *m_ruleList;
Rss::DownloadRuleList *m_editableRuleList;
Ui::AutomatedRssDownloader *m_ui;
QListWidgetItem *m_currentRuleItem;
QShortcut *m_editHotkey;
QShortcut *m_deleteHotkey;
QSet<QPair<QString, QString>> m_treeListEntries;
RSS::AutoDownloadRule m_currentRule;
QHash<QString, QListWidgetItem *> m_itemsByRuleName;
QRegularExpression *m_episodeRegex;
QShortcut *editHotkey;
QShortcut *deleteHotkey;
QSet<QPair<QString, QString >> m_treeListEntries;
};
#endif // AUTOMATEDRSSDOWNLOADER_H

View File

@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>816</width>
<height>537</height>
<height>523</height>
</rect>
</property>
<property name="windowTitle">
@@ -15,20 +15,31 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QCheckBox" name="checkEnableDownloader">
<widget class="QLabel" name="labelWarn">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
<italic>true</italic>
</font>
</property>
<property name="styleSheet">
<string notr="true">color: red;</string>
</property>
<property name="text">
<string>Enable Automated RSS Downloader</string>
<string>Auto downloading of RSS torrents is disabled now! You can enable it in application settings.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QSplitter" name="hsplitter">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
@@ -249,12 +260,12 @@
</item>
<item>
<widget class="QSpinBox" name="spinIgnorePeriod">
<property name="specialValueText">
<string>Disabled</string>
</property>
<property name="enabled">
<bool>true</bool>
</property>
<property name="specialValueText">
<string>Disabled</string>
</property>
<property name="suffix">
<string> days</string>
</property>
@@ -374,6 +385,9 @@
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QPushButton" name="importBtn">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>&amp;Import...</string>
</property>
@@ -381,6 +395,9 @@
</item>
<item>
<widget class="QPushButton" name="exportBtn">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>&amp;Export...</string>
</property>

View File

@@ -1,6 +1,7 @@
/*
* Bittorrent Client using Qt4 and libtorrent.
* Copyright (C) 2010 Christophe Dumez
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2017 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -24,213 +25,241 @@
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*
* Contact: chris@qbittorrent.org, arnaud@qbittorrent.org
*/
#include "base/rss/rssmanager.h"
#include "base/rss/rssfolder.h"
#include "base/rss/rssfeed.h"
#include "guiiconprovider.h"
#include "feedlistwidget.h"
FeedListWidget::FeedListWidget(QWidget *parent, const Rss::ManagerPtr& rssmanager)
#include <QDragMoveEvent>
#include <QDropEvent>
#include <QTreeWidgetItem>
#include "base/rss/rss_article.h"
#include "base/rss/rss_feed.h"
#include "base/rss/rss_folder.h"
#include "base/rss/rss_session.h"
#include "guiiconprovider.h"
FeedListWidget::FeedListWidget(QWidget *parent)
: QTreeWidget(parent)
, m_rssManager(rssmanager)
, m_currentFeed(nullptr)
{
setContextMenuPolicy(Qt::CustomContextMenu);
setDragDropMode(QAbstractItemView::InternalMove);
setSelectionMode(QAbstractItemView::ExtendedSelection);
setColumnCount(1);
headerItem()->setText(0, tr("RSS feeds"));
m_unreadStickyItem = new QTreeWidgetItem(this);
m_unreadStickyItem->setText(0, tr("Unread") + QString::fromUtf8(" (") + QString::number(rssmanager->rootFolder()->unreadCount()) + QString(")"));
m_unreadStickyItem->setData(0,Qt::DecorationRole, GuiIconProvider::instance()->getIcon("mail-folder-inbox"));
itemAdded(m_unreadStickyItem, rssmanager->rootFolder());
connect(this, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), SLOT(updateCurrentFeed(QTreeWidgetItem*)));
setCurrentItem(m_unreadStickyItem);
setContextMenuPolicy(Qt::CustomContextMenu);
setDragDropMode(QAbstractItemView::InternalMove);
setSelectionMode(QAbstractItemView::ExtendedSelection);
setColumnCount(1);
headerItem()->setText(0, tr("RSS feeds"));
connect(RSS::Session::instance(), &RSS::Session::itemAdded, this, &FeedListWidget::handleItemAdded);
connect(RSS::Session::instance(), &RSS::Session::feedStateChanged, this, &FeedListWidget::handleFeedStateChanged);
connect(RSS::Session::instance(), &RSS::Session::feedIconLoaded, this, &FeedListWidget::handleFeedIconLoaded);
connect(RSS::Session::instance(), &RSS::Session::itemPathChanged, this, &FeedListWidget::handleItemPathChanged);
connect(RSS::Session::instance(), &RSS::Session::itemAboutToBeRemoved, this, &FeedListWidget::handleItemAboutToBeRemoved);
m_rssToTreeItemMapping[RSS::Session::instance()->rootFolder()] = invisibleRootItem();
m_unreadStickyItem = new QTreeWidgetItem(this);
m_unreadStickyItem->setData(0, Qt::UserRole, reinterpret_cast<quintptr>(RSS::Session::instance()->rootFolder()));
m_unreadStickyItem->setText(0, tr("Unread (%1)").arg(RSS::Session::instance()->rootFolder()->unreadCount()));
m_unreadStickyItem->setData(0, Qt::DecorationRole, GuiIconProvider::instance()->getIcon("mail-folder-inbox"));
connect(RSS::Session::instance()->rootFolder(), &RSS::Item::unreadCountChanged, this, &FeedListWidget::handleItemUnreadCountChanged);
setSortingEnabled(false);
fill(nullptr, RSS::Session::instance()->rootFolder());
setSortingEnabled(true);
// setCurrentItem(m_unreadStickyItem);
}
FeedListWidget::~FeedListWidget() {
delete m_unreadStickyItem;
FeedListWidget::~FeedListWidget()
{
delete m_unreadStickyItem;
}
void FeedListWidget::itemAdded(QTreeWidgetItem *item, const Rss::FilePtr& file) {
m_rssMapping[item] = file;
if (Rss::FeedPtr feed = qSharedPointerDynamicCast<Rss::Feed>(file)) {
m_feedsItems[feed->id()] = item;
}
void FeedListWidget::handleItemAdded(RSS::Item *rssItem)
{
auto parentItem = m_rssToTreeItemMapping.value(
RSS::Session::instance()->itemByPath(RSS::Item::parentPath(rssItem->path())));
createItem(rssItem, parentItem);
}
void FeedListWidget::itemAboutToBeRemoved(QTreeWidgetItem *item) {
Rss::FilePtr file = m_rssMapping.take(item);
if (Rss::FeedPtr feed = qSharedPointerDynamicCast<Rss::Feed>(file)) {
m_feedsItems.remove(feed->id());
} else if (Rss::FolderPtr folder = qSharedPointerDynamicCast<Rss::Folder>(file)) {
Rss::FeedList feeds = folder->getAllFeeds();
foreach (const Rss::FeedPtr& feed, feeds) {
m_feedsItems.remove(feed->id());
}
}
}
void FeedListWidget::handleFeedStateChanged(RSS::Feed *feed)
{
QTreeWidgetItem *item = m_rssToTreeItemMapping.value(feed);
Q_ASSERT(item);
bool FeedListWidget::hasFeed(const QString &url) const {
return m_feedsItems.contains(QUrl(url).toString());
}
QList<QTreeWidgetItem*> FeedListWidget::getAllFeedItems() const {
return m_feedsItems.values();
}
QTreeWidgetItem* FeedListWidget::stickyUnreadItem() const {
return m_unreadStickyItem;
}
QStringList FeedListWidget::getItemPath(QTreeWidgetItem* item) const {
QStringList path;
if (item) {
if (item->parent())
path << getItemPath(item->parent());
path.append(getRSSItem(item)->id());
}
return path;
}
QList<QTreeWidgetItem*> FeedListWidget::getAllOpenFolders(QTreeWidgetItem *parent) const {
QList<QTreeWidgetItem*> open_folders;
int nbChildren;
if (parent)
nbChildren = parent->childCount();
else
nbChildren = topLevelItemCount();
for (int i=0; i<nbChildren; ++i) {
QTreeWidgetItem *item;
if (parent)
item = parent->child(i);
QIcon icon;
if (feed->isLoading())
icon = QIcon(QStringLiteral(":/icons/loading.png"));
else if (feed->hasError())
icon = GuiIconProvider::instance()->getIcon(QStringLiteral("unavailable"));
else if (!feed->iconPath().isEmpty())
icon = QIcon(feed->iconPath());
else
item = topLevelItem(i);
if (isFolder(item) && item->isExpanded()) {
QList<QTreeWidgetItem*> open_subfolders = getAllOpenFolders(item);
if (!open_subfolders.empty()) {
open_folders << open_subfolders;
} else {
open_folders << item;
}
}
}
return open_folders;
icon = GuiIconProvider::instance()->getIcon(QStringLiteral("application-rss+xml"));
item->setData(0, Qt::DecorationRole, icon);
}
QList<QTreeWidgetItem*> FeedListWidget::getAllFeedItems(QTreeWidgetItem* folder) {
QList<QTreeWidgetItem*> feeds;
const int nbChildren = folder->childCount();
for (int i=0; i<nbChildren; ++i) {
QTreeWidgetItem *item = folder->child(i);
if (isFeed(item)) {
feeds << item;
} else {
feeds << getAllFeedItems(item);
void FeedListWidget::handleFeedIconLoaded(RSS::Feed *feed)
{
if (!feed->isLoading() && !feed->hasError()) {
QTreeWidgetItem *item = m_rssToTreeItemMapping.value(feed);
Q_ASSERT(item);
item->setData(0, Qt::DecorationRole, QIcon(feed->iconPath()));
}
}
return feeds;
}
Rss::FilePtr FeedListWidget::getRSSItem(QTreeWidgetItem *item) const {
return m_rssMapping.value(item, Rss::FilePtr());
void FeedListWidget::handleItemUnreadCountChanged(RSS::Item *rssItem)
{
if (rssItem == RSS::Session::instance()->rootFolder()) {
m_unreadStickyItem->setText(0, tr("Unread (%1)").arg(RSS::Session::instance()->rootFolder()->unreadCount()));
}
else {
QTreeWidgetItem *item = mapRSSItem(rssItem);
Q_ASSERT(item);
item->setData(0, Qt::DisplayRole, QString("%1 (%2)").arg(rssItem->name()).arg(rssItem->unreadCount()));
}
}
void FeedListWidget::handleItemPathChanged(RSS::Item *rssItem)
{
QTreeWidgetItem *item = mapRSSItem(rssItem);
Q_ASSERT(item);
item->setData(0, Qt::DisplayRole, QString("%1 (%2)").arg(rssItem->name()).arg(rssItem->unreadCount()));
RSS::Item *parentRssItem = RSS::Session::instance()->itemByPath(RSS::Item::parentPath(rssItem->path()));
QTreeWidgetItem *parentItem = mapRSSItem(parentRssItem);
Q_ASSERT(parentItem);
parentItem->addChild(item);
}
void FeedListWidget::handleItemAboutToBeRemoved(RSS::Item *rssItem)
{
delete m_rssToTreeItemMapping.take(rssItem);
}
QTreeWidgetItem *FeedListWidget::stickyUnreadItem() const
{
return m_unreadStickyItem;
}
QList<QTreeWidgetItem *> FeedListWidget::getAllOpenedFolders(QTreeWidgetItem *parent) const
{
QList<QTreeWidgetItem *> openedFolders;
int nbChildren = (parent ? parent->childCount() : topLevelItemCount());
for (int i = 0; i < nbChildren; ++i) {
QTreeWidgetItem *item (parent ? parent->child(i) : topLevelItem(i));
if (isFolder(item) && item->isExpanded()) {
QList<QTreeWidgetItem *> openedSubfolders = getAllOpenedFolders(item);
if (!openedSubfolders.empty())
openedFolders << openedSubfolders;
else
openedFolders << item;
}
}
return openedFolders;
}
RSS::Item *FeedListWidget::getRSSItem(QTreeWidgetItem *item) const
{
return reinterpret_cast<RSS::Item *>(item->data(0, Qt::UserRole).value<quintptr>());
}
QTreeWidgetItem *FeedListWidget::mapRSSItem(RSS::Item *rssItem) const
{
return m_rssToTreeItemMapping.value(rssItem);
}
QString FeedListWidget::itemPath(QTreeWidgetItem *item) const
{
return getRSSItem(item)->path();
}
bool FeedListWidget::isFeed(QTreeWidgetItem *item) const
{
return (qSharedPointerDynamicCast<Rss::Feed>(m_rssMapping.value(item)) != NULL);
return qobject_cast<RSS::Feed *>(getRSSItem(item));
}
bool FeedListWidget::isFolder(QTreeWidgetItem *item) const
{
return (qSharedPointerDynamicCast<Rss::Folder>(m_rssMapping.value(item)) != NULL);
return qobject_cast<RSS::Folder *>(getRSSItem(item));
}
QString FeedListWidget::getItemID(QTreeWidgetItem *item) const {
return m_rssMapping.value(item)->id();
void FeedListWidget::dragMoveEvent(QDragMoveEvent *event)
{
QTreeWidget::dragMoveEvent(event);
QTreeWidgetItem *item = itemAt(event->pos());
// Prohibit dropping onto global unread counter
if (item == m_unreadStickyItem)
event->ignore();
// Prohibit dragging of global unread counter
else if (selectedItems().contains(m_unreadStickyItem))
event->ignore();
// Prohibit dropping onto feeds
else if (item && isFeed(item))
event->ignore();
}
QTreeWidgetItem* FeedListWidget::getTreeItemFromUrl(const QString &url) const {
return m_feedsItems.value(url, 0);
}
void FeedListWidget::dropEvent(QDropEvent *event)
{
QTreeWidgetItem *destFolderItem = itemAt(event->pos());
RSS::Folder *destFolder = (destFolderItem
? static_cast<RSS::Folder *>(getRSSItem(destFolderItem))
: RSS::Session::instance()->rootFolder());
Rss::FeedPtr FeedListWidget::getRSSItemFromUrl(const QString &url) const {
return qSharedPointerDynamicCast<Rss::Feed>(getRSSItem(getTreeItemFromUrl(url)));
}
QTreeWidgetItem* FeedListWidget::currentItem() const {
return m_currentFeed;
}
QTreeWidgetItem* FeedListWidget::currentFeed() const {
return m_currentFeed;
}
void FeedListWidget::updateCurrentFeed(QTreeWidgetItem* new_item) {
if (!new_item) return;
if (!m_rssMapping.contains(new_item)) return;
if (isFeed(new_item) || new_item == m_unreadStickyItem)
m_currentFeed = new_item;
}
void FeedListWidget::dragMoveEvent(QDragMoveEvent * event) {
QTreeWidget::dragMoveEvent(event);
QTreeWidgetItem *item = itemAt(event->pos());
// Prohibit dropping onto global unread counter
if (item == m_unreadStickyItem) {
event->ignore();
return;
}
// Prohibit dragging of global unread counter
if (selectedItems().contains(m_unreadStickyItem)) {
event->ignore();
return;
}
// Prohibit dropping onto feeds
if (item && isFeed(item)) {
event->ignore();
return;
}
}
void FeedListWidget::dropEvent(QDropEvent *event) {
qDebug("dropEvent");
QList<QTreeWidgetItem*> folders_altered;
QTreeWidgetItem *dest_folder_item = itemAt(event->pos());
Rss::FolderPtr dest_folder;
if (dest_folder_item) {
dest_folder = qSharedPointerCast<Rss::Folder>(getRSSItem(dest_folder_item));
folders_altered << dest_folder_item;
} else {
dest_folder = m_rssManager->rootFolder();
}
QList<QTreeWidgetItem *> src_items = selectedItems();
// Check if there is not going to overwrite another file
foreach (QTreeWidgetItem *src_item, src_items) {
Rss::FilePtr file = getRSSItem(src_item);
if (dest_folder->hasChild(file->id())) {
QTreeWidget::dropEvent(event);
return;
// move as much items as possible
foreach (QTreeWidgetItem *srcItem, selectedItems()) {
auto rssItem = getRSSItem(srcItem);
RSS::Session::instance()->moveItem(rssItem, RSS::Item::joinPath(destFolder->path(), rssItem->name()));
}
QTreeWidget::dropEvent(event);
if (destFolderItem)
destFolderItem->setExpanded(true);
}
QTreeWidgetItem *FeedListWidget::createItem(RSS::Item *rssItem, QTreeWidgetItem *parentItem)
{
QTreeWidgetItem *item = new QTreeWidgetItem;
item->setData(0, Qt::DisplayRole, QString("%1 (%2)").arg(rssItem->name()).arg(rssItem->unreadCount()));
item->setData(0, Qt::UserRole, reinterpret_cast<quintptr>(rssItem));
m_rssToTreeItemMapping[rssItem] = item;
QIcon icon;
if (auto feed = qobject_cast<RSS::Feed *>(rssItem)) {
if (feed->isLoading())
icon = QIcon(QStringLiteral(":/icons/loading.png"));
else if (feed->hasError())
icon = GuiIconProvider::instance()->getIcon(QStringLiteral("unavailable"));
else if (!feed->iconPath().isEmpty())
icon = QIcon(feed->iconPath());
else
icon = GuiIconProvider::instance()->getIcon(QStringLiteral("application-rss+xml"));
}
else {
icon = GuiIconProvider::instance()->getIcon("inode-directory");
}
item->setData(0, Qt::DecorationRole, icon);
connect(rssItem, &RSS::Item::unreadCountChanged, this, &FeedListWidget::handleItemUnreadCountChanged);
if (!parentItem || (parentItem == m_unreadStickyItem))
addTopLevelItem(item);
else
parentItem->addChild(item);
return item;
}
void FeedListWidget::fill(QTreeWidgetItem *parent, RSS::Folder *rssParent)
{
foreach (auto rssItem, rssParent->items()) {
QTreeWidgetItem *item = createItem(rssItem, parent);
// Recursive call if this is a folder.
if (auto folder = qobject_cast<RSS::Folder *>(rssItem))
fill(item, folder);
}
}
// Proceed with the move
foreach (QTreeWidgetItem *src_item, src_items) {
QTreeWidgetItem *parent_folder = src_item->parent();
if (parent_folder && !folders_altered.contains(parent_folder))
folders_altered << parent_folder;
// Actually move the file
Rss::FilePtr file = getRSSItem(src_item);
m_rssManager->moveFile(file, dest_folder);
}
QTreeWidget::dropEvent(event);
if (dest_folder_item)
dest_folder_item->setExpanded(true);
// Emit signal for update
if (!folders_altered.empty())
emit foldersAltered(folders_altered);
}

View File

@@ -1,6 +1,7 @@
/*
* Bittorrent Client using Qt4 and libtorrent.
* Copyright (C) 2010 Christophe Dumez
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2017 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -24,67 +25,54 @@
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*
* Contact: chris@qbittorrent.org, arnaud@qbittorrent.org
*/
#ifndef FEEDLIST_H
#define FEEDLIST_H
#ifndef FEEDLISTWIDGET_H
#define FEEDLISTWIDGET_H
#include <QTreeWidget>
#include <QTreeWidgetItem>
#include <QDropEvent>
#include <QDragMoveEvent>
#include <QStringList>
#include <QHash>
#include <QUrl>
#include <QTreeWidget>
#include "base/rss/rssfile.h"
#include "base/rss/rssfeed.h"
#include "base/rss/rssmanager.h"
namespace RSS
{
class Article;
class Feed;
class Folder;
class Item;
}
class FeedListWidget: public QTreeWidget {
Q_OBJECT
class FeedListWidget: public QTreeWidget
{
Q_OBJECT
public:
FeedListWidget(QWidget *parent, const Rss::ManagerPtr& rssManager);
~FeedListWidget();
explicit FeedListWidget(QWidget *parent);
~FeedListWidget();
bool hasFeed(const QString &url) const;
QList<QTreeWidgetItem*> getAllFeedItems() const;
QTreeWidgetItem* stickyUnreadItem() const;
QStringList getItemPath(QTreeWidgetItem* item) const;
QList<QTreeWidgetItem*> getAllOpenFolders(QTreeWidgetItem *parent=0) const;
QList<QTreeWidgetItem*> getAllFeedItems(QTreeWidgetItem* folder);
Rss::FilePtr getRSSItem(QTreeWidgetItem *item) const;
bool isFeed(QTreeWidgetItem *item) const;
bool isFolder(QTreeWidgetItem *item) const;
QString getItemID(QTreeWidgetItem *item) const;
QTreeWidgetItem* getTreeItemFromUrl(const QString &url) const;
Rss::FeedPtr getRSSItemFromUrl(const QString &url) const;
QTreeWidgetItem* currentItem() const;
QTreeWidgetItem* currentFeed() const;
public slots:
void itemAdded(QTreeWidgetItem *item, const Rss::FilePtr& file);
void itemAboutToBeRemoved(QTreeWidgetItem *item);
signals:
void foldersAltered(const QList<QTreeWidgetItem*> &folders);
QTreeWidgetItem *stickyUnreadItem() const;
QList<QTreeWidgetItem *> getAllOpenedFolders(QTreeWidgetItem *parent = nullptr) const;
RSS::Item *getRSSItem(QTreeWidgetItem *item) const;
QTreeWidgetItem *mapRSSItem(RSS::Item *rssItem) const;
QString itemPath(QTreeWidgetItem *item) const;
bool isFeed(QTreeWidgetItem *item) const;
bool isFolder(QTreeWidgetItem *item) const;
private slots:
void updateCurrentFeed(QTreeWidgetItem* new_item);
protected:
void dragMoveEvent(QDragMoveEvent * event);
void dropEvent(QDropEvent *event);
void handleItemAdded(RSS::Item *rssItem);
void handleFeedStateChanged(RSS::Feed *feed);
void handleFeedIconLoaded(RSS::Feed *feed);
void handleItemUnreadCountChanged(RSS::Item *rssItem);
void handleItemPathChanged(RSS::Item *rssItem);
void handleItemAboutToBeRemoved(RSS::Item *rssItem);
private:
Rss::ManagerPtr m_rssManager;
QHash<QTreeWidgetItem*, Rss::FilePtr> m_rssMapping;
QHash<QString, QTreeWidgetItem*> m_feedsItems;
QTreeWidgetItem* m_currentFeed;
QTreeWidgetItem *m_unreadStickyItem;
void dragMoveEvent(QDragMoveEvent *event);
void dropEvent(QDropEvent *event);
QTreeWidgetItem *createItem(RSS::Item *rssItem, QTreeWidgetItem *parentItem = nullptr);
void fill(QTreeWidgetItem *parent, RSS::Folder *rssParent);
QHash<RSS::Item *, QTreeWidgetItem *> m_rssToTreeItemMapping;
QTreeWidgetItem *m_unreadStickyItem;
};
#endif // FEEDLIST_H
#endif // FEEDLISTWIDGET_H

View File

@@ -1,17 +0,0 @@
INCLUDEPATH += $$PWD
HEADERS += $$PWD/rss_imp.h \
$$PWD/rsssettingsdlg.h \
$$PWD/feedlistwidget.h \
$$PWD/automatedrssdownloader.h \
$$PWD/htmlbrowser.h
SOURCES += $$PWD/rss_imp.cpp \
$$PWD/rsssettingsdlg.cpp \
$$PWD/feedlistwidget.cpp \
$$PWD/automatedrssdownloader.cpp \
$$PWD/htmlbrowser.cpp
FORMS += $$PWD/rss.ui \
$$PWD/rsssettingsdlg.ui \
$$PWD/automatedrssdownloader.ui

View File

@@ -1,806 +0,0 @@
/*
* Bittorrent Client using Qt4 and libtorrent.
* Copyright (C) 2006 Christophe Dumez, Arnaud Demaiziere
*
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*
* Contact : chris@qbittorrent.org arnaud@qbittorrent.org
*/
#include "rss_imp.h"
#include <QDesktopServices>
#include <QMenu>
#include <QStandardItemModel>
#include <QMessageBox>
#include <QString>
#include <QClipboard>
#include <QDragMoveEvent>
#include <QDebug>
#include "feedlistwidget.h"
#include "base/bittorrent/session.h"
#include "base/net/downloadmanager.h"
#include "base/preferences.h"
#include "rsssettingsdlg.h"
#include "base/rss/rssmanager.h"
#include "base/rss/rssfolder.h"
#include "base/rss/rssarticle.h"
#include "base/rss/rssfeed.h"
#include "automatedrssdownloader.h"
#include "guiiconprovider.h"
#include "autoexpandabledialog.h"
#include "addnewtorrentdialog.h"
#include "ui_rss.h"
namespace Article
{
enum ArticleRoles
{
TitleRole = Qt::DisplayRole,
IconRole = Qt::DecorationRole,
ColorRole = Qt::ForegroundRole,
IdRole = Qt::UserRole + 1,
FeedUrlRole = Qt::UserRole + 2
};
}
// display a right-click menu
void RSSImp::displayRSSListMenu(const QPoint &pos)
{
if (!m_feedList->indexAt(pos).isValid())
// No item under the mouse, clear selection
m_feedList->clearSelection();
QMenu myRSSListMenu(this);
QList<QTreeWidgetItem * > selectedItems = m_feedList->selectedItems();
if (selectedItems.size() > 0) {
myRSSListMenu.addAction(m_ui->actionUpdate);
myRSSListMenu.addAction(m_ui->actionMark_items_read);
myRSSListMenu.addSeparator();
if (selectedItems.size() == 1) {
if (m_feedList->getRSSItem(selectedItems.first()) != m_rssManager->rootFolder()) {
myRSSListMenu.addAction(m_ui->actionRename);
myRSSListMenu.addAction(m_ui->actionDelete);
myRSSListMenu.addSeparator();
if (m_feedList->isFolder(selectedItems.first()))
myRSSListMenu.addAction(m_ui->actionNew_folder);
}
}
else {
myRSSListMenu.addAction(m_ui->actionDelete);
myRSSListMenu.addSeparator();
}
myRSSListMenu.addAction(m_ui->actionNew_subscription);
if (m_feedList->isFeed(selectedItems.first())) {
myRSSListMenu.addSeparator();
myRSSListMenu.addAction(m_ui->actionCopy_feed_URL);
}
}
else {
myRSSListMenu.addAction(m_ui->actionNew_subscription);
myRSSListMenu.addAction(m_ui->actionNew_folder);
myRSSListMenu.addSeparator();
myRSSListMenu.addAction(m_ui->actionUpdate_all_feeds);
}
myRSSListMenu.exec(QCursor::pos());
}
void RSSImp::displayItemsListMenu(const QPoint &)
{
QMenu myItemListMenu(this);
QList<QListWidgetItem * > selectedItems = m_ui->listArticles->selectedItems();
if (selectedItems.size() <= 0)
return;
bool hasTorrent = false;
bool hasLink = false;
foreach (const QListWidgetItem *item, selectedItems) {
if (!item) continue;
Rss::FeedPtr feed = m_feedList->getRSSItemFromUrl(item->data(Article::FeedUrlRole).toString());
if (!feed) continue;
Rss::ArticlePtr article = feed->getItem(item->data(Article::IdRole).toString());
if (!article) continue;
if (!article->torrentUrl().isEmpty())
hasTorrent = true;
if (!article->link().isEmpty())
hasLink = true;
if (hasTorrent && hasLink)
break;
}
if (hasTorrent)
myItemListMenu.addAction(m_ui->actionDownload_torrent);
if (hasLink)
myItemListMenu.addAction(m_ui->actionOpen_news_URL);
if (hasTorrent || hasLink)
myItemListMenu.exec(QCursor::pos());
}
void RSSImp::askNewFolder()
{
QTreeWidgetItem *parent_item = 0;
Rss::FolderPtr rss_parent;
if (m_feedList->selectedItems().size() > 0) {
parent_item = m_feedList->selectedItems().at(0);
rss_parent = qSharedPointerDynamicCast<Rss::Folder>(m_feedList->getRSSItem(parent_item));
Q_ASSERT(rss_parent);
}
else {
rss_parent = m_rssManager->rootFolder();
}
bool ok;
QString new_name = AutoExpandableDialog::getText(this, tr("Please choose a folder name"), tr("Folder name:"), QLineEdit::Normal, tr("New folder"), &ok);
if (!ok || rss_parent->hasChild(new_name))
return;
Rss::FolderPtr newFolder(new Rss::Folder(new_name));
rss_parent->addFile(newFolder);
QTreeWidgetItem *folderItem = createFolderListItem(newFolder);
if (parent_item)
parent_item->addChild(folderItem);
else
m_feedList->addTopLevelItem(folderItem);
// Notify TreeWidget
m_feedList->itemAdded(folderItem, newFolder);
// Expand parent folder to display new folder
if (parent_item)
parent_item->setExpanded(true);
m_feedList->setCurrentItem(folderItem);
m_rssManager->saveStreamList();
}
// add a stream by a button
void RSSImp::on_newFeedButton_clicked()
{
// Determine parent folder for new feed
QTreeWidgetItem *parent_item = 0;
QList<QTreeWidgetItem * > selected_items = m_feedList->selectedItems();
if (!selected_items.empty()) {
parent_item = selected_items.first();
// Consider the case where the user clicked on Unread item
if (parent_item == m_feedList->stickyUnreadItem())
parent_item = 0;
else
if (!m_feedList->isFolder(parent_item))
parent_item = parent_item->parent();
}
Rss::FolderPtr rss_parent;
if (parent_item)
rss_parent = qSharedPointerCast<Rss::Folder>(m_feedList->getRSSItem(parent_item));
else
rss_parent = m_rssManager->rootFolder();
// Ask for feed URL
bool ok;
QString clip_txt = qApp->clipboard()->text();
QString default_url = "http://";
if (clip_txt.startsWith("http://", Qt::CaseInsensitive) || clip_txt.startsWith("https://", Qt::CaseInsensitive) || clip_txt.startsWith("ftp://", Qt::CaseInsensitive))
default_url = clip_txt;
QString newUrl = AutoExpandableDialog::getText(this, tr("Please type a RSS stream URL"), tr("Stream URL:"), QLineEdit::Normal, default_url, &ok);
if (!ok)
return;
newUrl = newUrl.trimmed();
if (newUrl.isEmpty())
return;
if (m_feedList->hasFeed(newUrl)) {
QMessageBox::warning(this, "qBittorrent",
tr("This RSS feed is already in the list."),
QMessageBox::Ok);
return;
}
Rss::FeedPtr stream(new Rss::Feed(newUrl, m_rssManager.data()));
rss_parent->addFile(stream);
// Create TreeWidget item
QTreeWidgetItem *item = createFolderListItem(stream);
if (parent_item)
parent_item->addChild(item);
else
m_feedList->addTopLevelItem(item);
// Notify TreeWidget
m_feedList->itemAdded(item, stream);
// Expand parent folder to display new feed
if (parent_item)
parent_item->setExpanded(true);
m_feedList->setCurrentItem(item);
m_rssManager->saveStreamList();
}
// delete a stream by a button
void RSSImp::deleteSelectedItems()
{
QList<QTreeWidgetItem * > selectedItems = m_feedList->selectedItems();
if (selectedItems.isEmpty())
return;
if ((selectedItems.size() == 1) && (selectedItems.first() == m_feedList->stickyUnreadItem()))
return;
QMessageBox::StandardButton answer = QMessageBox::question(this, tr("Deletion confirmation"),
tr("Are you sure you want to delete the selected RSS feeds?"),
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
if (answer == QMessageBox::No)
return;
QList<QString> deleted;
foreach (QTreeWidgetItem *item, selectedItems) {
if (item == m_feedList->stickyUnreadItem())
continue;
Rss::FilePtr rss_item = m_feedList->getRSSItem(item);
QTreeWidgetItem *parent = item->parent();
// Notify TreeWidget
m_feedList->itemAboutToBeRemoved(item);
// Actually delete the item
rss_item->parentFolder()->removeChild(rss_item->id());
deleted << rss_item->id();
delete item;
// Update parents count
while (parent && (parent != m_feedList->invisibleRootItem())) {
updateItemInfos(parent);
parent = parent->parent();
}
}
m_rssManager->saveStreamList();
foreach (const QString &feed_id, deleted)
m_rssManager->forwardFeedInfosChanged(feed_id, "", 0);
// Update Unread items
updateItemInfos(m_feedList->stickyUnreadItem());
if (m_feedList->currentItem() == m_feedList->stickyUnreadItem())
populateArticleList(m_feedList->stickyUnreadItem());
}
void RSSImp::loadFoldersOpenState()
{
QStringList open_folders = Preferences::instance()->getRssOpenFolders();
foreach (const QString &var_path, open_folders) {
QStringList path = var_path.split("\\");
QTreeWidgetItem *parent = 0;
foreach (const QString &name, path) {
int nbChildren = 0;
if (parent)
nbChildren = parent->childCount();
else
nbChildren = m_feedList->topLevelItemCount();
for (int i = 0; i < nbChildren; ++i) {
QTreeWidgetItem *child;
if (parent)
child = parent->child(i);
else
child = m_feedList->topLevelItem(i);
if (m_feedList->getRSSItem(child)->id() == name) {
parent = child;
parent->setExpanded(true);
qDebug("expanding folder %s", qPrintable(name));
break;
}
}
}
}
}
void RSSImp::saveFoldersOpenState()
{
QStringList open_folders;
QList<QTreeWidgetItem * > items = m_feedList->getAllOpenFolders();
foreach (QTreeWidgetItem *item, items) {
QString path = m_feedList->getItemPath(item).join("\\");
qDebug("saving open folder: %s", qPrintable(path));
open_folders << path;
}
Preferences::instance()->setRssOpenFolders(open_folders);
}
// refresh all streams by a button
void RSSImp::refreshAllFeeds()
{
foreach (QTreeWidgetItem *item, m_feedList->getAllFeedItems())
item->setData(0, Qt::DecorationRole, QVariant(QIcon(":/icons/loading.png")));
m_rssManager->refresh();
}
void RSSImp::downloadSelectedTorrents()
{
QList<QListWidgetItem * > selected_items = m_ui->listArticles->selectedItems();
if (selected_items.size() <= 0)
return;
foreach (QListWidgetItem *item, selected_items) {
if (!item) continue;
Rss::FeedPtr feed = m_feedList->getRSSItemFromUrl(item->data(Article::FeedUrlRole).toString());
if (!feed) continue;
Rss::ArticlePtr article = feed->getItem(item->data(Article::IdRole).toString());
if (!article) continue;
// Mark as read
article->markAsRead();
item->setData(Article::ColorRole, QVariant(QColor("grey")));
item->setData(Article::IconRole, QVariant(QIcon(":/icons/sphere.png")));
if (article->torrentUrl().isEmpty())
continue;
if (AddNewTorrentDialog::isEnabled())
AddNewTorrentDialog::show(article->torrentUrl());
else
BitTorrent::Session::instance()->addTorrent(article->torrentUrl());
}
// Decrement feed nb unread news
updateItemInfos(m_feedList->stickyUnreadItem());
updateItemInfos(m_feedList->getTreeItemFromUrl(selected_items.first()->data(Article::FeedUrlRole).toString()));
}
// open the url of the selected RSS articles in the Web browser
void RSSImp::openSelectedArticlesUrls()
{
QList<QListWidgetItem * > selected_items = m_ui->listArticles->selectedItems();
if (selected_items.size() <= 0)
return;
foreach (QListWidgetItem *item, selected_items) {
if (!item) continue;
Rss::FeedPtr feed = m_feedList->getRSSItemFromUrl(item->data(Article::FeedUrlRole).toString());
if (!feed) continue;
Rss::ArticlePtr article = feed->getItem(item->data(Article::IdRole).toString());
if (!article) continue;
// Mark as read
article->markAsRead();
item->setData(Article::ColorRole, QVariant(QColor("grey")));
item->setData(Article::IconRole, QVariant(QIcon(":/icons/sphere.png")));
const QString link = article->link();
if (!link.isEmpty())
QDesktopServices::openUrl(QUrl(link));
}
// Decrement feed nb unread news
updateItemInfos(m_feedList->stickyUnreadItem());
updateItemInfos(m_feedList->getTreeItemFromUrl(selected_items.first()->data(Article::FeedUrlRole).toString()));
}
// right-click on stream : give it an alias
void RSSImp::renameSelectedRssFile()
{
QList<QTreeWidgetItem * > selectedItems = m_feedList->selectedItems();
if (selectedItems.size() != 1)
return;
QTreeWidgetItem *item = selectedItems.first();
if (item == m_feedList->stickyUnreadItem())
return;
Rss::FilePtr rss_item = m_feedList->getRSSItem(item);
bool ok;
QString newName;
do {
newName = AutoExpandableDialog::getText(this, tr("Please choose a new name for this RSS feed"), tr("New feed name:"), QLineEdit::Normal, m_feedList->getRSSItem(item)->displayName(), &ok);
// Check if name is already taken
if (ok) {
if (rss_item->parentFolder()->hasChild(newName)) {
QMessageBox::warning(0, tr("Name already in use"), tr("This name is already used by another item, please choose another one."));
ok = false;
}
}
else {
return;
}
} while (!ok);
// Rename item
rss_item->rename(newName);
m_rssManager->saveStreamList();
// Update TreeWidget
updateItemInfos(item);
}
// right-click on stream : refresh it
void RSSImp::refreshSelectedItems()
{
QList<QTreeWidgetItem * > selectedItems = m_feedList->selectedItems();
foreach (QTreeWidgetItem *item, selectedItems) {
Rss::FilePtr file = m_feedList->getRSSItem(item);
// Update icons
if (item == m_feedList->stickyUnreadItem()) {
refreshAllFeeds();
return;
}
else {
if (!file->refresh())
continue;
// Update UI
if (qSharedPointerDynamicCast<Rss::Feed>(file)) {
item->setData(0, Qt::DecorationRole, QVariant(QIcon(":/icons/loading.png")));
}
else if (qSharedPointerDynamicCast<Rss::Folder>(file)) {
// Update feeds in the folder
foreach (QTreeWidgetItem *feed, m_feedList->getAllFeedItems(item))
feed->setData(0, Qt::DecorationRole, QVariant(QIcon(":/icons/loading.png")));
}
}
}
}
void RSSImp::copySelectedFeedsURL()
{
QStringList URLs;
QList<QTreeWidgetItem * > selectedItems = m_feedList->selectedItems();
QTreeWidgetItem *item;
foreach (item, selectedItems)
if (m_feedList->isFeed(item))
URLs << m_feedList->getItemID(item);
qApp->clipboard()->setText(URLs.join("\n"));
}
void RSSImp::on_markReadButton_clicked()
{
QList<QTreeWidgetItem * > selectedItems = m_feedList->selectedItems();
foreach (QTreeWidgetItem *item, selectedItems) {
Rss::FilePtr rss_item = m_feedList->getRSSItem(item);
Q_ASSERT(rss_item);
rss_item->markAsRead();
updateItemInfos(item);
}
// Update article list
if (!selectedItems.isEmpty())
populateArticleList(m_feedList->currentItem());
}
QTreeWidgetItem *RSSImp::createFolderListItem(const Rss::FilePtr &rssFile)
{
Q_ASSERT(rssFile);
QTreeWidgetItem *item = new QTreeWidgetItem;
item->setData(0, Qt::DisplayRole, QVariant(rssFile->displayName() + QString::fromUtf8(" (") + QString::number(rssFile->unreadCount()) + QString(")")));
item->setData(0, Qt::DecorationRole, QIcon(rssFile->iconPath()));
return item;
}
void RSSImp::fillFeedsList(QTreeWidgetItem *parent, const Rss::FolderPtr &rss_parent)
{
QList<Rss::FilePtr> children;
if (parent)
children = rss_parent->getContent();
else
children = m_rssManager->rootFolder()->getContent();
foreach (const Rss::FilePtr &rssFile, children) {
QTreeWidgetItem *item = createFolderListItem(rssFile);
Q_ASSERT(item);
if (parent)
parent->addChild(item);
else
m_feedList->addTopLevelItem(item);
// Notify TreeWidget of item addition
m_feedList->itemAdded(item, rssFile);
// Recursive call if this is a folder.
if (Rss::FolderPtr folder = qSharedPointerDynamicCast<Rss::Folder>(rssFile))
fillFeedsList(item, folder);
}
}
QListWidgetItem *RSSImp::createArticleListItem(const Rss::ArticlePtr &article)
{
Q_ASSERT(article);
QListWidgetItem *item = new QListWidgetItem;
item->setData(Article::TitleRole, article->title());
item->setData(Article::FeedUrlRole, article->parent()->url());
item->setData(Article::IdRole, article->guid());
if (article->isRead()) {
item->setData(Article::ColorRole, QVariant(QColor("grey")));
item->setData(Article::IconRole, QVariant(QIcon(":/icons/sphere.png")));
}
else {
item->setData(Article::ColorRole, QVariant(QColor("blue")));
item->setData(Article::IconRole, QVariant(QIcon(":/icons/sphere2.png")));
}
return item;
}
// fills the newsList
void RSSImp::populateArticleList(QTreeWidgetItem *item)
{
if (!item) {
m_ui->listArticles->clear();
return;
}
Rss::FilePtr rss_item = m_feedList->getRSSItem(item);
if (!rss_item)
return;
// Clear the list first
m_ui->textBrowser->clear();
m_currentArticle = 0;
m_ui->listArticles->clear();
qDebug("Getting the list of news");
Rss::ArticleList articles;
if (rss_item == m_rssManager->rootFolder())
articles = rss_item->unreadArticleListByDateDesc();
else
articles = rss_item->articleListByDateDesc();
qDebug("Got the list of news");
foreach (const Rss::ArticlePtr &article, articles) {
QListWidgetItem *articleItem = createArticleListItem(article);
m_ui->listArticles->addItem(articleItem);
}
qDebug("Added all news to the GUI");
}
// display a news
void RSSImp::refreshTextBrowser()
{
QList<QListWidgetItem * > selection = m_ui->listArticles->selectedItems();
if (selection.empty()) return;
QListWidgetItem *item = selection.first();
Q_ASSERT(item);
if (item == m_currentArticle) return;
m_currentArticle = item;
Rss::FeedPtr feed = m_feedList->getRSSItemFromUrl(item->data(Article::FeedUrlRole).toString());
if (!feed) return;
Rss::ArticlePtr article = feed->getItem(item->data(Article::IdRole).toString());
if (!article) return;
QString html;
html += "<div style='border: 2px solid red; margin-left: 5px; margin-right: 5px; margin-bottom: 5px;'>";
html += "<div style='background-color: #678db2; font-weight: bold; color: #fff;'>" + article->title() + "</div>";
if (article->date().isValid())
html += "<div style='background-color: #efefef;'><b>" + tr("Date: ") + "</b>" + article->date().toLocalTime().toString(Qt::SystemLocaleLongDate) + "</div>";
if (!article->author().isEmpty())
html += "<div style='background-color: #efefef;'><b>" + tr("Author: ") + "</b>" + article->author() + "</div>";
html += "</div>";
html += "<div style='margin-left: 5px; margin-right: 5px;'>";
if (Qt::mightBeRichText(article->description())) {
html += article->description();
}
else {
QString description = article->description();
QRegExp rx;
// If description is plain text, replace BBCode tags with HTML and wrap everything in <pre></pre> so it looks nice
rx.setMinimal(true);
rx.setCaseSensitivity(Qt::CaseInsensitive);
rx.setPattern("\\[img\\](.+)\\[/img\\]");
description = description.replace(rx, "<img src=\"\\1\">");
rx.setPattern("\\[url=(\")?(.+)\\1\\]");
description = description.replace(rx, "<a href=\"\\2\">");
description = description.replace("[/url]", "</a>", Qt::CaseInsensitive);
rx.setPattern("\\[(/)?([bius])\\]");
description = description.replace(rx, "<\\1\\2>");
rx.setPattern("\\[color=(\")?(.+)\\1\\]");
description = description.replace(rx, "<span style=\"color:\\2\">");
description = description.replace("[/color]", "</span>", Qt::CaseInsensitive);
rx.setPattern("\\[size=(\")?(.+)\\d\\1\\]");
description = description.replace(rx, "<span style=\"font-size:\\2px\">");
description = description.replace("[/size]", "</span>", Qt::CaseInsensitive);
html += "<pre>" + description + "</pre>";
}
html += "</div>";
m_ui->textBrowser->setHtml(html);
article->markAsRead();
item->setData(Article::ColorRole, QVariant(QColor("grey")));
item->setData(Article::IconRole, QVariant(QIcon(":/icons/sphere.png")));
// Decrement feed nb unread news
updateItemInfos(m_feedList->stickyUnreadItem());
updateItemInfos(m_feedList->getTreeItemFromUrl(item->data(Article::FeedUrlRole).toString()));
}
void RSSImp::saveSlidersPosition()
{
// Remember sliders positions
Preferences *const pref = Preferences::instance();
pref->setRssSideSplitterState(m_ui->splitterSide->saveState());
pref->setRssMainSplitterState(m_ui->splitterMain->saveState());
qDebug("Splitters position saved");
}
void RSSImp::restoreSlidersPosition()
{
const Preferences *const pref = Preferences::instance();
const QByteArray stateSide = pref->getRssSideSplitterState();
if (!stateSide.isEmpty())
m_ui->splitterSide->restoreState(stateSide);
const QByteArray stateMain = pref->getRssMainSplitterState();
if (!stateMain.isEmpty())
m_ui->splitterMain->restoreState(stateMain);
}
void RSSImp::updateItemsInfos(const QList<QTreeWidgetItem *> &items)
{
foreach (QTreeWidgetItem *item, items)
updateItemInfos(item);
}
void RSSImp::updateItemInfos(QTreeWidgetItem *item)
{
Rss::FilePtr rss_item = m_feedList->getRSSItem(item);
if (!rss_item)
return;
QString name;
if (rss_item == m_rssManager->rootFolder()) {
name = tr("Unread");
emit updateRSSCount(rss_item->unreadCount());
}
else {
name = rss_item->displayName();
}
item->setText(0, name + QString::fromUtf8(" (") + QString::number(rss_item->unreadCount()) + QString(")"));
// If item has a parent, update it too
if (item->parent())
updateItemInfos(item->parent());
}
void RSSImp::updateFeedIcon(const QString &url, const QString &iconPath)
{
QTreeWidgetItem *item = m_feedList->getTreeItemFromUrl(url);
if (item)
item->setData(0, Qt::DecorationRole, QVariant(QIcon(iconPath)));
}
void RSSImp::updateFeedInfos(const QString &url, const QString &display_name, uint nbUnread)
{
qDebug() << Q_FUNC_INFO << display_name;
QTreeWidgetItem *item = m_feedList->getTreeItemFromUrl(url);
if (item) {
Rss::FeedPtr stream = qSharedPointerCast<Rss::Feed>(m_feedList->getRSSItem(item));
item->setText(0, display_name + QString::fromUtf8(" (") + QString::number(nbUnread) + QString(")"));
if (!stream->isLoading())
item->setData(0, Qt::DecorationRole, QIcon(stream->iconPath()));
// Update parent
if (item->parent())
updateItemInfos(item->parent());
}
// Update Unread item
updateItemInfos(m_feedList->stickyUnreadItem());
}
void RSSImp::onFeedContentChanged(const QString &url)
{
qDebug() << Q_FUNC_INFO << url;
QTreeWidgetItem *item = m_feedList->getTreeItemFromUrl(url);
// If the feed is selected, update the displayed news
if (m_feedList->currentItem() == item)
populateArticleList(item);
// Update unread items
else if (m_feedList->currentItem() == m_feedList->stickyUnreadItem())
populateArticleList(m_feedList->stickyUnreadItem());
}
void RSSImp::updateRefreshInterval(uint val)
{
m_rssManager->updateRefreshInterval(val);
}
RSSImp::RSSImp(QWidget *parent)
: QWidget(parent)
, m_ui(new Ui::RSS())
, m_rssManager(new Rss::Manager)
{
m_ui->setupUi(this);
// Icons
m_ui->actionCopy_feed_URL->setIcon(GuiIconProvider::instance()->getIcon("edit-copy"));
m_ui->actionDelete->setIcon(GuiIconProvider::instance()->getIcon("edit-delete"));
m_ui->actionDownload_torrent->setIcon(GuiIconProvider::instance()->getIcon("download"));
m_ui->actionMark_items_read->setIcon(GuiIconProvider::instance()->getIcon("mail-mark-read"));
m_ui->actionNew_folder->setIcon(GuiIconProvider::instance()->getIcon("folder-new"));
m_ui->actionNew_subscription->setIcon(GuiIconProvider::instance()->getIcon("list-add"));
m_ui->actionOpen_news_URL->setIcon(GuiIconProvider::instance()->getIcon("application-x-mswinurl"));
m_ui->actionRename->setIcon(GuiIconProvider::instance()->getIcon("edit-rename"));
m_ui->actionUpdate->setIcon(GuiIconProvider::instance()->getIcon("view-refresh"));
m_ui->actionUpdate_all_feeds->setIcon(GuiIconProvider::instance()->getIcon("view-refresh"));
m_ui->newFeedButton->setIcon(GuiIconProvider::instance()->getIcon("list-add"));
m_ui->markReadButton->setIcon(GuiIconProvider::instance()->getIcon("mail-mark-read"));
m_ui->updateAllButton->setIcon(GuiIconProvider::instance()->getIcon("view-refresh"));
m_ui->rssDownloaderBtn->setIcon(GuiIconProvider::instance()->getIcon("download"));
m_ui->settingsButton->setIcon(GuiIconProvider::instance()->getIcon("configure", "preferences-system"));
m_feedList = new FeedListWidget(m_ui->splitterSide, m_rssManager);
m_ui->splitterSide->insertWidget(0, m_feedList);
editHotkey = new QShortcut(Qt::Key_F2, m_feedList, 0, 0, Qt::WidgetShortcut);
connect(editHotkey, SIGNAL(activated()), SLOT(renameSelectedRssFile()));
connect(m_feedList, SIGNAL(doubleClicked(QModelIndex)), SLOT(renameSelectedRssFile()));
deleteHotkey = new QShortcut(QKeySequence::Delete, m_feedList, 0, 0, Qt::WidgetShortcut);
connect(deleteHotkey, SIGNAL(activated()), SLOT(deleteSelectedItems()));
m_rssManager->loadStreamList();
m_feedList->setSortingEnabled(false);
fillFeedsList();
m_feedList->setSortingEnabled(true);
populateArticleList(m_feedList->currentItem());
loadFoldersOpenState();
connect(m_rssManager.data(), SIGNAL(feedInfosChanged(QString,QString,unsigned int)), SLOT(updateFeedInfos(QString,QString,unsigned int)));
connect(m_rssManager.data(), SIGNAL(feedContentChanged(QString)), SLOT(onFeedContentChanged(QString)));
connect(m_rssManager.data(), SIGNAL(feedIconChanged(QString,QString)), SLOT(updateFeedIcon(QString,QString)));
connect(m_feedList, SIGNAL(customContextMenuRequested(const QPoint&)), SLOT(displayRSSListMenu(const QPoint&)));
connect(m_ui->listArticles, SIGNAL(customContextMenuRequested(const QPoint&)), SLOT(displayItemsListMenu(const QPoint&)));
// Feeds list actions
connect(m_ui->actionDelete, SIGNAL(triggered()), this, SLOT(deleteSelectedItems()));
connect(m_ui->actionRename, SIGNAL(triggered()), this, SLOT(renameSelectedRssFile()));
connect(m_ui->actionUpdate, SIGNAL(triggered()), this, SLOT(refreshSelectedItems()));
connect(m_ui->actionNew_folder, SIGNAL(triggered()), this, SLOT(askNewFolder()));
connect(m_ui->actionNew_subscription, SIGNAL(triggered()), this, SLOT(on_newFeedButton_clicked()));
connect(m_ui->actionUpdate_all_feeds, SIGNAL(triggered()), this, SLOT(refreshAllFeeds()));
connect(m_ui->updateAllButton, SIGNAL(clicked()), SLOT(refreshAllFeeds()));
connect(m_ui->actionCopy_feed_URL, SIGNAL(triggered()), this, SLOT(copySelectedFeedsURL()));
connect(m_ui->actionMark_items_read, SIGNAL(triggered()), this, SLOT(on_markReadButton_clicked()));
// News list actions
connect(m_ui->actionOpen_news_URL, SIGNAL(triggered()), this, SLOT(openSelectedArticlesUrls()));
connect(m_ui->actionDownload_torrent, SIGNAL(triggered()), this, SLOT(downloadSelectedTorrents()));
connect(m_feedList, SIGNAL(currentItemChanged(QTreeWidgetItem *,QTreeWidgetItem *)), this, SLOT(populateArticleList(QTreeWidgetItem *)));
connect(m_feedList, SIGNAL(foldersAltered(QList<QTreeWidgetItem * >)), this, SLOT(updateItemsInfos(QList<QTreeWidgetItem * >)));
connect(m_ui->listArticles, SIGNAL(itemSelectionChanged()), this, SLOT(refreshTextBrowser()));
connect(m_ui->listArticles, SIGNAL(itemDoubleClicked(QListWidgetItem *)), this, SLOT(downloadSelectedTorrents()));
// Restore sliders position
restoreSlidersPosition();
// Bind saveSliders slots
connect(m_ui->splitterMain, SIGNAL(splitterMoved(int,int)), this, SLOT(saveSlidersPosition()));
connect(m_ui->splitterSide, SIGNAL(splitterMoved(int,int)), this, SLOT(saveSlidersPosition()));
qDebug("RSSImp constructed");
}
RSSImp::~RSSImp()
{
qDebug("Deleting RSSImp...");
saveFoldersOpenState();
delete editHotkey;
delete deleteHotkey;
delete m_feedList;
delete m_ui;
qDebug("RSSImp deleted");
}
void RSSImp::on_settingsButton_clicked()
{
RssSettingsDlg dlg(this);
if (dlg.exec())
updateRefreshInterval(Preferences::instance()->getRSSRefreshInterval());
}
void RSSImp::on_rssDownloaderBtn_clicked()
{
AutomatedRssDownloader dlg(m_rssManager, this);
dlg.exec();
if (dlg.isRssDownloaderEnabled()) {
m_rssManager->rootFolder()->recheckRssItemsForDownload();
refreshAllFeeds();
}
}

View File

@@ -1,61 +0,0 @@
/*
* Bittorrent Client using Qt4 and libtorrent.
* Copyright (C) 2010 Christophe Dumez
*
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*
* Contact : chris@qbittorrent.org
*/
#include "rsssettingsdlg.h"
#include "ui_rsssettingsdlg.h"
#include "base/preferences.h"
#include "base/utils/misc.h"
#include "guiiconprovider.h"
RssSettingsDlg::RssSettingsDlg(QWidget *parent) :
QDialog(parent),
ui(new Ui::RssSettingsDlg)
{
ui->setupUi(this);
ui->rssIcon->setPixmap(GuiIconProvider::instance()->getIcon("application-rss+xml").pixmap(Utils::Misc::largeIconSize()));
// Load settings
const Preferences* const pref = Preferences::instance();
ui->spinRSSRefresh->setValue(pref->getRSSRefreshInterval());
ui->spinRSSMaxArticlesPerFeed->setValue(pref->getRSSMaxArticlesPerFeed());
}
RssSettingsDlg::~RssSettingsDlg()
{
qDebug("Deleting the RSS settings dialog");
delete ui;
}
void RssSettingsDlg::on_buttonBox_accepted() {
// Save settings
Preferences* const pref = Preferences::instance();
pref->setRSSRefreshInterval(ui->spinRSSRefresh->value());
pref->setRSSMaxArticlesPerFeed(ui->spinRSSMaxArticlesPerFeed->value());
}

View File

@@ -1,126 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>RssSettingsDlg</class>
<widget class="QDialog" name="RssSettingsDlg">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>415</width>
<height>123</height>
</rect>
</property>
<property name="windowTitle">
<string>RSS Reader Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="1">
<widget class="QLabel" name="label">
<property name="text">
<string>RSS feeds refresh interval:</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QSpinBox" name="spinRSSRefresh">
<property name="suffix">
<string> min</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>999999</number>
</property>
<property name="value">
<number>5</number>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Maximum number of articles per feed:</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QSpinBox" name="spinRSSMaxArticlesPerFeed">
<property name="maximum">
<number>9999</number>
</property>
<property name="value">
<number>100</number>
</property>
</widget>
</item>
<item row="0" column="0" rowspan="2">
<widget class="QLabel" name="rssIcon"/>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources>
<include location="../../icons.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>RssSettingsDlg</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>RssSettingsDlg</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

533
src/gui/rss/rsswidget.cpp Normal file
View File

@@ -0,0 +1,533 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2017 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
* Copyright (C) 2006 Arnaud Demaiziere <arnaud@qbittorrent.org>
*
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*/
#include "rsswidget.h"
#include <QClipboard>
#include <QDebug>
#include <QDesktopServices>
#include <QDragMoveEvent>
#include <QMenu>
#include <QMessageBox>
#include <QStandardItemModel>
#include <QString>
#include "base/bittorrent/session.h"
#include "base/net/downloadmanager.h"
#include "base/preferences.h"
#include "base/rss/rss_article.h"
#include "base/rss/rss_feed.h"
#include "base/rss/rss_folder.h"
#include "base/rss/rss_session.h"
#include "base/utils/misc.h"
#include "addnewtorrentdialog.h"
#include "articlelistwidget.h"
#include "autoexpandabledialog.h"
#include "automatedrssdownloader.h"
#include "feedlistwidget.h"
#include "guiiconprovider.h"
#include "ui_rsswidget.h"
RSSWidget::RSSWidget(QWidget *parent)
: QWidget(parent)
, m_ui(new Ui::RSSWidget)
{
m_ui->setupUi(this);
// Icons
m_ui->actionCopyFeedURL->setIcon(GuiIconProvider::instance()->getIcon("edit-copy"));
m_ui->actionDelete->setIcon(GuiIconProvider::instance()->getIcon("edit-delete"));
m_ui->actionDownloadTorrent->setIcon(GuiIconProvider::instance()->getIcon("download"));
m_ui->actionMarkItemsRead->setIcon(GuiIconProvider::instance()->getIcon("mail-mark-read"));
m_ui->actionNewFolder->setIcon(GuiIconProvider::instance()->getIcon("folder-new"));
m_ui->actionNewSubscription->setIcon(GuiIconProvider::instance()->getIcon("list-add"));
m_ui->actionOpenNewsURL->setIcon(GuiIconProvider::instance()->getIcon("application-x-mswinurl"));
m_ui->actionRename->setIcon(GuiIconProvider::instance()->getIcon("edit-rename"));
m_ui->actionUpdate->setIcon(GuiIconProvider::instance()->getIcon("view-refresh"));
m_ui->actionUpdateAllFeeds->setIcon(GuiIconProvider::instance()->getIcon("view-refresh"));
m_ui->newFeedButton->setIcon(GuiIconProvider::instance()->getIcon("list-add"));
m_ui->markReadButton->setIcon(GuiIconProvider::instance()->getIcon("mail-mark-read"));
m_ui->updateAllButton->setIcon(GuiIconProvider::instance()->getIcon("view-refresh"));
m_ui->rssDownloaderBtn->setIcon(GuiIconProvider::instance()->getIcon("download"));
m_articleListWidget = new ArticleListWidget(m_ui->splitterMain);
m_ui->splitterMain->insertWidget(0, m_articleListWidget);
connect(m_articleListWidget, &ArticleListWidget::customContextMenuRequested, this, &RSSWidget::displayItemsListMenu);
connect(m_articleListWidget, &ArticleListWidget::currentItemChanged, this, &RSSWidget::handleCurrentArticleItemChanged);
connect(m_articleListWidget, &ArticleListWidget::itemDoubleClicked, this, &RSSWidget::downloadSelectedTorrents);
m_feedListWidget = new FeedListWidget(m_ui->splitterSide);
m_ui->splitterSide->insertWidget(0, m_feedListWidget);
connect(m_feedListWidget, &QAbstractItemView::doubleClicked, this, &RSSWidget::renameSelectedRSSItem);
connect(m_feedListWidget, &QTreeWidget::currentItemChanged, this, &RSSWidget::handleCurrentFeedItemChanged);
connect(m_feedListWidget, &QWidget::customContextMenuRequested, this, &RSSWidget::displayRSSListMenu);
loadFoldersOpenState();
m_feedListWidget->setCurrentItem(m_feedListWidget->stickyUnreadItem());
m_editHotkey = new QShortcut(Qt::Key_F2, m_feedListWidget, 0, 0, Qt::WidgetShortcut);
connect(m_editHotkey, &QShortcut::activated, this, &RSSWidget::renameSelectedRSSItem);
m_deleteHotkey = new QShortcut(QKeySequence::Delete, m_feedListWidget, 0, 0, Qt::WidgetShortcut);
connect(m_deleteHotkey, &QShortcut::activated, this, &RSSWidget::deleteSelectedItems);
// Feeds list actions
connect(m_ui->actionDelete, &QAction::triggered, this, &RSSWidget::deleteSelectedItems);
connect(m_ui->actionRename, &QAction::triggered, this, &RSSWidget::renameSelectedRSSItem);
connect(m_ui->actionUpdate, &QAction::triggered, this, &RSSWidget::refreshSelectedItems);
connect(m_ui->actionNewFolder, &QAction::triggered, this, &RSSWidget::askNewFolder);
connect(m_ui->actionNewSubscription, &QAction::triggered, this, &RSSWidget::on_newFeedButton_clicked);
connect(m_ui->actionUpdateAllFeeds, &QAction::triggered, this, &RSSWidget::refreshAllFeeds);
connect(m_ui->updateAllButton, &QAbstractButton::clicked, this, &RSSWidget::refreshAllFeeds);
connect(m_ui->actionCopyFeedURL, &QAction::triggered, this, &RSSWidget::copySelectedFeedsURL);
connect(m_ui->actionMarkItemsRead, &QAction::triggered, this, &RSSWidget::on_markReadButton_clicked);
// News list actions
connect(m_ui->actionOpenNewsURL, &QAction::triggered, this, &RSSWidget::openSelectedArticlesUrls);
connect(m_ui->actionDownloadTorrent, &QAction::triggered, this, &RSSWidget::downloadSelectedTorrents);
// Restore sliders position
restoreSlidersPosition();
// Bind saveSliders slots
connect(m_ui->splitterMain, &QSplitter::splitterMoved, this, &RSSWidget::saveSlidersPosition);
connect(m_ui->splitterSide, &QSplitter::splitterMoved, this, &RSSWidget::saveSlidersPosition);
if (RSS::Session::instance()->isProcessingEnabled())
m_ui->labelWarn->hide();
connect(RSS::Session::instance(), &RSS::Session::processingStateChanged
, this, &RSSWidget::handleSessionProcessingStateChanged);
connect(RSS::Session::instance()->rootFolder(), &RSS::Folder::unreadCountChanged
, this, &RSSWidget::handleUnreadCountChanged);
}
RSSWidget::~RSSWidget()
{
// we need it here to properly mark latest article
// as read without having additional code
m_articleListWidget->clear();
saveFoldersOpenState();
delete m_editHotkey;
delete m_deleteHotkey;
delete m_feedListWidget;
delete m_ui;
}
// display a right-click menu
void RSSWidget::displayRSSListMenu(const QPoint &pos)
{
if (!m_feedListWidget->indexAt(pos).isValid())
// No item under the mouse, clear selection
m_feedListWidget->clearSelection();
QMenu myRSSListMenu(this);
QList<QTreeWidgetItem *> selectedItems = m_feedListWidget->selectedItems();
if (selectedItems.size() > 0) {
myRSSListMenu.addAction(m_ui->actionUpdate);
myRSSListMenu.addAction(m_ui->actionMarkItemsRead);
myRSSListMenu.addSeparator();
if (selectedItems.size() == 1) {
if (selectedItems.first() != m_feedListWidget->stickyUnreadItem()) {
myRSSListMenu.addAction(m_ui->actionRename);
myRSSListMenu.addAction(m_ui->actionDelete);
myRSSListMenu.addSeparator();
if (m_feedListWidget->isFolder(selectedItems.first()))
myRSSListMenu.addAction(m_ui->actionNewFolder);
}
}
else {
myRSSListMenu.addAction(m_ui->actionDelete);
myRSSListMenu.addSeparator();
}
myRSSListMenu.addAction(m_ui->actionNewSubscription);
if (m_feedListWidget->isFeed(selectedItems.first())) {
myRSSListMenu.addSeparator();
myRSSListMenu.addAction(m_ui->actionCopyFeedURL);
}
}
else {
myRSSListMenu.addAction(m_ui->actionNewSubscription);
myRSSListMenu.addAction(m_ui->actionNewFolder);
myRSSListMenu.addSeparator();
myRSSListMenu.addAction(m_ui->actionUpdateAllFeeds);
}
myRSSListMenu.exec(QCursor::pos());
}
void RSSWidget::displayItemsListMenu(const QPoint &)
{
bool hasTorrent = false;
bool hasLink = false;
foreach (const QListWidgetItem *item, m_articleListWidget->selectedItems()) {
auto article = reinterpret_cast<RSS::Article *>(item->data(Qt::UserRole).value<quintptr>());
Q_ASSERT(article);
if (!article->torrentUrl().isEmpty())
hasTorrent = true;
if (!article->link().isEmpty())
hasLink = true;
if (hasTorrent && hasLink)
break;
}
QMenu myItemListMenu(this);
if (hasTorrent)
myItemListMenu.addAction(m_ui->actionDownloadTorrent);
if (hasLink)
myItemListMenu.addAction(m_ui->actionOpenNewsURL);
if (hasTorrent || hasLink)
myItemListMenu.exec(QCursor::pos());
}
void RSSWidget::askNewFolder()
{
bool ok;
QString newName = AutoExpandableDialog::getText(
this, tr("Please choose a folder name"), tr("Folder name:"), QLineEdit::Normal
, tr("New folder"), &ok);
if (!ok) return;
newName = newName.trimmed();
if (newName.isEmpty()) return;
// Determine destination folder for new item
QTreeWidgetItem *destItem = nullptr;
QList<QTreeWidgetItem *> selectedItems = m_feedListWidget->selectedItems();
if (!selectedItems.empty()) {
destItem = selectedItems.first();
if (!m_feedListWidget->isFolder(destItem))
destItem = destItem->parent();
}
// Consider the case where the user clicked on Unread item
RSS::Folder *rssDestFolder = ((destItem == m_feedListWidget->stickyUnreadItem())
? RSS::Session::instance()->rootFolder()
: qobject_cast<RSS::Folder *>(m_feedListWidget->getRSSItem(destItem)));
QString error;
const QString newFolderPath = RSS::Item::joinPath(rssDestFolder->path(), newName);
if (!RSS::Session::instance()->addFolder(newFolderPath, &error))
QMessageBox::warning(this, "qBittorrent", error, QMessageBox::Ok);
// Expand destination folder to display new feed
if (destItem != m_feedListWidget->stickyUnreadItem())
destItem->setExpanded(true);
// As new RSS items are added synchronously, we can do the following here.
m_feedListWidget->setCurrentItem(m_feedListWidget->mapRSSItem(RSS::Session::instance()->itemByPath(newFolderPath)));
}
// add a stream by a button
void RSSWidget::on_newFeedButton_clicked()
{
// Ask for feed URL
const QString clipText = qApp->clipboard()->text();
const QString defaultURL = (Utils::Misc::isUrl(clipText) ? clipText : "http://");
bool ok;
QString newURL = AutoExpandableDialog::getText(
this, tr("Please type a RSS feed URL"), tr("Feed URL:"), QLineEdit::Normal, defaultURL, &ok);
if (!ok) return;
newURL = newURL.trimmed();
if (newURL.isEmpty()) return;
// Determine destination folder for new item
QTreeWidgetItem *destItem = nullptr;
QList<QTreeWidgetItem *> selectedItems = m_feedListWidget->selectedItems();
if (!selectedItems.empty()) {
destItem = selectedItems.first();
if (!m_feedListWidget->isFolder(destItem))
destItem = destItem->parent();
}
// Consider the case where the user clicked on Unread item
RSS::Folder *rssDestFolder = ((!destItem || (destItem == m_feedListWidget->stickyUnreadItem()))
? RSS::Session::instance()->rootFolder()
: qobject_cast<RSS::Folder *>(m_feedListWidget->getRSSItem(destItem)));
QString error;
// NOTE: We still add feed using legacy way (with URL as feed name)
const QString newFeedPath = RSS::Item::joinPath(rssDestFolder->path(), newURL);
if (!RSS::Session::instance()->addFeed(newURL, newFeedPath, &error))
QMessageBox::warning(this, "qBittorrent", error, QMessageBox::Ok);
// Expand destination folder to display new feed
if (destItem && (destItem != m_feedListWidget->stickyUnreadItem()))
destItem->setExpanded(true);
// As new RSS items are added synchronously, we can do the following here.
m_feedListWidget->setCurrentItem(m_feedListWidget->mapRSSItem(RSS::Session::instance()->itemByPath(newFeedPath)));
}
void RSSWidget::deleteSelectedItems()
{
QList<QTreeWidgetItem *> selectedItems = m_feedListWidget->selectedItems();
if (selectedItems.isEmpty())
return;
if ((selectedItems.size() == 1) && (selectedItems.first() == m_feedListWidget->stickyUnreadItem()))
return;
QMessageBox::StandardButton answer = QMessageBox::question(
this, tr("Deletion confirmation"), tr("Are you sure you want to delete the selected RSS feeds?")
, QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
if (answer == QMessageBox::No)
return;
foreach (QTreeWidgetItem *item, selectedItems)
if (item != m_feedListWidget->stickyUnreadItem())
RSS::Session::instance()->removeItem(m_feedListWidget->itemPath(item));
}
void RSSWidget::loadFoldersOpenState()
{
const QStringList openedFolders = Preferences::instance()->getRssOpenFolders();
foreach (const QString &varPath, openedFolders) {
QTreeWidgetItem *parent = nullptr;
foreach (const QString &name, varPath.split("\\")) {
int nbChildren = (parent ? parent->childCount() : m_feedListWidget->topLevelItemCount());
for (int i = 0; i < nbChildren; ++i) {
QTreeWidgetItem *child = (parent ? parent->child(i) : m_feedListWidget->topLevelItem(i));
if (m_feedListWidget->getRSSItem(child)->name() == name) {
parent = child;
parent->setExpanded(true);
break;
}
}
}
}
}
void RSSWidget::saveFoldersOpenState()
{
QStringList openedFolders;
foreach (QTreeWidgetItem *item, m_feedListWidget->getAllOpenedFolders())
openedFolders << m_feedListWidget->itemPath(item);
Preferences::instance()->setRssOpenFolders(openedFolders);
}
void RSSWidget::refreshAllFeeds()
{
RSS::Session::instance()->refresh();
}
void RSSWidget::downloadSelectedTorrents()
{
foreach (QListWidgetItem *item, m_articleListWidget->selectedItems()) {
auto article = reinterpret_cast<RSS::Article *>(item->data(Qt::UserRole).value<quintptr>());
Q_ASSERT(article);
// Mark as read
article->markAsRead();
if (!article->torrentUrl().isEmpty()) {
if (AddNewTorrentDialog::isEnabled())
AddNewTorrentDialog::show(article->torrentUrl());
else
BitTorrent::Session::instance()->addTorrent(article->torrentUrl());
}
}
}
// open the url of the selected RSS articles in the Web browser
void RSSWidget::openSelectedArticlesUrls()
{
foreach (QListWidgetItem *item, m_articleListWidget->selectedItems()) {
auto article = reinterpret_cast<RSS::Article *>(item->data(Qt::UserRole).value<quintptr>());
Q_ASSERT(article);
// Mark as read
article->markAsRead();
if (!article->link().isEmpty())
QDesktopServices::openUrl(QUrl(article->link()));
}
}
void RSSWidget::renameSelectedRSSItem()
{
QList<QTreeWidgetItem *> selectedItems = m_feedListWidget->selectedItems();
if (selectedItems.size() != 1) return;
QTreeWidgetItem *item = selectedItems.first();
if (item == m_feedListWidget->stickyUnreadItem())
return;
RSS::Item *rssItem = m_feedListWidget->getRSSItem(item);
const QString parentPath = RSS::Item::parentPath(rssItem->path());
bool ok;
do {
QString newName = AutoExpandableDialog::getText(
this, tr("Please choose a new name for this RSS feed"), tr("New feed name:")
, QLineEdit::Normal, rssItem->name(), &ok);
// Check if name is already taken
if (!ok) return;
QString error;
if (!RSS::Session::instance()->moveItem(rssItem, RSS::Item::joinPath(parentPath, newName), &error)) {
QMessageBox::warning(0, tr("Rename failed"), error);
ok = false;
}
} while (!ok);
}
void RSSWidget::refreshSelectedItems()
{
foreach (QTreeWidgetItem *item, m_feedListWidget->selectedItems()) {
if (item == m_feedListWidget->stickyUnreadItem()) {
refreshAllFeeds();
return;
}
m_feedListWidget->getRSSItem(item)->refresh();
}
}
void RSSWidget::copySelectedFeedsURL()
{
QStringList URLs;
foreach (QTreeWidgetItem *item, m_feedListWidget->selectedItems()) {
if (auto feed = qobject_cast<RSS::Feed *>(m_feedListWidget->getRSSItem(item)))
URLs << feed->url();
}
qApp->clipboard()->setText(URLs.join("\n"));
}
void RSSWidget::handleCurrentFeedItemChanged(QTreeWidgetItem *currentItem)
{
if (!currentItem) {
m_articleListWidget->clear();
return;
}
m_articleListWidget->setRSSItem(m_feedListWidget->getRSSItem(currentItem)
, (currentItem == m_feedListWidget->stickyUnreadItem()));
}
void RSSWidget::on_markReadButton_clicked()
{
foreach (QTreeWidgetItem *item, m_feedListWidget->selectedItems()) {
m_feedListWidget->getRSSItem(item)->markAsRead();
if (item == m_feedListWidget->stickyUnreadItem())
break; // all items was read
}
}
// display a news
void RSSWidget::handleCurrentArticleItemChanged(QListWidgetItem *currentItem, QListWidgetItem *previousItem)
{
m_ui->textBrowser->clear();
if (previousItem) {
auto article = m_articleListWidget->getRSSArticle(previousItem);
Q_ASSERT(article);
article->markAsRead();
}
if (!currentItem) return;
auto article = m_articleListWidget->getRSSArticle(currentItem);
Q_ASSERT(article);
QString html;
html += "<div style='border: 2px solid red; margin-left: 5px; margin-right: 5px; margin-bottom: 5px;'>";
html += "<div style='background-color: #678db2; font-weight: bold; color: #fff;'>" + article->title() + "</div>";
if (article->date().isValid())
html += "<div style='background-color: #efefef;'><b>" + tr("Date: ") + "</b>" + article->date().toLocalTime().toString(Qt::SystemLocaleLongDate) + "</div>";
if (!article->author().isEmpty())
html += "<div style='background-color: #efefef;'><b>" + tr("Author: ") + "</b>" + article->author() + "</div>";
html += "</div>";
html += "<div style='margin-left: 5px; margin-right: 5px;'>";
if (Qt::mightBeRichText(article->description())) {
html += article->description();
}
else {
QString description = article->description();
QRegExp rx;
// If description is plain text, replace BBCode tags with HTML and wrap everything in <pre></pre> so it looks nice
rx.setMinimal(true);
rx.setCaseSensitivity(Qt::CaseInsensitive);
rx.setPattern("\\[img\\](.+)\\[/img\\]");
description = description.replace(rx, "<img src=\"\\1\">");
rx.setPattern("\\[url=(\")?(.+)\\1\\]");
description = description.replace(rx, "<a href=\"\\2\">");
description = description.replace("[/url]", "</a>", Qt::CaseInsensitive);
rx.setPattern("\\[(/)?([bius])\\]");
description = description.replace(rx, "<\\1\\2>");
rx.setPattern("\\[color=(\")?(.+)\\1\\]");
description = description.replace(rx, "<span style=\"color:\\2\">");
description = description.replace("[/color]", "</span>", Qt::CaseInsensitive);
rx.setPattern("\\[size=(\")?(.+)\\d\\1\\]");
description = description.replace(rx, "<span style=\"font-size:\\2px\">");
description = description.replace("[/size]", "</span>", Qt::CaseInsensitive);
html += "<pre>" + description + "</pre>";
}
html += "</div>";
m_ui->textBrowser->setHtml(html);
}
void RSSWidget::saveSlidersPosition()
{
// Remember sliders positions
Preferences *const pref = Preferences::instance();
pref->setRssSideSplitterState(m_ui->splitterSide->saveState());
pref->setRssMainSplitterState(m_ui->splitterMain->saveState());
}
void RSSWidget::restoreSlidersPosition()
{
const Preferences *const pref = Preferences::instance();
const QByteArray stateSide = pref->getRssSideSplitterState();
if (!stateSide.isEmpty())
m_ui->splitterSide->restoreState(stateSide);
const QByteArray stateMain = pref->getRssMainSplitterState();
if (!stateMain.isEmpty())
m_ui->splitterMain->restoreState(stateMain);
}
void RSSWidget::updateRefreshInterval(uint val)
{
RSS::Session::instance()->setRefreshInterval(val);
}
void RSSWidget::on_rssDownloaderBtn_clicked()
{
AutomatedRssDownloader(this).exec();
}
void RSSWidget::handleSessionProcessingStateChanged(bool enabled)
{
m_ui->labelWarn->setVisible(!enabled);
}
void RSSWidget::handleUnreadCountChanged()
{
emit unreadCountUpdated(RSS::Session::instance()->rootFolder()->unreadCount());
}

View File

@@ -1,6 +1,8 @@
/*
* Bittorrent Client using Qt4 and libtorrent.
* Copyright (C) 2006 Christophe Dumez, Arnaud Demaiziere
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2017 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
* Copyright (C) 2006 Arnaud Demaiziere <arnaud@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -24,46 +26,38 @@
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*
* Contact : chris@qbittorrent.org arnaud@qbittorrent.org
*/
#ifndef __RSS_IMP_H__
#define __RSS_IMP_H__
#define REFRESH_MAX_LATENCY 600000
#ifndef RSSWIDGET_H
#define RSSWIDGET_H
#include <QPointer>
#include <QShortcut>
#include "base/rss/rssfolder.h"
#include "base/rss/rssmanager.h"
class ArticleListWidget;
class FeedListWidget;
QT_BEGIN_NAMESPACE
class QListWidgetItem;
class QTreeWidgetItem;
QT_END_NAMESPACE
namespace Ui
{
class RSS;
class RSSWidget;
}
class RSSImp: public QWidget
class RSSWidget: public QWidget
{
Q_OBJECT
public:
RSSImp(QWidget * parent);
~RSSImp();
RSSWidget(QWidget *parent);
~RSSWidget();
public slots:
void deleteSelectedItems();
void updateRefreshInterval(uint val);
signals:
void updateRSSCount(int);
void unreadCountUpdated(int count);
private slots:
void on_newFeedButton_clicked();
@@ -71,38 +65,28 @@ private slots:
void on_markReadButton_clicked();
void displayRSSListMenu(const QPoint &);
void displayItemsListMenu(const QPoint &);
void renameSelectedRssFile();
void renameSelectedRSSItem();
void refreshSelectedItems();
void copySelectedFeedsURL();
void populateArticleList(QTreeWidgetItem *item);
void refreshTextBrowser();
void updateFeedIcon(const QString &url, const QString &icon_path);
void updateFeedInfos(const QString &url, const QString &display_name, uint nbUnread);
void onFeedContentChanged(const QString &url);
void updateItemsInfos(const QList<QTreeWidgetItem *> &items);
void updateItemInfos(QTreeWidgetItem *item);
void handleCurrentFeedItemChanged(QTreeWidgetItem *currentItem);
void handleCurrentArticleItemChanged(QListWidgetItem *currentItem, QListWidgetItem *previousItem);
void openSelectedArticlesUrls();
void downloadSelectedTorrents();
void fillFeedsList(QTreeWidgetItem *parent = 0, const Rss::FolderPtr &rss_parent = Rss::FolderPtr());
void saveSlidersPosition();
void restoreSlidersPosition();
void askNewFolder();
void saveFoldersOpenState();
void loadFoldersOpenState();
void on_settingsButton_clicked();
void on_rssDownloaderBtn_clicked();
void handleSessionProcessingStateChanged(bool enabled);
void handleUnreadCountChanged();
private:
static QListWidgetItem *createArticleListItem(const Rss::ArticlePtr &article);
static QTreeWidgetItem *createFolderListItem(const Rss::FilePtr &rssFile);
private:
Ui::RSS *m_ui;
Rss::ManagerPtr m_rssManager;
FeedListWidget *m_feedList;
QListWidgetItem *m_currentArticle;
QShortcut *editHotkey;
QShortcut *deleteHotkey;
Ui::RSSWidget *m_ui;
ArticleListWidget *m_articleListWidget;
FeedListWidget *m_feedListWidget;
QShortcut *m_editHotkey;
QShortcut *m_deleteHotkey;
};
#endif
#endif // RSSWIDGET_H

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>RSS</class>
<widget class="QWidget" name="RSS">
<class>RSSWidget</class>
<widget class="QWidget" name="RSSWidget">
<property name="geometry">
<rect>
<x>0</x>
@@ -17,6 +17,24 @@
<string>Search</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="labelWarn">
<property name="font">
<font>
<italic>true</italic>
</font>
</property>
<property name="styleSheet">
<string notr="true">color: red;</string>
</property>
<property name="text">
<string>Fetching of RSS feeds is disabled now! You can enable it in application settings.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
@@ -63,13 +81,6 @@
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="settingsButton">
<property name="text">
<string>Settings...</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
@@ -109,14 +120,6 @@
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QListWidget" name="listArticles">
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
</widget>
<widget class="HtmlBrowser" name="textBrowser">
<property name="openExternalLinks">
<bool>true</bool>
@@ -153,12 +156,12 @@
<string>Update</string>
</property>
</action>
<action name="actionNew_subscription">
<action name="actionNewSubscription">
<property name="text">
<string>New subscription...</string>
</property>
</action>
<action name="actionUpdate_all_feeds">
<action name="actionUpdateAllFeeds">
<property name="text">
<string>Update all feeds</string>
</property>
@@ -166,7 +169,7 @@
<string>Update all feeds</string>
</property>
</action>
<action name="actionMark_items_read">
<action name="actionMarkItemsRead">
<property name="text">
<string>Mark items read</string>
</property>
@@ -174,22 +177,22 @@
<string>Mark items read</string>
</property>
</action>
<action name="actionDownload_torrent">
<action name="actionDownloadTorrent">
<property name="text">
<string>Download torrent</string>
</property>
</action>
<action name="actionOpen_news_URL">
<action name="actionOpenNewsURL">
<property name="text">
<string>Open news URL</string>
</property>
</action>
<action name="actionCopy_feed_URL">
<action name="actionCopyFeedURL">
<property name="text">
<string>Copy feed URL</string>
</property>
</action>
<action name="actionNew_folder">
<action name="actionNewFolder">
<property name="text">
<string>New folder...</string>
</property>
@@ -199,7 +202,7 @@
<customwidget>
<class>HtmlBrowser</class>
<extends>QTextBrowser</extends>
<header>htmlbrowser.h</header>
<header>gui/rss/htmlbrowser.h</header>
</customwidget>
</customwidgets>
<resources/>