qBittorrent
feedlistwidget.cpp
Go to the documentation of this file.
1 /*
2  * Bittorrent Client using Qt and libtorrent.
3  * Copyright (C) 2017 Vladimir Golovnev <[email protected]>
4  * Copyright (C) 2010 Christophe Dumez <[email protected]>
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19  *
20  * In addition, as a special exception, the copyright holders give permission to
21  * link this program with the OpenSSL project's "OpenSSL" library (or with
22  * modified versions of it that use the same license as the "OpenSSL" library),
23  * and distribute the linked executables. You must obey the GNU General Public
24  * License in all respects for all of the code used other than "OpenSSL". If you
25  * modify file(s), you may extend this exception to your version of the file(s),
26  * but you are not obligated to do so. If you do not wish to do so, delete this
27  * exception statement from your version.
28  */
29 
30 #include "feedlistwidget.h"
31 
32 #include <QDragMoveEvent>
33 #include <QDropEvent>
34 #include <QHeaderView>
35 #include <QTreeWidgetItem>
36 
37 #include "base/global.h"
38 #include "base/rss/rss_article.h"
39 #include "base/rss/rss_feed.h"
40 #include "base/rss/rss_folder.h"
41 #include "base/rss/rss_session.h"
42 #include "gui/uithememanager.h"
43 
44 namespace
45 {
46  enum
47  {
48  StickyItemTagRole = Qt::UserRole + 1
49  };
50 
51  class FeedListItem final : public QTreeWidgetItem
52  {
53  public:
54  using QTreeWidgetItem::QTreeWidgetItem;
55 
56  private:
57  bool operator<(const QTreeWidgetItem &other) const override
58  {
59  const bool lhsSticky = data(0, StickyItemTagRole).toBool();
60  const bool rhsSticky = other.data(0, StickyItemTagRole).toBool();
61 
62  if (lhsSticky == rhsSticky)
63  return QTreeWidgetItem::operator<(other);
64 
65  const int order = treeWidget()->header()->sortIndicatorOrder();
66  return ((order == Qt::AscendingOrder) ? lhsSticky : rhsSticky);
67  }
68  };
69 
70  QIcon loadIcon(const QString &path, const QString &fallbackId)
71  {
72  const QPixmap pixmap {path};
73  if (!pixmap.isNull())
74  return {pixmap};
75 
76  return UIThemeManager::instance()->getIcon(fallbackId);
77  }
78 
79  QIcon rssFeedIcon(const RSS::Feed *feed)
80  {
81  if (feed->isLoading())
82  return UIThemeManager::instance()->getIcon(QLatin1String("loading"));
83  if (feed->hasError())
84  return UIThemeManager::instance()->getIcon(QLatin1String("unavailable"));
85 
86  return loadIcon(feed->iconPath(), QLatin1String("application-rss+xml"));
87  }
88 }
89 
91  : QTreeWidget(parent)
92 {
93  setContextMenuPolicy(Qt::CustomContextMenu);
94  setDragDropMode(QAbstractItemView::InternalMove);
95  setSelectionMode(QAbstractItemView::ExtendedSelection);
96  setColumnCount(1);
97  headerItem()->setText(0, tr("RSS feeds"));
98 
104 
105  m_rssToTreeItemMapping[RSS::Session::instance()->rootFolder()] = invisibleRootItem();
106 
107  m_unreadStickyItem = new FeedListItem(this);
108  m_unreadStickyItem->setData(0, Qt::UserRole, reinterpret_cast<quintptr>(RSS::Session::instance()->rootFolder()));
109  m_unreadStickyItem->setText(0, tr("Unread (%1)").arg(RSS::Session::instance()->rootFolder()->unreadCount()));
110  m_unreadStickyItem->setData(0, Qt::DecorationRole, UIThemeManager::instance()->getIcon("mail-folder-inbox"));
111  m_unreadStickyItem->setData(0, StickyItemTagRole, true);
112 
113 
115 
116  setSortingEnabled(false);
117  fill(nullptr, RSS::Session::instance()->rootFolder());
118  setSortingEnabled(true);
119 
120 // setCurrentItem(m_unreadStickyItem);
121 }
122 
124 {
125  auto parentItem = m_rssToTreeItemMapping.value(
126  RSS::Session::instance()->itemByPath(RSS::Item::parentPath(rssItem->path())));
127  createItem(rssItem, parentItem);
128 }
129 
131 {
132  QTreeWidgetItem *item = m_rssToTreeItemMapping.value(feed);
133  Q_ASSERT(item);
134 
135  item->setData(0, Qt::DecorationRole, rssFeedIcon(feed));
136 }
137 
139 {
140  if (!feed->isLoading() && !feed->hasError())
141  {
142  QTreeWidgetItem *item = m_rssToTreeItemMapping.value(feed);
143  Q_ASSERT(item);
144 
145  item->setData(0, Qt::DecorationRole, rssFeedIcon(feed));
146  }
147 }
148 
150 {
151  if (rssItem == RSS::Session::instance()->rootFolder())
152  {
153  m_unreadStickyItem->setText(0, tr("Unread (%1)").arg(RSS::Session::instance()->rootFolder()->unreadCount()));
154  }
155  else
156  {
157  QTreeWidgetItem *item = mapRSSItem(rssItem);
158  Q_ASSERT(item);
159  item->setData(0, Qt::DisplayRole, QString::fromLatin1("%1 (%2)").arg(rssItem->name(), QString::number(rssItem->unreadCount())));
160  }
161 }
162 
164 {
165  QTreeWidgetItem *item = mapRSSItem(rssItem);
166  Q_ASSERT(item);
167 
168  item->setData(0, Qt::DisplayRole, QString::fromLatin1("%1 (%2)").arg(rssItem->name(), QString::number(rssItem->unreadCount())));
169 
170  RSS::Item *parentRssItem = RSS::Session::instance()->itemByPath(RSS::Item::parentPath(rssItem->path()));
171  QTreeWidgetItem *parentItem = mapRSSItem(parentRssItem);
172  Q_ASSERT(parentItem);
173 
174  parentItem->addChild(item);
175 }
176 
178 {
179  rssItem->disconnect(this);
180  delete m_rssToTreeItemMapping.take(rssItem);
181 
182  // RSS Item is still valid in this slot so if it is the last
183  // item we should prevent Unread list populating
184  if (m_rssToTreeItemMapping.size() == 1)
185  setCurrentItem(nullptr);
186 }
187 
188 QTreeWidgetItem *FeedListWidget::stickyUnreadItem() const
189 {
190  return m_unreadStickyItem;
191 }
192 
193 QList<QTreeWidgetItem *> FeedListWidget::getAllOpenedFolders(QTreeWidgetItem *parent) const
194 {
195  QList<QTreeWidgetItem *> openedFolders;
196  int nbChildren = (parent ? parent->childCount() : topLevelItemCount());
197  for (int i = 0; i < nbChildren; ++i)
198  {
199  QTreeWidgetItem *item (parent ? parent->child(i) : topLevelItem(i));
200  if (isFolder(item) && item->isExpanded())
201  {
202  QList<QTreeWidgetItem *> openedSubfolders = getAllOpenedFolders(item);
203  if (!openedSubfolders.empty())
204  openedFolders << openedSubfolders;
205  else
206  openedFolders << item;
207  }
208  }
209  return openedFolders;
210 }
211 
212 RSS::Item *FeedListWidget::getRSSItem(QTreeWidgetItem *item) const
213 {
214  if (!item) return nullptr;
215 
216  return reinterpret_cast<RSS::Item *>(item->data(0, Qt::UserRole).value<quintptr>());
217 }
218 
219 QTreeWidgetItem *FeedListWidget::mapRSSItem(RSS::Item *rssItem) const
220 {
221  return m_rssToTreeItemMapping.value(rssItem);
222 }
223 
224 QString FeedListWidget::itemPath(QTreeWidgetItem *item) const
225 {
226  return getRSSItem(item)->path();
227 }
228 
229 bool FeedListWidget::isFeed(QTreeWidgetItem *item) const
230 {
231  return qobject_cast<RSS::Feed *>(getRSSItem(item));
232 }
233 
234 bool FeedListWidget::isFolder(QTreeWidgetItem *item) const
235 {
236  return qobject_cast<RSS::Folder *>(getRSSItem(item));
237 }
238 
239 void FeedListWidget::dragMoveEvent(QDragMoveEvent *event)
240 {
241  QTreeWidget::dragMoveEvent(event);
242 
243  QTreeWidgetItem *item = itemAt(event->pos());
244  // Prohibit dropping onto global unread counter
245  if (item == m_unreadStickyItem)
246  event->ignore();
247  // Prohibit dragging of global unread counter
248  else if (selectedItems().contains(m_unreadStickyItem))
249  event->ignore();
250  // Prohibit dropping onto feeds
251  else if (item && isFeed(item))
252  event->ignore();
253 }
254 
255 void FeedListWidget::dropEvent(QDropEvent *event)
256 {
257  QTreeWidgetItem *destFolderItem = itemAt(event->pos());
258  RSS::Folder *destFolder = (destFolderItem
259  ? static_cast<RSS::Folder *>(getRSSItem(destFolderItem))
261 
262  // move as much items as possible
263  for (QTreeWidgetItem *srcItem : asConst(selectedItems()))
264  {
265  auto rssItem = getRSSItem(srcItem);
266  RSS::Session::instance()->moveItem(rssItem, RSS::Item::joinPath(destFolder->path(), rssItem->name()));
267  }
268 
269  QTreeWidget::dropEvent(event);
270  if (destFolderItem)
271  destFolderItem->setExpanded(true);
272 }
273 
274 QTreeWidgetItem *FeedListWidget::createItem(RSS::Item *rssItem, QTreeWidgetItem *parentItem)
275 {
276  auto *item = new FeedListItem;
277  item->setData(0, Qt::DisplayRole, QString::fromLatin1("%1 (%2)").arg(rssItem->name(), QString::number(rssItem->unreadCount())));
278  item->setData(0, Qt::UserRole, reinterpret_cast<quintptr>(rssItem));
279  m_rssToTreeItemMapping[rssItem] = item;
280 
281  QIcon icon;
282  if (auto feed = qobject_cast<RSS::Feed *>(rssItem))
283  icon = rssFeedIcon(feed);
284  else
285  icon = UIThemeManager::instance()->getIcon(QLatin1String("inode-directory"));
286  item->setData(0, Qt::DecorationRole, icon);
287 
289 
290  if (!parentItem || (parentItem == m_unreadStickyItem))
291  addTopLevelItem(item);
292  else
293  parentItem->addChild(item);
294 
295  return item;
296 }
297 
298 void FeedListWidget::fill(QTreeWidgetItem *parent, RSS::Folder *rssParent)
299 {
300  for (const auto rssItem : asConst(rssParent->items()))
301  {
302  QTreeWidgetItem *item = createItem(rssItem, parent);
303  // Recursive call if this is a folder.
304  if (auto folder = qobject_cast<RSS::Folder *>(rssItem))
305  fill(item, folder);
306  }
307 }
QString itemPath(QTreeWidgetItem *item) const
void dragMoveEvent(QDragMoveEvent *event) override
void handleItemUnreadCountChanged(RSS::Item *rssItem)
QHash< RSS::Item *, QTreeWidgetItem * > m_rssToTreeItemMapping
QTreeWidgetItem * stickyUnreadItem() const
void handleFeedIconLoaded(RSS::Feed *feed)
bool isFolder(QTreeWidgetItem *item) const
RSS::Item * getRSSItem(QTreeWidgetItem *item) const
void handleItemPathChanged(RSS::Item *rssItem)
QTreeWidgetItem * m_unreadStickyItem
QTreeWidgetItem * createItem(RSS::Item *rssItem, QTreeWidgetItem *parentItem=nullptr)
void handleFeedStateChanged(RSS::Feed *feed)
void handleItemAdded(RSS::Item *rssItem)
void handleItemAboutToBeRemoved(RSS::Item *rssItem)
FeedListWidget(QWidget *parent)
QTreeWidgetItem * mapRSSItem(RSS::Item *rssItem) const
bool isFeed(QTreeWidgetItem *item) const
void dropEvent(QDropEvent *event) override
QList< QTreeWidgetItem * > getAllOpenedFolders(QTreeWidgetItem *parent=nullptr) const
void fill(QTreeWidgetItem *parent, RSS::Folder *rssParent)
QString iconPath() const
Definition: rss_feed.cpp:503
bool isLoading() const
Definition: rss_feed.cpp:164
bool hasError() const
Definition: rss_feed.cpp:199
QList< Item * > items() const
Definition: rss_folder.cpp:94
static QString joinPath(const QString &path1, const QString &path2)
Definition: rss_item.cpp:82
static QString parentPath(const QString &path)
Definition: rss_item.cpp:108
void unreadCountChanged(Item *item=nullptr)
QString name() const
Definition: rss_item.cpp:62
virtual int unreadCount() const =0
QString path() const
Definition: rss_item.cpp:57
static Session * instance()
void itemPathChanged(Item *item)
Folder * rootFolder() const
void feedStateChanged(Feed *feed)
Item * itemByPath(const QString &path) const
nonstd::expected< void, QString > moveItem(const QString &itemPath, const QString &destPath)
void itemAdded(Item *item)
void feedIconLoaded(Feed *feed)
void itemAboutToBeRemoved(Item *item)
static UIThemeManager * instance()
QIcon getIcon(const QString &iconId, const QString &fallback={}) const
bool operator<(const QTreeWidgetItem &other) const override
bool operator<(const Digest32< N > &left, const Digest32< N > &right)
Definition: digest32.h:110
constexpr std::add_const_t< T > & asConst(T &t) noexcept
Definition: global.h:42
QIcon loadIcon(const QString &path, const QString &fallbackId)
QIcon rssFeedIcon(const RSS::Feed *feed)