From 9d49ca0e4c83463a0bc27bcda9f76a73c6643f2a Mon Sep 17 00:00:00 2001 From: csoler Date: Sat, 17 Nov 2018 22:36:07 +0100 Subject: [PATCH] initial attempt at creating an ItemModel for forums --- .../src/gui/gxsforums/GxsForumModel.cpp | 409 ++++++++++++++++++ .../src/gui/gxsforums/GxsForumModel.h | 53 +++ retroshare-gui/src/retroshare-gui.pro | 2 + 3 files changed, 464 insertions(+) create mode 100644 retroshare-gui/src/gui/gxsforums/GxsForumModel.cpp create mode 100644 retroshare-gui/src/gui/gxsforums/GxsForumModel.h diff --git a/retroshare-gui/src/gui/gxsforums/GxsForumModel.cpp b/retroshare-gui/src/gui/gxsforums/GxsForumModel.cpp new file mode 100644 index 000000000..b2e6241b9 --- /dev/null +++ b/retroshare-gui/src/gui/gxsforums/GxsForumModel.cpp @@ -0,0 +1,409 @@ +#include +#include +#include + +#include "GxsForumModel.h" + +#define DEBUG_FORUMMODEL + +#define COLUMN_TITLE 0 +#define COLUMN_READ_STATUS 1 +#define COLUMN_DATE 2 +#define COLUMN_AUTHOR 3 +#define COLUMN_COUNT 4 + +Q_DECLARE_METATYPE(RsMsgMetaData); + +std::ostream& operator<<(std::ostream& o, const QModelIndex& i) +{ + return o << i.row() << "," << i.column() << "," << i.internalPointer() ; +} + +RsGxsForumModel::RsGxsForumModel(QObject *parent) + : QAbstractItemModel(parent) +{ + mPosts.resize(1); // adds a sentinel item +} + +int RsGxsForumModel::rowCount(const QModelIndex& parent) const +{ + void *ref = (parent.isValid())?parent.internalPointer():NULL ; + + if(mPosts.empty()) // security. Should never happen. + return 0; + + uint32_t entry = 0 ; + int source_id ; + + if(!convertRefPointerToTabEntry(ref,entry) || entry >= mPosts.size()) + { +#ifdef DEBUG_FORUMMODEL + std::cerr << "rowCount-2(" << parent << ") : " << 0 << std::endl; +#endif + return 0 ; + } + +#ifdef DEBUG_FORUMMODEL + std::cerr << "rowCount-3(" << parent << ") : " << mPosts[entry].children.size() << std::endl; +#endif + return mPosts[entry].children.size(); +} + +int RsGxsForumModel::columnCount(const QModelIndex &parent) const +{ + return COLUMN_COUNT ; +} + +bool RsGxsForumModel::hasChildren(const QModelIndex &parent) const +{ + void *ref = (parent.isValid())?parent.internalPointer():NULL ; + uint32_t entry = 0; + + if(!ref) + { +#ifdef DEBUG_FORUMMODEL + std::cerr << "hasChildren-1(" << parent << ") : " << true << std::endl; +#endif + return true ; + } + + if(!convertRefPointerToTabEntry(ref,entry) || entry >= mPosts.size()) + { +#ifdef DEBUG_FORUMMODEL + std::cerr << "hasChildren-2(" << parent << ") : " << false << std::endl; +#endif + return false ; + } + +#ifdef DEBUG_DOWNLOADLIST + std::cerr << "hasChildren-3(" << parent << ") : " << !mDownloads[entry].peers.empty() << std::endl; +#endif + return !mPosts[entry].children.empty(); +} + +bool RsGxsForumModel::convertTabEntryToRefPointer(uint32_t entry,void *& ref) +{ + // the pointer is formed the following way: + // + // [ 32 bits ] + // + // This means that the whole software has the following build-in limitation: + // * 4 B simultaenous posts. Should be enough ! + + ref = reinterpret_cast( (intptr_t)entry ); + + return true; +} + +bool RsGxsForumModel::convertRefPointerToTabEntry(void *ref,uint32_t& entry) +{ + intptr_t val = (intptr_t)ref; + + if(val > (intptr_t)(~(uint32_t(0)))) // make sure the pointer is an int that fits in 32bits + { + std::cerr << "(EE) trying to make a ForumModelIndex out of a number that is larger than 2^32 !" << std::endl; + return false ; + } + entry = uint32_t(val); + + return true; +} + +QModelIndex RsGxsForumModel::index(int row, int column, const QModelIndex & parent) const +{ + if(row < 0 || column < 0 || column >= COLUMN_COUNT) + return QModelIndex(); + + void *parent_ref = (parent.isValid())?parent.internalPointer():NULL ; + uint32_t entry = 0; + int source_id=0 ; + + if(!parent_ref) // top level. The entry is that of a transfer + { + void *ref = NULL ; + + if(row >= (int)mPosts.size() || !convertTabEntryToRefPointer(row,ref)) + { +#ifdef DEBUG_FORUMMODEL + std::cerr << "index-1(" << row << "," << column << " parent=" << parent << ") : " << "NULL" << std::endl; +#endif + return QModelIndex() ; + } + +#ifdef DEBUG_FORUMMODEL + std::cerr << "index-2(" << row << "," << column << " parent=" << parent << ") : " << createIndex(row,column,ref) << std::endl; +#endif + return createIndex(row,column,ref) ; + } + + if(!convertRefPointerToTabEntry(parent_ref,entry) || entry >= mPosts.size()) + { +#ifdef DEBUG_FORUMMODEL + std::cerr << "index-5(" << row << "," << column << " parent=" << parent << ") : " << "NULL"<< std::endl ; +#endif + return QModelIndex() ; + } + + void *ref = NULL ; + + if(row >= mPosts[entry].children.size() || !convertTabEntryToRefPointer(mPosts[entry].children[row],ref)) + { +#ifdef DEBUG_FORUMMODEL + std::cerr << "index-4(" << row << "," << column << " parent=" << parent << ") : " << "NULL" << std::endl; +#endif + return QModelIndex() ; + } + +#ifdef DEBUG_FORUMMODEL + std::cerr << "index-3(" << row << "," << column << " parent=" << parent << ") : " << createIndex(row,column,ref) << std::endl; +#endif + return createIndex(row,column,ref) ; +} + +QModelIndex RsGxsForumModel::parent(const QModelIndex& child) const +{ + void *child_ref = (child.isValid())?child.internalPointer():NULL ; + uint32_t entry = 0; + int source_id=0 ; + + if(!child_ref) + return QModelIndex() ; + + if(!convertRefPointerToTabEntry(child_ref,entry) || entry >= mPosts.size()) + return QModelIndex() ; + + void *parent_ref =NULL; + + if(!convertTabEntryToRefPointer(mPosts[entry].parent,parent_ref)) + return QModelIndex() ; + + return createIndex(entry,child.column(),parent_ref) ; // I'm not sure about the .column() here ! +} + +QVariant RsGxsForumModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if(role != Qt::DisplayRole) + return QVariant(); + + switch(section) + { + default: + case COLUMN_TITLE: return tr("Title"); + case COLUMN_READ_STATUS: return tr("Read Status"); + case COLUMN_DATE: return tr("Date"); + case COLUMN_AUTHOR: return tr("Author"); + } +} + +QVariant RsGxsForumModel::data(const QModelIndex &index, int role) const +{ + if(!index.isValid()) + return QVariant(); + + int coln = index.column() ; + + switch(role) + { + case Qt::SizeHintRole: return sizeHintRole(index.column()) ; + case Qt::TextAlignmentRole: + case Qt::TextColorRole: + case Qt::WhatsThisRole: + case Qt::EditRole: + case Qt::ToolTipRole: + case Qt::StatusTipRole: + return QVariant(); + } + + void *ref = (index.isValid())?index.internalPointer():NULL ; + uint32_t entry = 0; + +#ifdef DEBUG_FORUMMODEL + std::cerr << "data(" << index << ")" ; +#endif + + if(!ref) + { +#ifdef DEBUG_FORUMMODEL + std::cerr << " [empty]" << std::endl; +#endif + return QVariant() ; + } + + if(!convertRefPointerToTabEntry(ref,entry) || entry >= mPosts.size()) + { +#ifdef DEBUG_FORUMMODEL + std::cerr << "Bad pointer: " << (void*)ref << std::endl; +#endif + return QVariant() ; + } + + const RsMsgMetaData& meta(mPosts[entry].meta_versions[0]) ; + +#ifdef DEBUG_DOWNLOADLIST + std::cerr << " [ok]" << std::endl; +#endif + + switch(role) + { + case Qt::DisplayRole: return displayRole (meta,index.column()) ; + case Qt::DecorationRole: return decorationRole(meta,index.column()) ; + case Qt::UserRole: return userRole (meta,index.column()) ; + default: + return QVariant(); + } +} + +QVariant RsGxsForumModel::sizeHintRole(int col) const +{ + float factor = QFontMetricsF(QApplication::font()).height()/14.0f ; + + switch(col) + { + default: + case COLUMN_TITLE: return QVariant( factor * 170 ); + case COLUMN_READ_STATUS: return QVariant( factor * 10 ); + case COLUMN_DATE: return QVariant( factor * 75 ); + case COLUMN_AUTHOR: return QVariant( factor * 75 ); + } +} + +QVariant RsGxsForumModel::displayRole(const RsMsgMetaData& meta,int col) const +{ + switch(col) + { + case COLUMN_TITLE: return QVariant(QString::fromUtf8(meta.mMsgName.c_str())); + case COLUMN_READ_STATUS: return QVariant(meta.mMsgStatus); + case COLUMN_DATE: return QVariant(qulonglong(meta.mPublishTs)); + case COLUMN_AUTHOR: return QVariant(QString::fromStdString(meta.mAuthorId.toStdString())); + + default: + return QVariant("[ TODO ]"); + } + + + return QVariant("[ERROR]"); +} + +QVariant RsGxsForumModel::userRole(const RsMsgMetaData &meta, int col) const +{ +#ifdef TODO + switch(col) + { + case COLUMN_PROGRESS: + { + FileChunksInfo fcinfo; + if (!rsFiles->FileDownloadChunksDetails(fileInfo.hash, fcinfo)) + return QVariant(); + + FileProgressInfo pinfo; + pinfo.cmap = fcinfo.chunks; + pinfo.type = FileProgressInfo::DOWNLOAD_LINE; + pinfo.progress = (fileInfo.size == 0) ? 0 : (fileInfo.transfered * 100.0 / fileInfo.size); + pinfo.nb_chunks = pinfo.cmap._map.empty() ? 0 : fcinfo.chunks.size(); + + for (uint32_t i = 0; i < fcinfo.chunks.size(); ++i) + switch(fcinfo.chunks[i]) + { + case FileChunksInfo::CHUNK_CHECKING: pinfo.chunks_in_checking.push_back(i); + break ; + case FileChunksInfo::CHUNK_ACTIVE: pinfo.chunks_in_progress.push_back(i); + break ; + case FileChunksInfo::CHUNK_DONE: + case FileChunksInfo::CHUNK_OUTSTANDING: + break ; + } + + return QVariant::fromValue(pinfo); + } + + case COLUMN_ID: return QVariant(QString::fromStdString(fileInfo.hash.toStdString())); + + + default: + return QVariant(); + } + } +#endif + return QVariant(); + +} + +QVariant RsGxsForumModel::decorationRole(const RsMsgMetaData& meta,int col) const +{ + return QVariant(); +} + +void RsGxsForumModel::update_posts() +{ +#ifdef TODO + std::list downHashes; + rsFiles->FileDownloads(downHashes); + + size_t old_size = mDownloads.size(); + + mDownloads.resize(downHashes.size()) ; + + if(old_size < mDownloads.size()) + { + beginInsertRows(QModelIndex(), old_size, mDownloads.size()-1); + insertRows(old_size, mDownloads.size() - old_size); + endInsertRows(); + } + else if(mDownloads.size() < old_size) + { + beginRemoveRows(QModelIndex(), mDownloads.size(), old_size-1); + removeRows(mDownloads.size(), old_size - mDownloads.size()); + endRemoveRows(); + } + + uint32_t i=0; + + for(auto it(downHashes.begin());it!=downHashes.end();++it,++i) + { + FileInfo fileInfo(mDownloads[i]); // we dont update the data itself but only a copy of it.... + int old_size = fileInfo.peers.size() ; + + rsFiles->FileDetails(*it, RS_FILE_HINTS_DOWNLOAD, fileInfo); + + int new_size = fileInfo.peers.size() ; + + if(old_size < new_size) + { + beginInsertRows(index(i,0), old_size, new_size-1); + insertRows(old_size, new_size - old_size,index(i,0)); +#ifdef DEBUG_DOWNLOADLIST + std::cerr << "called insert rows ( " << old_size << ", " << new_size - old_size << ",index(" << index(i,0)<< "))" << std::endl; +#endif + endInsertRows(); + } + else if(new_size < old_size) + { + beginRemoveRows(index(i,0), new_size, old_size-1); + removeRows(new_size, old_size - new_size,index(i,0)); +#ifdef DEBUG_DOWNLOADLIST + std::cerr << "called remove rows ( " << old_size << ", " << old_size - new_size << ",index(" << index(i,0)<< "))" << std::endl; +#endif + endRemoveRows(); + } + + uint32_t old_status = mDownloads[i].downloadStatus ; + + mDownloads[i] = fileInfo ; // ... because insertRows() calls rowCount() which needs to be consistent with the *old* number of rows. + + if(fileInfo.downloadStatus == FT_STATE_DOWNLOADING || old_status != fileInfo.downloadStatus) + { + QModelIndex topLeft = createIndex(i,0), bottomRight = createIndex(i, COLUMN_COUNT-1); + emit dataChanged(topLeft, bottomRight); + } + + // This is apparently not needed. + // + // if(!mDownloads.empty()) + // { + // QModelIndex topLeft = createIndex(0,0), bottomRight = createIndex(mDownloads.size()-1, COLUMN_COUNT-1); + // emit dataChanged(topLeft, bottomRight); + // mDownloads[i] = fileInfo ; + // } + } +#endif +} diff --git a/retroshare-gui/src/gui/gxsforums/GxsForumModel.h b/retroshare-gui/src/gui/gxsforums/GxsForumModel.h new file mode 100644 index 000000000..7b8845f80 --- /dev/null +++ b/retroshare-gui/src/gui/gxsforums/GxsForumModel.h @@ -0,0 +1,53 @@ +#include "retroshare/rsgxsifacetypes.h" + +// This class holds the actual hierarchy of posts, represented by identifiers +// It is responsible for auto-updating when necessary and holds a mutex to allow the Model to +// safely access the data. + +// The model contains a post in place 0 that is the parent of all posts. + +typedef uint32_t ForumModelIndex; + +struct ForumPostEntry +{ + std::vector meta_versions; // maybe we don't need all this. Could be too large. + + std::vector children; + ForumModelIndex parent; +}; + +// This class is the item model used by Qt to display the information + +class RsGxsForumModel : public QAbstractItemModel +{ + // Q_OBJECT + +public: + explicit RsGxsForumModel(QObject *parent = NULL); + ~RsGxsForumModel(){} + + enum Roles{ SortRole = Qt::UserRole+1 }; + + int rowCount(const QModelIndex& parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + bool hasChildren(const QModelIndex &parent = QModelIndex()) const; + + QModelIndex index(int row, int column, const QModelIndex & parent = QModelIndex()) const; + QModelIndex parent(const QModelIndex& child) const; + + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + + QVariant sizeHintRole(int col) const; + QVariant displayRole(const RsMsgMetaData& meta, int col) const; + QVariant userRole(const RsMsgMetaData& meta, int col) const; + QVariant decorationRole(const RsMsgMetaData &meta, int col) const; + + void update_posts(); + +private: + static bool convertTabEntryToRefPointer(uint32_t entry,void *& ref); + static bool convertRefPointerToTabEntry(void *ref,uint32_t& entry); + + std::vector mPosts ; // store the list of posts updated from rsForums. +}; diff --git a/retroshare-gui/src/retroshare-gui.pro b/retroshare-gui/src/retroshare-gui.pro index 61baf713e..8a9ae04ce 100644 --- a/retroshare-gui/src/retroshare-gui.pro +++ b/retroshare-gui/src/retroshare-gui.pro @@ -1231,6 +1231,7 @@ gxsforums { gui/gxsforums/CreateGxsForumMsg.h \ gui/gxsforums/GxsForumThreadWidget.h \ gui/gxsforums/GxsForumsFillThread.h \ + gui/gxsforums/GxsForumModel.h \ gui/gxsforums/GxsForumUserNotify.h \ gui/feeds/GxsForumGroupItem.h \ gui/feeds/GxsForumMsgItem.h @@ -1244,6 +1245,7 @@ gxsforums { gui/gxsforums/GxsForumGroupDialog.cpp \ gui/gxsforums/CreateGxsForumMsg.cpp \ gui/gxsforums/GxsForumThreadWidget.cpp \ + gui/gxsforums/GxsForumModel.cpp \ gui/gxsforums/GxsForumsFillThread.cpp \ gui/gxsforums/GxsForumUserNotify.cpp \ gui/feeds/GxsForumGroupItem.cpp \