diff options
| author | Javier S. Pedro <maemo@javispedro.com> | 2013-04-04 15:49:31 +0200 | 
|---|---|---|
| committer | Javier S. Pedro <maemo@javispedro.com> | 2013-04-04 15:49:31 +0200 | 
| commit | d8fcff1a2d6eb61c97c44790dbdb920ba9f52980 (patch) | |
| tree | 49df7f1e07e34061301ad5944a1807feba24b526 | |
| parent | 11b4152301b408c7a4f02a8b202fed9f5e1ee1e7 (diff) | |
| download | tapasboard-d8fcff1a2d6eb61c97c44790dbdb920ba9f52980.tar.gz tapasboard-d8fcff1a2d6eb61c97c44790dbdb920ba9f52980.zip | |
add showing unread posts
| -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 | 
