summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJavier S. Pedro <maemo@javispedro.com>2013-04-04 15:49:31 +0200
committerJavier S. Pedro <maemo@javispedro.com>2013-04-04 15:49:31 +0200
commitd8fcff1a2d6eb61c97c44790dbdb920ba9f52980 (patch)
tree49df7f1e07e34061301ad5944a1807feba24b526
parent11b4152301b408c7a4f02a8b202fed9f5e1ee1e7 (diff)
downloadtapasboard-d8fcff1a2d6eb61c97c44790dbdb920ba9f52980.tar.gz
tapasboard-d8fcff1a2d6eb61c97c44790dbdb920ba9f52980.zip
add showing unread posts
-rw-r--r--board.cpp42
-rw-r--r--board.h16
-rw-r--r--boardmodel.cpp5
-rw-r--r--boardmodel.h3
-rw-r--r--fetchpostsaction.cpp30
-rw-r--r--fetchpostsaction.h1
-rw-r--r--forummodel.cpp15
-rw-r--r--forummodel.h4
-rw-r--r--main.cpp30
-rw-r--r--qml/BoardPage.qml (renamed from qml/tapasboard/BoardPage.qml)2
-rw-r--r--qml/EmptyListDelegate.qml35
-rw-r--r--qml/ForumPage.qml (renamed from qml/tapasboard/ForumPage.qml)2
-rw-r--r--qml/GroupHeader.qml (renamed from qml/tapasboard/GroupHeader.qml)0
-rw-r--r--qml/MainPage.qml (renamed from qml/tapasboard/MainPage.qml)0
-rw-r--r--qml/TopicPage.qml (renamed from qml/tapasboard/TopicPage.qml)0
-rw-r--r--qml/main.qml (renamed from qml/tapasboard/main.qml)0
-rw-r--r--qml/tapasboard/EmptyListDelegate.qml29
-rw-r--r--tapasboard.pro14
-rw-r--r--topicmodel.cpp67
-rw-r--r--topicmodel.h11
20 files changed, 250 insertions, 56 deletions
diff --git a/board.cpp b/board.cpp
index fc5db62..7766088 100644
--- a/board.cpp
+++ b/board.cpp
@@ -21,7 +21,8 @@ Board::Board(QObject *parent) :
Board::Board(const QUrl& url, const QString& username, const QString& password, QObject *parent) :
QObject(parent), _url(url), _slug(createSlug(url)),
_db(QSqlDatabase::addDatabase("QSQLITE", _slug)),
- _iface(new XmlRpcInterface(QUrl(_url), this))
+ _iface(new XmlRpcInterface(QUrl(_url), this)),
+ _markReadDelay(new QTimer(this))
{
_db.setDatabaseName(QDir::toNativeSeparators(getDbPathFor(_slug)));
qDebug() << "Opening database file" << _db.databaseName() << "for" << _url;
@@ -47,6 +48,7 @@ Board::Board(const QUrl& url, const QString& username, const QString& password,
fetchForumsIfOutdated();
initializeBbCode(); // TODO This might depend on board config
initializeSmilies();
+ initializeMarkRead();
}
Board::~Board()
@@ -210,7 +212,18 @@ QString Board::renderHumanDate(const QDateTime &dateTime)
QString Board::renderHumanTime(const QDateTime &dateTime)
{
- return dateTime.toLocalTime().time().toString(Qt::DefaultLocaleShortDate);
+ QDateTime localDateTime = dateTime.toLocalTime();
+ const int secs = localDateTime.secsTo(QDateTime::currentDateTime());
+ if (secs < 1) {
+ return tr("Just now");
+ } else if (secs < 60) {
+ return tr("%n second(s) ago", 0, secs);
+ } else if (secs < 3600) {
+ int mins = (secs + 10) / 3600; // + 10 to round a bit
+ return tr("%n minute(s) ago", 0, mins);
+ } else {
+ return localDateTime.time().toString(Qt::DefaultLocaleShortDate);
+ }
}
void Board::cancelAllActions()
@@ -243,16 +256,22 @@ void Board::notifyForumsChanged()
void Board::notifyForumTopicsChanged(int forumId, int start, int end)
{
- qDebug() << "ForumTopics Changed" << forumId << start << end;
+ qDebug() << "ForumTopics changed" << forumId << start << end;
emit forumTopicsChanged(forumId, start, end);
}
void Board::notifyTopicPostsChanged(int topicId, int start, int end)
{
- qDebug() << "TopicPosts Changed" << topicId << start << end;
+ qDebug() << "TopicPosts changed" << topicId << start << end;
emit topicPostsChanged(topicId, start, end);
}
+void Board::notifyTopicPostsUnread(int topicId, int position)
+{
+ qDebug() << "TopicPosts unread" << topicId << position;
+ emit topicPostsUnread(topicId, position);
+}
+
void Board::notifyLogin(const QMap<QString, QVariant> &info)
{
if (_loginInfo.empty()) {
@@ -281,6 +300,14 @@ void Board::notifyLogout()
}
}
+void Board::markPostAsRead(int postId)
+{
+ _postsToMarkRead.insert(postId);
+ if (!_markReadDelay->isActive()) {
+ _markReadDelay->start();
+ }
+}
+
QString Board::createSlug(const QUrl& url)
{
static const QRegExp regexp("[^a-z0-9]+");
@@ -490,6 +517,13 @@ void Board::initializeSmilies()
Q_ASSERT(_smilieRegexp.isValid());
}
+void Board::initializeMarkRead()
+{
+ _markReadDelay->setInterval(1000); // 1 sec
+ _markReadDelay->setSingleShot(true);
+ // TODO connect
+}
+
void Board::fetchConfigIfOutdated()
{
if (_iface->isAccessible()) {
diff --git a/board.h b/board.h
index 588219c..698b4b6 100644
--- a/board.h
+++ b/board.h
@@ -5,6 +5,8 @@
#include <QtCore/QHash>
#include <QtCore/QPair>
#include <QtCore/QRegExp>
+#include <QtCore/QSet>
+#include <QtCore/QTimer>
#include <QtCore/QQueue>
#include <QtCore/QUrl>
#include <QtCore/QVariant>
@@ -63,9 +65,13 @@ public slots:
void notifyForumsChanged();
void notifyForumTopicsChanged(int forumId, int start, int end);
void notifyTopicPostsChanged(int topicId, int start, int end);
+ void notifyTopicPostsUnread(int topicId, int position);
void notifyLogin(const QMap<QString, QVariant>& info);
void notifyLogout();
+ // Functions for marking posts as read
+ void markPostAsRead(int postId);
+
signals:
void busyChanged();
void loggedInChanged();
@@ -73,6 +79,7 @@ signals:
void forumsChanged();
void forumTopicsChanged(int forumId, int start, int end);
void topicPostsChanged(int topicId, int start, int end);
+ void topicPostsUnread(int topicId, int position);
private:
static QString createSlug(const QUrl& url);
@@ -86,6 +93,7 @@ private:
void executeActionFromQueue();
void initializeBbCode();
void initializeSmilies();
+ void initializeMarkRead();
void fetchConfigIfOutdated();
void fetchForumsIfOutdated();
@@ -98,13 +106,21 @@ private:
QString _slug;
QSqlDatabase _db;
XmlRpcInterface *_iface;
+ /** The queue of pending actions. The first one is currently being run. */
QQueue<Action*> _queue;
/** Configuration cache */
mutable QHash<QString, QString> _config;
+ /** Login information, which is obviously not persistent. */
QMap<QString, QVariant> _loginInfo;
+ /** Bbcodes list and their HTML replacements for quick-and-dirty parsing. */
QList< QPair<QRegExp, QString> > _bbcodes;
+ /** List of smilies and their replacements. */
QHash<QString, QString> _smilies;
+ /** A regular expression that matches every possibly smilie. */
QRegExp _smilieRegexp;
+ /** This timer helps delay marking topics/posts as read. */
+ QTimer *_markReadDelay;
+ QSet<int> _postsToMarkRead;
};
inline bool Board::busy() const
diff --git a/boardmodel.cpp b/boardmodel.cpp
index dc1757e..3e24769 100644
--- a/boardmodel.cpp
+++ b/boardmodel.cpp
@@ -16,6 +16,7 @@ BoardModel::BoardModel(QObject *parent) :
roles[ForumIdRole] = QByteArray("forumId");
roles[SubOnlyRole] = QByteArray("subOnly");
roles[CategoryRole] = QByteArray("category");
+ roles[UnreadRole] = QByteArray("unread");
setRoleNames(roles);
}
@@ -78,6 +79,8 @@ QVariant BoardModel::data(const QModelIndex &index, int role) const
return _query.value(4);
case CategoryRole:
return _query.value(5);
+ case UnreadRole:
+ return _query.value(6);
}
return QVariant();
@@ -134,7 +137,7 @@ void BoardModel::reload()
if (_board && _forumId >= 0) {
_query = QSqlQuery(_board->database());
- _query.prepare("SELECT f1.forum_id,f1.forum_name,f1.logo_url,f1.description,f1.sub_only,f2.forum_name AS cat_name FROM forums f1 "
+ _query.prepare("SELECT f1.forum_id,f1.forum_name,f1.logo_url,f1.description,f1.sub_only,f2.forum_name,f1.new_post AS cat_name FROM forums f1 "
"LEFT JOIN forums f2 ON f2.forum_id = f1.parent_id "
"WHERE (f1.parent_id=:parent_id_1 AND f1.sub_only = 0) OR f1.parent_id IN "
"(SELECT forum_id from forums WHERE parent_id=:parent_id_2 AND sub_only=1) "
diff --git a/boardmodel.h b/boardmodel.h
index 56d127d..35ed447 100644
--- a/boardmodel.h
+++ b/boardmodel.h
@@ -22,7 +22,8 @@ public:
ForumIdRole = Qt::UserRole,
SubOnlyRole,
- CategoryRole
+ CategoryRole,
+ UnreadRole
};
Board * board() const;
diff --git a/fetchpostsaction.cpp b/fetchpostsaction.cpp
index 2493aa3..714718c 100644
--- a/fetchpostsaction.cpp
+++ b/fetchpostsaction.cpp
@@ -59,6 +59,16 @@ void FetchPostsAction::handleFinishedCall()
QSqlDatabase db = _board->database();
db.transaction();
+ bool ok = false;
+ int topic_id = map["topic_id"].toInt(&ok);
+ if (!ok) {
+ // Not fatal, just assume it's the one we requested
+ topic_id = _topicId;
+ }
+ QString topic_title = map["topic_title"].toString();
+ int unread_position = map["position"].toInt();
+ qDebug() << "unread_position" << unread_position;
+
QSqlQuery query(db);
query.prepare("INSERT OR REPLACE INTO posts (topic_id, post_id, post_title, post_content, post_author_id, post_author_name, can_edit, icon_url, post_time, last_update_time) "
"VALUES (:topic_id, :post_id, :post_title, :post_content, :post_author_id, :post_author_name, :can_edit, :icon_url, :post_time, :last_update_time)");
@@ -66,11 +76,6 @@ void FetchPostsAction::handleFinishedCall()
foreach (const QVariant& post_v, posts) {
QVariantMap post = post_v.toMap();
bool ok = false;
- int topic_id = post["topic_id"].toInt(&ok);
- if (!ok) {
- // Not fatal, just assume it's the one we requested
- topic_id = _topicId;
- }
int post_id = post["post_id"].toInt(&ok);
if (!ok) {
qWarning() << "No post_id in" << post;
@@ -79,7 +84,7 @@ void FetchPostsAction::handleFinishedCall()
query.bindValue(":topic_id", topic_id);
query.bindValue(":post_id", post_id);
- query.bindValue(":post_title", unencodePostText(post["post_title"]));
+ query.bindValue(":post_title", unencodePostTitle(post["post_title"], topic_title));
query.bindValue(":post_content", unencodePostContent(post["post_content"]));
query.bindValue(":post_author_id", post["post_author_id"].toInt());
query.bindValue(":post_author_name", unencodePostText(post["post_author_name"]));
@@ -100,6 +105,10 @@ void FetchPostsAction::handleFinishedCall()
_board->notifyTopicPostsChanged(_topicId,
_start, _start + posts.size() - 1);
}
+ if (unread_position > 0) {
+ // API says this is 1-indexed instead of 0-indexed.
+ _board->notifyTopicPostsUnread(_topicId, unread_position - 1);
+ }
if (_end == FetchAllPosts && posts.size() == MAX_TOPIC_PAGE_SIZE) {
// Ok, let's prepare to fetch the next block of posts because
// there are probably more of them
@@ -120,6 +129,15 @@ QString FetchPostsAction::unencodePostText(const QVariant &v)
return QString::fromUtf8(ba.constData(), ba.length());
}
+QString FetchPostsAction::unencodePostTitle(const QVariant &v, const QString &topicTitle)
+{
+ QString title = unencodePostText(v);
+ if (QString::compare(title, "Re: " + topicTitle, Qt::CaseInsensitive)) {
+ return QString();
+ }
+ return title;
+}
+
QString FetchPostsAction::unencodePostContent(const QVariant &v)
{
QString richText = _board->bbcodeToRichText(unencodePostText(v));
diff --git a/fetchpostsaction.h b/fetchpostsaction.h
index 732c0d2..e122622 100644
--- a/fetchpostsaction.h
+++ b/fetchpostsaction.h
@@ -26,6 +26,7 @@ private slots:
private:
static QString unencodePostText(const QVariant& v);
+ static QString unencodePostTitle(const QVariant& v, const QString& topicTitle);
QString unencodePostContent(const QVariant& v);
private:
diff --git a/forummodel.cpp b/forummodel.cpp
index 63fb0b8..faf0b75 100644
--- a/forummodel.cpp
+++ b/forummodel.cpp
@@ -15,6 +15,7 @@ ForumModel::ForumModel(QObject *parent) :
roles[IconRole] = QByteArray("icon");
roles[TopicIdRole] = QByteArray("topicId");
roles[NumRepliesRole] = QByteArray("numReplies");
+ roles[UnreadRole] = QByteArray("unread");
setRoleNames(roles);
}
@@ -84,6 +85,8 @@ QVariant ForumModel::data(const QModelIndex &index, int role) const
return _data[row].topic_id;
case NumRepliesRole:
return _data[row].num_replies;
+ case UnreadRole:
+ return _data[row].unread;
}
return QVariant();
@@ -103,7 +106,7 @@ void ForumModel::fetchMore(const QModelIndex &parent)
const int start = _data.size();
QList<Topic> topics = loadTopics(start, start + FORUM_PAGE_SIZE - 1);
- const int new_end = start + _data.size() - 1;
+ const int new_end = start + topics.size() - 1;
if (topics.empty()) {
// We could not load anything more from DB!
@@ -116,11 +119,12 @@ void ForumModel::fetchMore(const QModelIndex &parent)
}
if (_board->service()->isAccessible()) {
- if (!_data.empty()) {
+ if (!topics.empty()) {
QDateTime last = oldestPostUpdate(topics);
// If the topics we got from DB are too old, refresh online.
if (last.secsTo(QDateTime::currentDateTime()) > FORUM_TOPICS_TLL) {
qDebug() << "Fetching topics because of old";
+ Q_ASSERT(new_end > 0);
_board->enqueueAction(new FetchTopicsAction(_forumId,
start,
new_end,
@@ -190,7 +194,7 @@ QList<ForumModel::Topic> ForumModel::loadTopics(int start, int end)
const int rows = end - start + 1;
QList<Topic> topics;
QSqlQuery query(_board->database());
- query.prepare("SELECT topic_id, topic_title, reply_number, last_reply_time, last_update_time FROM topics "
+ query.prepare("SELECT topic_id, topic_title, reply_number, new_post, last_reply_time, last_update_time FROM topics "
"WHERE forum_id = :forum_id "
"ORDER by last_reply_time DESC "
"LIMIT :start, :limit");
@@ -204,8 +208,9 @@ QList<ForumModel::Topic> ForumModel::loadTopics(int start, int end)
topic.topic_id = query.value(0).toInt();
topic.title = query.value(1).toString();
topic.num_replies = query.value(2).toInt();
- topic.last_reply_time = parseDateTime(query.value(3));
- topic.last_update_time = parseDateTime(query.value(4));
+ topic.unread = query.value(3).toBool();
+ topic.last_reply_time = parseDateTime(query.value(4));
+ topic.last_update_time = parseDateTime(query.value(5));
topics.append(topic);
}
} else {
diff --git a/forummodel.h b/forummodel.h
index 4c00692..d0dd980 100644
--- a/forummodel.h
+++ b/forummodel.h
@@ -22,7 +22,8 @@ public:
TopicIdRole = Qt::UserRole,
TopicTypeRole,
- NumRepliesRole
+ NumRepliesRole,
+ UnreadRole
};
Board * board() const;
@@ -49,6 +50,7 @@ protected:
int topic_id;
QString title;
int num_replies;
+ bool unread;
QDateTime last_reply_time;
QDateTime last_update_time;
};
diff --git a/main.cpp b/main.cpp
index ccb6fbd..2d23694 100644
--- a/main.cpp
+++ b/main.cpp
@@ -13,12 +13,42 @@
BoardManager *board_manager;
+static QString adjustPath(const QString &path)
+{
+#ifdef Q_OS_UNIX
+#ifdef Q_OS_MAC
+ if (!QDir::isAbsolutePath(path))
+ return QString::fromLatin1("%1/../Resources/%2")
+ .arg(QCoreApplication::applicationDirPath(), path);
+#else
+ const QString pathInInstallDir =
+ QString::fromLatin1("%1/../%2").arg(QCoreApplication::applicationDirPath(), path);
+ if (QFileInfo(pathInInstallDir).exists())
+ return pathInInstallDir;
+#endif
+#endif
+ return path;
+}
+
Q_DECL_EXPORT int main(int argc, char *argv[])
{
QScopedPointer<QApplication> app(createApplication(argc, argv));
QApplication::setOrganizationDomain("com.javispedro.tapasboard");
QApplication::setOrganizationName("tapasboard");
QApplication::setApplicationName("tapasboard");
+ QApplication::setApplicationVersion("0.0.1");
+
+ const QString locale_path = adjustPath("i18n");
+ QString locale = QLocale::system().name();
+ QTranslator translator;
+
+ if (!(translator.load(locale, locale_path))) {
+ // Fallback to English
+ qDebug() << "Translation not available for" << locale;
+ translator.load("en", locale_path);
+ }
+
+ app->installTranslator(&translator);
QScopedPointer<BoardManager> manager(new BoardManager);
board_manager = manager.data(); // Set the global pointer to this singleton
diff --git a/qml/tapasboard/BoardPage.qml b/qml/BoardPage.qml
index 7efc6db..ca988f3 100644
--- a/qml/tapasboard/BoardPage.qml
+++ b/qml/BoardPage.qml
@@ -49,6 +49,8 @@ Page {
height: Math.max(forumItemColumn.height + UiConstants.ButtonSpacing * 2, UiConstants.ListItemHeightDefault)
+ unread: model.unread
+
Column {
id: forumItemColumn
anchors.left: parent.left
diff --git a/qml/EmptyListDelegate.qml b/qml/EmptyListDelegate.qml
new file mode 100644
index 0000000..71fa3dc
--- /dev/null
+++ b/qml/EmptyListDelegate.qml
@@ -0,0 +1,35 @@
+import QtQuick 1.1
+import com.nokia.meego 1.1
+
+Item {
+ id: listItem
+
+ signal clicked
+ property alias pressed: mouseArea.pressed
+ property bool unread: false
+
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.leftMargin: UiConstants.ButtonSpacing
+ height: UiConstants.ListItemHeightDefault
+
+ BorderImage {
+ id: background
+ anchors.fill: parent
+ anchors.leftMargin: -(UiConstants.DefaultMargin+parent.anchors.leftMargin)
+ anchors.rightMargin: -UiConstants.DefaultMargin
+ border { left: 22; right: 22; top: 22; bottom: 22; }
+ visible: pressed || unread
+ source: "image://theme/meegotouch" + (unread|(!unread&&!pressed)?"-unread-inbox":"")
+ + "-panel" + (theme.inverted?"-inverted":"")
+ + "-background" + (pressed?"-pressed":"")
+ }
+
+ MouseArea {
+ id: mouseArea;
+ anchors.fill: parent
+ onClicked: {
+ listItem.clicked();
+ }
+ }
+}
diff --git a/qml/tapasboard/ForumPage.qml b/qml/ForumPage.qml
index 96b8082..ee04e43 100644
--- a/qml/tapasboard/ForumPage.qml
+++ b/qml/ForumPage.qml
@@ -42,6 +42,8 @@ Page {
height: Math.max(topicItemColumn.height + UiConstants.ButtonSpacing * 2, UiConstants.ListItemHeightDefault)
+ unread: model.unread
+
Column {
id: topicItemColumn
anchors.left: parent.left
diff --git a/qml/tapasboard/GroupHeader.qml b/qml/GroupHeader.qml
index 0350ee0..0350ee0 100644
--- a/qml/tapasboard/GroupHeader.qml
+++ b/qml/GroupHeader.qml
diff --git a/qml/tapasboard/MainPage.qml b/qml/MainPage.qml
index e0e8fb8..e0e8fb8 100644
--- a/qml/tapasboard/MainPage.qml
+++ b/qml/MainPage.qml
diff --git a/qml/tapasboard/TopicPage.qml b/qml/TopicPage.qml
index 689dce7..689dce7 100644
--- a/qml/tapasboard/TopicPage.qml
+++ b/qml/TopicPage.qml
diff --git a/qml/tapasboard/main.qml b/qml/main.qml
index 11a7e44..11a7e44 100644
--- a/qml/tapasboard/main.qml
+++ b/qml/main.qml
diff --git a/qml/tapasboard/EmptyListDelegate.qml b/qml/tapasboard/EmptyListDelegate.qml
deleted file mode 100644
index 9a9d63d..0000000
--- a/qml/tapasboard/EmptyListDelegate.qml
+++ /dev/null
@@ -1,29 +0,0 @@
-import QtQuick 1.1
-import com.nokia.meego 1.1
-
-Item {
- id: listItem
-
- signal clicked
- property alias pressed: mouseArea.pressed
-
- height: UiConstants.ListItemHeightDefault
- width: parent.width
-
- BorderImage {
- id: background
- anchors.fill: parent
- anchors.leftMargin: -UiConstants.DefaultMargin
- anchors.rightMargin: -UiConstants.DefaultMargin
- visible: pressed
- source: theme.inverted ? "image://theme/meegotouch-panel-inverted-background-pressed" : "image://theme/meegotouch-panel-background-pressed"
- }
-
- MouseArea {
- id: mouseArea;
- anchors.fill: parent
- onClicked: {
- listItem.clicked();
- }
- }
-}
diff --git a/tapasboard.pro b/tapasboard.pro
index 1cba839..4c16089 100644
--- a/tapasboard.pro
+++ b/tapasboard.pro
@@ -1,7 +1,9 @@
# Add more folders to ship with the application, here
-folder_01.source = qml/tapasboard
-folder_01.target = qml
-DEPLOYMENTFOLDERS = folder_01
+qml_folder.source = qml
+qml_folder.target = qml
+i18n_folder.source = i18n/*.qm
+i18n_folder.target = i18n
+DEPLOYMENTFOLDERS = qml_folder i18n_folder
# Additional import path used to resolve QML modules in Creator's code model
QML_IMPORT_PATH =
@@ -70,6 +72,12 @@ HEADERS += \
imagenetworkaccessmanager.h \
loginaction.h
+TRANSLATIONS += i18n/en.ts i18n/es.ts
+
+evil_hack_to_fool_lupdate {
+ SOURCES += qml/*.qml
+}
+
OTHER_FILES += \
qtc_packaging/debian_harmattan/rules \
qtc_packaging/debian_harmattan/README \
diff --git a/topicmodel.cpp b/topicmodel.cpp
index c058a6a..6b4991d 100644
--- a/topicmodel.cpp
+++ b/topicmodel.cpp
@@ -8,7 +8,7 @@
#include "topicmodel.h"
TopicModel::TopicModel(QObject *parent) :
- QAbstractListModel(parent), _board(0), _topicId(-1), _eof(false)
+ QAbstractListModel(parent), _board(0), _topicId(-1), _eof(false), _firstUnread(-1)
{
QHash<int, QByteArray> roles = roleNames();
roles[TitleRole] = QByteArray("title");
@@ -32,6 +32,7 @@ void TopicModel::setBoard(Board *board)
{
if (_board != board) {
disconnect(this, SLOT(handleTopicPostsChanged(int,int,int)));
+ disconnect(this, SLOT(handleTopicPostsUnread(int,int)));
clearModel();
_board = board;
@@ -39,6 +40,8 @@ void TopicModel::setBoard(Board *board)
if (_board) {
connect(_board, SIGNAL(topicPostsChanged(int,int,int)),
SLOT(handleTopicPostsChanged(int,int,int)));
+ connect(_board, SIGNAL(topicPostsUnread(int,int)),
+ SLOT(handleTopicPostsUnread(int,int)));
if (_topicId >= 0) {
update();
reload();
@@ -68,6 +71,11 @@ void TopicModel::setTopicId(const int id)
}
}
+int TopicModel::firstUnreadPost() const
+{
+ return _firstUnread;
+}
+
int TopicModel::rowCount(const QModelIndex &parent) const
{
return parent.isValid() ? 0 : _data.size();
@@ -84,6 +92,12 @@ QVariant TopicModel::data(const QModelIndex &index, int role) const
return QVariant();
}
+ if (_data[row].post_id < 0) {
+ // This post is a unfetched stub
+ this->fetchPost(row);
+ return QVariant(); // Will update the model once the post arrives
+ }
+
switch (role) { // Mind the lack of break statements
case TitleRole:
return _data[row].title;
@@ -120,7 +134,7 @@ void TopicModel::fetchMore(const QModelIndex &parent)
const int start = _data.size();
QList<Post> posts = loadPosts(start, start + TOPIC_PAGE_SIZE - 1);
- const int new_end = start + _data.size() - 1;
+ const int new_end = start + posts.size() - 1;
if (posts.empty()) {
// We could not load anything more from DB!
@@ -133,11 +147,12 @@ void TopicModel::fetchMore(const QModelIndex &parent)
}
if (_board->service()->isAccessible()) {
- if (!_data.empty()) {
+ if (!posts.empty()) {
QDateTime last = oldestPostUpdate(posts);
// If the posts we got from DB are too old, refresh online.
if (last.secsTo(QDateTime::currentDateTime()) > TOPIC_POSTS_TLL) {
qDebug() << "Fetching posts because of old";
+ Q_ASSERT(new_end > 0);
_board->enqueueAction(new FetchPostsAction(_topicId,
start,
new_end,
@@ -215,6 +230,7 @@ QList<TopicModel::Post> TopicModel::loadPosts(int start, int end)
query.bindValue(":start", start);
query.bindValue(":limit", rows);
if (query.exec()) {
+ int loaded = 0;
posts.reserve(rows);
while (query.next()) {
Post post;
@@ -226,6 +242,7 @@ QList<TopicModel::Post> TopicModel::loadPosts(int start, int end)
post.time = parseDbDateTime(query.value(5));
post.last_update_time = parseDbDateTime(query.value(6));
posts.append(post);
+ loaded++;
}
} else {
qWarning() << "Could not load posts:" << query.lastError().text();
@@ -233,6 +250,36 @@ QList<TopicModel::Post> TopicModel::loadPosts(int start, int end)
return posts;
}
+void TopicModel::fetchPost(int position) const
+{
+ if (_board->service()->isAccessible()) {
+ // There are lest posts on the DB than we wanted
+ qDebug() << "Fetching posts because of unfetched";
+ // Always fetch one page at least.
+ int fetch_start = position % FORUM_PAGE_SIZE;
+ int fetch_end = fetch_start + FORUM_PAGE_SIZE;
+ _board->enqueueAction(new FetchPostsAction(_topicId,
+ fetch_start, fetch_end,
+ _board));
+ }
+}
+
+void TopicModel::enlargeModel(int end)
+{
+ int start = _data.size();
+ if (end > start) {
+ qDebug() << "Call insert rows (enlarge):" << start << end;
+ beginInsertRows(QModelIndex(), start, end);
+ Post post;
+ post.post_id = -1;
+ for (int i = start; i <= end; i++) {
+ Q_ASSERT(_data.size() == i);
+ _data.append(post);
+ }
+ endInsertRows();
+ }
+}
+
void TopicModel::clearModel()
{
beginResetModel();
@@ -245,7 +292,6 @@ void TopicModel::handleTopicPostsChanged(int topicId, int start, int end)
{
if (topicId == _topicId) {
// Yep, our posts list changed.
- qDebug() << "My posts changed" << start << end;
if (end > _data.size()) {
// If for any reason we have more posts now, it means we might
// no longer be EOF...
@@ -264,7 +310,7 @@ void TopicModel::handleTopicPostsChanged(int topicId, int start, int end)
}
if (end >= _data.size()) {
- qDebug() << "Call insert rows" << _data.size() << end;
+ qDebug() << "Call insert rows (changed):" << _data.size() << end;
beginInsertRows(QModelIndex(), _data.size(), end);
_data.reserve(end + 1);
for (int i = start; i < _data.size(); i++) {
@@ -286,6 +332,17 @@ void TopicModel::handleTopicPostsChanged(int topicId, int start, int end)
}
}
+void TopicModel::handleTopicPostsUnread(int topicId, int position)
+{
+ if (topicId == _topicId) {
+ if (position != _firstUnread) {
+ enlargeModel(position);
+ _firstUnread = position;
+ emit firstUnreadPostChanged();
+ }
+ }
+}
+
void TopicModel::update()
{
if (!_board || _topicId < 0) return;
diff --git a/topicmodel.h b/topicmodel.h
index 0151242..67e48c4 100644
--- a/topicmodel.h
+++ b/topicmodel.h
@@ -12,6 +12,7 @@ class TopicModel : public QAbstractListModel
Q_OBJECT
Q_PROPERTY(Board * board READ board WRITE setBoard NOTIFY boardChanged)
Q_PROPERTY(int topicId READ topicId WRITE setTopicId NOTIFY topicIdChanged)
+ Q_PROPERTY(int firstUnreadPost READ firstUnreadPost NOTIFY firstUnreadPostChanged)
public:
TopicModel(QObject *parent = 0);
@@ -35,6 +36,8 @@ public:
int topicId() const;
void setTopicId(const int id);
+ int firstUnreadPost() const;
+
int rowCount(const QModelIndex &parent = QModelIndex()) const;
QVariant data(const QModelIndex &index, int role) const;
@@ -47,9 +50,11 @@ public slots:
signals:
void boardChanged();
void topicIdChanged();
+ void firstUnreadPostChanged();
protected:
struct Post {
+ /** Set 'post_id' to -1 for "not yet fetched" */
int post_id;
QString title;
QString content;
@@ -64,10 +69,13 @@ private:
static QDateTime oldestPostUpdate(const QList<Post>& posts);
QDateTime lastTopPostUpdate();
QList<Post> loadPosts(int start, int end);
+ void fetchPost(int position) const; // const because data() calls this
+ void enlargeModel(int end);
void clearModel();
private slots:
- void handleTopicPostsChanged(int forumId, int start, int end);
+ void handleTopicPostsChanged(int topicId, int start, int end);
+ void handleTopicPostsUnread(int topicId, int position);
void update();
void reload();
@@ -76,6 +84,7 @@ private:
int _topicId;
QList<Post> _data;
bool _eof;
+ int _firstUnread;
};
#endif // TOPICMODEL_H