diff options
-rw-r--r-- | board.cpp | 42 | ||||
-rw-r--r-- | board.h | 16 | ||||
-rw-r--r-- | boardmodel.cpp | 5 | ||||
-rw-r--r-- | boardmodel.h | 3 | ||||
-rw-r--r-- | fetchpostsaction.cpp | 30 | ||||
-rw-r--r-- | fetchpostsaction.h | 1 | ||||
-rw-r--r-- | forummodel.cpp | 15 | ||||
-rw-r--r-- | forummodel.h | 4 | ||||
-rw-r--r-- | main.cpp | 30 | ||||
-rw-r--r-- | qml/BoardPage.qml (renamed from qml/tapasboard/BoardPage.qml) | 2 | ||||
-rw-r--r-- | qml/EmptyListDelegate.qml | 35 | ||||
-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.qml | 29 | ||||
-rw-r--r-- | tapasboard.pro | 14 | ||||
-rw-r--r-- | topicmodel.cpp | 67 | ||||
-rw-r--r-- | topicmodel.h | 11 |
20 files changed, 250 insertions, 56 deletions
@@ -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()) { @@ -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; }; @@ -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 |