2 This file is part of Akregator.
4 Copyright (C) 2007 Frank Osterfeld <osterfeld@kde.org>
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
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.
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.
20 As a special exception, permission is given to link this program
21 with any edition of Qt, and distribute the resulting executable,
22 without including the source code for Qt in the source distribution.
24 #include "articlemodel.h"
27 #include "articlematcher.h"
28 #include "akregatorconfig.h"
31 #include <Syndication/Tools>
37 #include <KLocalizedString>
46 using namespace Akregator
;
48 class Q_DECL_HIDDEN
ArticleModel::Private
51 ArticleModel
*const q
;
53 Private(const QVector
<Article
> &articles
, ArticleModel
*qq
);
54 QVector
<Article
> articles
;
55 QVector
<QString
> titleCache
;
57 void articlesAdded(const QVector
<Article
> &);
58 void articlesRemoved(const QVector
<Article
> &);
59 void articlesUpdated(const QVector
<Article
> &);
63 //like Syndication::htmlToPlainText, but without linebreaks
65 static QString
stripHtml(const QString
&html
)
68 //TODO: preserve some formatting, such as line breaks
69 str
.remove(QRegExp(QLatin1String("<[^>]*>"))); // remove tags
70 str
= Syndication::resolveEntities(str
);
71 return str
.simplified();
74 ArticleModel::Private::Private(const QVector
<Article
> &articles_
, ArticleModel
*qq
)
75 : q(qq
), articles(articles_
)
77 const int articlesCount(articles
.count());
78 titleCache
.resize(articlesCount
);
79 for (int i
= 0; i
< articlesCount
; ++i
) {
80 titleCache
[i
] = stripHtml(articles
[i
].title());
84 ArticleModel::ArticleModel(const QVector
<Article
> &articles
, QObject
*parent
) : QAbstractTableModel(parent
), d(new Private(articles
, this))
88 ArticleModel::~ArticleModel()
93 int ArticleModel::columnCount(const QModelIndex
&parent
) const
95 return parent
.isValid() ? 0 : ColumnCount
;
98 int ArticleModel::rowCount(const QModelIndex
&parent
) const
100 return parent
.isValid() ? 0 : d
->articles
.count();
103 QVariant
ArticleModel::headerData(int section
, Qt::Orientation
, int role
) const
105 if (role
!= Qt::DisplayRole
) {
110 case ItemTitleColumn
:
111 return i18nc("Articlelist's column header", "Title");
112 case FeedTitleColumn
:
113 return i18nc("Articlelist's column header", "Feed");
115 return i18nc("Articlelist's column header", "Date");
117 return i18nc("Articlelist's column header", "Author");
118 case DescriptionColumn
:
119 return i18nc("Articlelist's column header", "Description");
121 return i18nc("Articlelist's column header", "Content");
127 QVariant
ArticleModel::data(const QModelIndex
&index
, int role
) const
129 if (!index
.isValid() || index
.row() < 0 || index
.row() >= d
->articles
.count()) {
132 const int row
= index
.row();
133 const Article
&article(d
->articles
[row
]);
135 if (article
.isNull()) {
141 if (index
.column() == DateColumn
) {
142 return article
.pubDate();
145 case Qt::DisplayRole
: {
146 switch (index
.column()) {
147 case FeedTitleColumn
:
148 return article
.feed() ? article
.feed()->title() : QVariant();
150 return QLocale().toString(article
.pubDate(), QLocale::ShortFormat
);
151 case ItemTitleColumn
:
152 return d
->titleCache
[row
];
154 return article
.authorShort();
155 case DescriptionColumn
:
157 return article
.description();
161 return article
.link();
165 return article
.guid();
168 return article
.feed() ? article
.feed()->xmlUrl() : QVariant();
171 return article
.status();
173 case IsImportantRole
: {
174 return article
.keep();
176 case IsDeletedRole
: {
177 return article
.isDeleted();
184 void ArticleModel::clear()
188 d
->titleCache
.clear();
192 void ArticleModel::articlesAdded(TreeNode
*, const QVector
<Article
> &l
)
197 void ArticleModel::articlesRemoved(TreeNode
*, const QVector
<Article
> &l
)
199 d
->articlesRemoved(l
);
201 void ArticleModel::articlesUpdated(TreeNode
*, const QVector
<Article
> &l
)
203 d
->articlesUpdated(l
);
206 void ArticleModel::Private::articlesAdded(const QVector
<Article
> &list
)
208 if (list
.isEmpty()) { //assert?
211 const int first
= articles
.count();
212 q
->beginInsertRows(QModelIndex(), first
, first
+ list
.size() - 1);
214 const int oldSize
= articles
.size();
217 const int newArticlesCount(articles
.count());
218 titleCache
.resize(newArticlesCount
);
219 for (int i
= oldSize
; i
< newArticlesCount
; ++i
) {
220 titleCache
[i
] = stripHtml(articles
[i
].title());
225 void ArticleModel::Private::articlesRemoved(const QVector
<Article
> &list
)
227 //might want to avoid indexOf() in case of performance problems
228 Q_FOREACH (const Article
&i
, list
) {
229 const int row
= articles
.indexOf(i
);
231 q
->removeRow(row
, QModelIndex());
235 void ArticleModel::Private::articlesUpdated(const QVector
<Article
> &list
)
240 const int numberOfArticles(articles
.count());
241 if (numberOfArticles
> 0) {
242 rmin
= numberOfArticles
- 1;
243 //might want to avoid indexOf() in case of performance problems
244 Q_FOREACH (const Article
&i
, list
) {
245 const int row
= articles
.indexOf(i
);
246 //TODO: figure out how why the Article might not be found in
247 //TODO: the articles list because we should need this conditional.
249 titleCache
[row
] = stripHtml(articles
[row
].title());
250 rmin
= std::min(row
, rmin
);
251 rmax
= std::max(row
, rmax
);
255 Q_EMIT q
->dataChanged(q
->index(rmin
, 0), q
->index(rmax
, ColumnCount
- 1));
258 bool ArticleModel::rowMatches(int row
, const QSharedPointer
<const Filters::AbstractMatcher
> &matcher
) const
261 return matcher
->matches(article(row
));
264 Article
ArticleModel::article(int row
) const
266 if (row
< 0 || row
>= d
->articles
.count()) {
269 return d
->articles
[row
];
272 QStringList
ArticleModel::mimeTypes() const
274 return QStringList() << QStringLiteral("text/uri-list");
277 QMimeData
*ArticleModel::mimeData(const QModelIndexList
&indexes
) const
279 QScopedPointer
<QMimeData
> md(new QMimeData
);
281 Q_FOREACH (const QModelIndex
&i
, indexes
) {
282 const QUrl url
= i
.data(ArticleModel::LinkRole
).toUrl();
286 const QUrl
guid(i
.data(ArticleModel::GuidRole
).toString());
287 if (guid
.isValid()) {
288 urls
.push_back(guid
);
296 Qt::ItemFlags
ArticleModel::flags(const QModelIndex
&idx
) const
298 const Qt::ItemFlags f
= QAbstractTableModel::flags(idx
);
299 if (!idx
.isValid()) {
302 return f
| Qt::ItemIsDragEnabled
;