File indexing completed on 2024-04-28 05:11:01
0001 /* 0002 This file is part of Akregator. 0003 0004 SPDX-FileCopyrightText: 2007 Frank Osterfeld <osterfeld@kde.org> 0005 0006 SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0 0007 */ 0008 #include "articlemodel.h" 0009 0010 #include "akregatorconfig.h" 0011 #include "articlematcher.h" 0012 #include "feed.h" 0013 #include "utils.h" 0014 0015 #include <Syndication/Tools> 0016 0017 #include <QList> 0018 #include <QMimeData> 0019 #include <QString> 0020 0021 #include <KLocalizedString> 0022 #include <QUrl> 0023 0024 #include <memory> 0025 0026 #include <QLocale> 0027 #include <cassert> 0028 #include <cmath> 0029 0030 using namespace Akregator; 0031 0032 // like Syndication::htmlToPlainText, but without linebreaks 0033 0034 static QString stripHtml(const QString &html) 0035 { 0036 QString str(html); 0037 // TODO: preserve some formatting, such as line breaks 0038 str = Akregator::Utils::stripTags(str); // remove tags 0039 str = Syndication::resolveEntities(str); 0040 return str.simplified(); 0041 } 0042 0043 ArticleModel::ArticleModel(const QList<Article> &articles, QObject *parent) 0044 : QAbstractTableModel(parent) 0045 , m_articles(articles) 0046 { 0047 const int articlesCount(articles.count()); 0048 m_titleCache.resize(articlesCount); 0049 for (int i = 0; i < articlesCount; ++i) { 0050 m_titleCache[i] = stripHtml(articles[i].title()); 0051 } 0052 } 0053 0054 ArticleModel::~ArticleModel() = default; 0055 0056 int ArticleModel::columnCount(const QModelIndex &parent) const 0057 { 0058 return parent.isValid() ? 0 : ColumnCount; 0059 } 0060 0061 int ArticleModel::rowCount(const QModelIndex &parent) const 0062 { 0063 return parent.isValid() ? 0 : m_articles.count(); 0064 } 0065 0066 QVariant ArticleModel::headerData(int section, Qt::Orientation, int role) const 0067 { 0068 if (role != Qt::DisplayRole) { 0069 return {}; 0070 } 0071 0072 switch (section) { 0073 case ItemTitleColumn: 0074 return i18nc("Articlelist's column header", "Title"); 0075 case FeedTitleColumn: 0076 return i18nc("Articlelist's column header", "Feed"); 0077 case DateColumn: 0078 return i18nc("Articlelist's column header", "Date"); 0079 case AuthorColumn: 0080 return i18nc("Articlelist's column header", "Author"); 0081 case DescriptionColumn: 0082 return i18nc("Articlelist's column header", "Description"); 0083 case ContentColumn: 0084 return i18nc("Articlelist's column header", "Content"); 0085 } 0086 0087 return {}; 0088 } 0089 0090 QVariant ArticleModel::data(const QModelIndex &index, int role) const 0091 { 0092 if (!index.isValid() || index.row() < 0 || index.row() >= m_articles.count()) { 0093 return {}; 0094 } 0095 const int row = index.row(); 0096 const Article &article(m_articles[row]); 0097 0098 switch (role) { 0099 case SortRole: 0100 if (index.column() == DateColumn) { 0101 return article.pubDate(); 0102 } 0103 [[fallthrough]]; 0104 // no break 0105 case Qt::DisplayRole: 0106 switch (index.column()) { 0107 case FeedTitleColumn: 0108 return article.feed() ? article.feed()->title() : QVariant(); 0109 case DateColumn: 0110 return QLocale().toString(article.pubDate(), QLocale::ShortFormat); 0111 case ItemTitleColumn: 0112 return m_titleCache[row]; 0113 case AuthorColumn: 0114 return article.authorShort(); 0115 case DescriptionColumn: 0116 case ContentColumn: 0117 return article.description(); 0118 } 0119 case LinkRole: 0120 return article.link(); 0121 case ItemIdRole: 0122 case GuidRole: 0123 return article.guid(); 0124 case FeedIdRole: 0125 return article.feed() ? article.feed()->xmlUrl() : QVariant(); 0126 case StatusRole: 0127 return article.status(); 0128 case IsImportantRole: 0129 return article.keep(); 0130 case IsDeletedRole: 0131 return article.isDeleted(); 0132 } 0133 0134 return {}; 0135 } 0136 0137 void ArticleModel::clear() 0138 { 0139 beginResetModel(); 0140 m_articles.clear(); 0141 m_titleCache.clear(); 0142 endResetModel(); 0143 } 0144 0145 void ArticleModel::articlesAdded(Akregator::TreeNode *, const QList<Article> &l) 0146 { 0147 if (l.isEmpty()) { // assert? 0148 return; 0149 } 0150 const int first = m_articles.count(); 0151 beginInsertRows(QModelIndex(), first, first + l.size() - 1); 0152 0153 const int oldSize = m_articles.size(); 0154 m_articles << l; 0155 0156 const int newArticlesCount(m_articles.count()); 0157 m_titleCache.resize(newArticlesCount); 0158 for (int i = oldSize; i < newArticlesCount; ++i) { 0159 m_titleCache[i] = stripHtml(m_articles[i].title()); 0160 } 0161 endInsertRows(); 0162 } 0163 0164 void ArticleModel::articlesRemoved(Akregator::TreeNode *, const QList<Article> &l) 0165 { 0166 // might want to avoid indexOf() in case of performance problems 0167 for (const Article &i : l) { 0168 const int row = m_articles.indexOf(i); 0169 Q_ASSERT(row != -1); 0170 removeRow(row, QModelIndex()); 0171 } 0172 } 0173 0174 void ArticleModel::articlesUpdated(Akregator::TreeNode *, const QList<Article> &l) 0175 { 0176 int rmin = 0; 0177 int rmax = 0; 0178 0179 const int numberOfArticles(m_articles.count()); 0180 if (numberOfArticles > 0) { 0181 rmin = numberOfArticles - 1; 0182 // might want to avoid indexOf() in case of performance problems 0183 for (const Article &i : l) { 0184 const int row = m_articles.indexOf(i); 0185 // TODO: figure out how why the Article might not be found in 0186 // TODO: the articles list because we should need this conditional. 0187 if (row >= 0) { 0188 m_titleCache[row] = stripHtml(m_articles[row].title()); 0189 rmin = std::min(row, rmin); 0190 rmax = std::max(row, rmax); 0191 } 0192 } 0193 } 0194 Q_EMIT dataChanged(index(rmin, 0), index(rmax, ColumnCount - 1)); 0195 } 0196 0197 bool ArticleModel::rowMatches(int row, const QSharedPointer<const Filters::AbstractMatcher> &matcher) const 0198 { 0199 Q_ASSERT(matcher); 0200 return matcher->matches(article(row)); 0201 } 0202 0203 Article ArticleModel::article(int row) const 0204 { 0205 if (row < 0 || row >= m_articles.count()) { 0206 return {}; 0207 } 0208 return m_articles[row]; 0209 } 0210 0211 QStringList ArticleModel::mimeTypes() const 0212 { 0213 return QStringList() << QStringLiteral("text/uri-list"); 0214 } 0215 0216 QMimeData *ArticleModel::mimeData(const QModelIndexList &indexes) const 0217 { 0218 std::unique_ptr<QMimeData> md(new QMimeData); 0219 QList<QUrl> urls; 0220 QList<int> seenArticles; 0221 for (const QModelIndex &i : indexes) { 0222 const int rowIndex = i.row(); 0223 if (seenArticles.contains(rowIndex)) { 0224 continue; 0225 } 0226 seenArticles.append(rowIndex); 0227 const QUrl url = i.data(ArticleModel::LinkRole).toUrl(); 0228 if (url.isValid()) { 0229 urls.push_back(url); 0230 } else { 0231 const QUrl guid(i.data(ArticleModel::GuidRole).toString()); 0232 if (guid.isValid()) { 0233 urls.push_back(guid); 0234 } 0235 } 0236 } 0237 md->setUrls(urls); 0238 return md.release(); 0239 } 0240 0241 Qt::ItemFlags ArticleModel::flags(const QModelIndex &idx) const 0242 { 0243 const Qt::ItemFlags f = QAbstractTableModel::flags(idx); 0244 if (!idx.isValid()) { 0245 return f; 0246 } 0247 return f | Qt::ItemIsDragEnabled; 0248 } 0249 0250 #include "moc_articlemodel.cpp"