diff --git a/libretroshare/src/gxs/rsgxsdataaccess.cc b/libretroshare/src/gxs/rsgxsdataaccess.cc index f9049d9f0..1206696f7 100644 --- a/libretroshare/src/gxs/rsgxsdataaccess.cc +++ b/libretroshare/src/gxs/rsgxsdataaccess.cc @@ -1714,10 +1714,11 @@ void RsGxsDataAccess::filterMsgList( MsgMetaFilter::const_iterator cit = msgMetas.find(groupId); if(cit == msgMetas.end()) continue; - +#ifdef DATA_DEBUG std::cerr << __PRETTY_FUNCTION__ << " " << msgsIdSet.size() << " for group: " << groupId << " before filtering" << std::endl; +#endif for( std::set::iterator msgIdIt = msgsIdSet.begin(); msgIdIt != msgsIdSet.end(); ) @@ -1738,9 +1739,11 @@ void RsGxsDataAccess::filterMsgList( else msgIdIt = msgsIdSet.erase(msgIdIt); } +#ifdef DATA_DEBUG std::cerr << __PRETTY_FUNCTION__ << " " << msgsIdSet.size() << " for group: " << groupId << " after filtering" << std::endl; +#endif } } diff --git a/libretroshare/src/retroshare/rsgxsforums.h b/libretroshare/src/retroshare/rsgxsforums.h index e98020e3c..6084214ff 100644 --- a/libretroshare/src/retroshare/rsgxsforums.h +++ b/libretroshare/src/retroshare/rsgxsforums.h @@ -54,6 +54,8 @@ static const uint32_t RS_GXS_FORUM_MSG_FLAGS_MODERATED = 0x00000001; struct RsGxsForumGroup : RsSerializable { + virtual ~RsGxsForumGroup() {} + RsGroupMetaData mMeta; std::string mDescription; @@ -76,6 +78,8 @@ struct RsGxsForumGroup : RsSerializable struct RsGxsForumMsg : RsSerializable { + virtual ~RsGxsForumMsg() {} + RsMsgMetaData mMeta; std::string mMsg; @@ -139,10 +143,11 @@ public: const std::list& forumIds, std::vector& forumsInfo ) = 0; + /** * @brief Get content of specified forums. Blocking API * @jsonapi{development} - * @param[in] forumIds id of the channels of which the content is requested + * @param[in] forumIds id of the forum of which the content is requested * @param[out] messages storage for the forum messages * @return false if something failed, true otherwhise */ @@ -150,6 +155,29 @@ public: const std::list& forumIds, std::vector& messages ) = 0; + /** + * @brief Get message metadatas for some messages of a specific forum. Blocking API + * @jsonapi{development} + * @param[in] forumIds id of the forum of which the content is requested + * @param[out] msg_metas storage for the forum messages meta data + * @return false if something failed, true otherwhise + */ + virtual bool getForumMsgMetaData( const RsGxsGroupId& forumId, + std::vector& msg_metas) =0; + + /** + * @brief Get specific list of messages from a single forums. Blocking API + * @jsonapi{development} + * @param[in] forumId id of the forum of which the content is requested + * @param[in] msgs_to_request list of message ids to request + * @param[out] msgs storage for the forum messages + * @return false if something failed, true otherwhise + */ + virtual bool getForumsContent( + const RsGxsGroupId& forumId, + std::set& msgs_to_request, + std::vector& msgs) =0; + /** * @brief Toggle message read status. Blocking API. * @jsonapi{development} diff --git a/libretroshare/src/retroshare/rsgxsifacetypes.h b/libretroshare/src/retroshare/rsgxsifacetypes.h index 72f290c2f..c80ce3afa 100644 --- a/libretroshare/src/retroshare/rsgxsifacetypes.h +++ b/libretroshare/src/retroshare/rsgxsifacetypes.h @@ -61,6 +61,8 @@ struct RsGroupMetaData : RsSerializable mCircleType(0x0001), mAuthenFlags(0), mSubscribeFlags(0), mPop(0), mVisibleMsgCount(0), mLastPost(0), mGroupStatus(0) {} + virtual ~RsGroupMetaData() {} + void operator =(const RsGxsGrpMetaData& rGxsMeta); RsGxsGroupId mGroupId; @@ -124,9 +126,9 @@ struct RsMsgMetaData : RsSerializable { RsMsgMetaData() : mPublishTs(0), mMsgFlags(0), mMsgStatus(0), mChildTs(0) {} + virtual ~RsMsgMetaData() {} void operator =(const RsGxsMsgMetaData& rGxsMeta); - RsGxsGroupId mGroupId; RsGxsMessageId mMsgId; diff --git a/libretroshare/src/services/p3gxsforums.cc b/libretroshare/src/services/p3gxsforums.cc index c8819662f..590596b7e 100644 --- a/libretroshare/src/services/p3gxsforums.cc +++ b/libretroshare/src/services/p3gxsforums.cc @@ -297,6 +297,11 @@ bool p3GxsForums::getGroupData(const uint32_t &token, std::vector& forums ) +bool p3GxsForums::getForumsSummaries( std::list& forums ) { uint32_t token; RsTokReqOptions opts; opts.mReqType = GXS_REQUEST_TYPE_GROUP_META; if( !requestGroupInfo(token, opts) - || waitToken(token) != RsTokenService::COMPLETE ) return false; + || waitToken(token,std::chrono::milliseconds(5000)) != RsTokenService::COMPLETE ) return false; return getGroupSummary(token, forums); } @@ -452,10 +456,24 @@ bool p3GxsForums::getForumsInfo( RsTokReqOptions opts; opts.mReqType = GXS_REQUEST_TYPE_GROUP_DATA; if( !requestGroupInfo(token, opts, forumIds) - || waitToken(token) != RsTokenService::COMPLETE ) return false; + || waitToken(token,std::chrono::milliseconds(5000)) != RsTokenService::COMPLETE ) return false; return getGroupData(token, forumsInfo); } +bool p3GxsForums::getForumsContent( const RsGxsGroupId& forumId, std::set& msgs_to_request,std::vector& msgs) +{ + uint32_t token; + RsTokReqOptions opts; + opts.mReqType = GXS_REQUEST_TYPE_MSG_DATA; + + GxsMsgReq msgIds; + msgIds[forumId] = msgs_to_request; + + if( !requestMsgInfo(token, opts, msgIds) || waitToken(token,std::chrono::milliseconds(5000)) != RsTokenService::COMPLETE ) return false; + + return getMsgData(token, msgs) ; +} + bool p3GxsForums::getForumsContent( const std::list& forumIds, std::vector& messages ) @@ -464,15 +482,35 @@ bool p3GxsForums::getForumsContent( RsTokReqOptions opts; opts.mReqType = GXS_REQUEST_TYPE_MSG_DATA; if( !requestMsgInfo(token, opts, forumIds) - || waitToken(token) != RsTokenService::COMPLETE ) return false; + || waitToken(token,std::chrono::milliseconds(5000)) != RsTokenService::COMPLETE ) return false; return getMsgData(token, messages); } + +bool p3GxsForums::getForumMsgMetaData(const RsGxsGroupId& forumId, std::vector& msg_metas) +{ + uint32_t token; + RsTokReqOptions opts; + opts.mReqType = GXS_REQUEST_TYPE_MSG_META; + + GxsMsgMetaMap meta_map; + std::list forumIds; + forumIds.push_back(forumId); + + if( !requestMsgInfo(token, opts, forumIds) || waitToken(token,std::chrono::milliseconds(5000)) != RsTokenService::COMPLETE ) return false; + + bool res = getMsgMetaData(token, meta_map); + + msg_metas = meta_map[forumId]; + + return res; +} + bool p3GxsForums::markRead(const RsGxsGrpMsgIdPair& msgId, bool read) { uint32_t token; setMessageReadStatus(token, msgId, read); - if(waitToken(token) != RsTokenService::COMPLETE ) return false; + if(waitToken(token,std::chrono::milliseconds(5000)) != RsTokenService::COMPLETE ) return false; return true; } @@ -517,7 +555,7 @@ bool p3GxsForums::createMessage(RsGxsForumMsg& message) { uint32_t token; if( !createMsg(token, message) - || waitToken(token) != RsTokenService::COMPLETE ) return false; + || waitToken(token,std::chrono::milliseconds(5000)) != RsTokenService::COMPLETE ) return false; if(RsGenExchange::getPublishedMsgMeta(token, message.mMeta)) return true; diff --git a/libretroshare/src/services/p3gxsforums.h b/libretroshare/src/services/p3gxsforums.h index 5e40cb3e7..8836958a7 100644 --- a/libretroshare/src/services/p3gxsforums.h +++ b/libretroshare/src/services/p3gxsforums.h @@ -77,6 +77,12 @@ public: const std::list& forumIds, std::vector& messages ); + /// @see RsGxsForums::getForumMsgMetaData + virtual bool getForumMsgMetaData(const RsGxsGroupId& forumId, std::vector& msg_metas) ; + + /// @see RsGxsForums::getForumsContent + virtual bool getForumsContent( const RsGxsGroupId& forumId, std::set& msgs_to_request,std::vector& msgs) ; + /// @see RsGxsForums::markRead virtual bool markRead(const RsGxsGrpMsgIdPair& messageId, bool read); @@ -86,6 +92,7 @@ public: virtual bool getGroupData(const uint32_t &token, std::vector &groups); virtual bool getMsgData(const uint32_t &token, std::vector &msgs); + virtual bool getMsgMetaData(const uint32_t &token, GxsMsgMetaMap& msg_metas); virtual void setMessageReadStatus(uint32_t& token, const RsGxsGrpMsgIdPair& msgId, bool read); virtual bool createGroup(uint32_t &token, RsGxsForumGroup &group); virtual bool createMsg(uint32_t &token, RsGxsForumMsg &msg); diff --git a/retroshare-gui/src/gui/gxs/GxsIdDetails.cpp b/retroshare-gui/src/gui/gxs/GxsIdDetails.cpp index 939b81708..6168090eb 100644 --- a/retroshare-gui/src/gui/gxs/GxsIdDetails.cpp +++ b/retroshare-gui/src/gui/gxs/GxsIdDetails.cpp @@ -37,7 +37,7 @@ #define IMAGE_PGPKNOWN ":/images/contact.png" #define IMAGE_PGPUNKNOWN ":/images/tags/pgp-unknown.png" #define IMAGE_ANON ":/images/tags/anon.png" -#define IMAGE_BANNED ":/icons/yellow_biohazard64.png" +#define IMAGE_BANNED ":/icons/biohazard_red.png" #define IMAGE_DEV_AMBASSADOR ":/images/tags/dev-ambassador.png" #define IMAGE_DEV_CONTRIBUTOR ":/images/tags/vote_down.png" @@ -897,7 +897,7 @@ QIcon GxsIdDetails::getLoadingIcon(const RsGxsId &/*id*/) return QIcon(IMAGE_LOADING); } -bool GxsIdDetails::MakeIdDesc(const RsGxsId &id, bool doIcons, QString &str, QList &icons, QString& comment) +bool GxsIdDetails::MakeIdDesc(const RsGxsId &id, bool doIcons, QString &str, QList &icons, QString& comment,uint32_t icon_types) { RsIdentityDetails details; @@ -921,7 +921,7 @@ bool GxsIdDetails::MakeIdDesc(const RsGxsId &id, bool doIcons, QString &str, QLi comment += getComment(details); if (doIcons) - getIcons(details, icons); + getIcons(details, icons,icon_types); // Cyril: I disabled these three which I believe to have been put for testing purposes. // @@ -973,7 +973,7 @@ QString nickname ; if (details.mFlags & RS_IDENTITY_FLAGS_PGP_LINKED) { - comment += QString("
%1:%2 ").arg(QApplication::translate("GxsIdDetails", "Authentication"), QApplication::translate("GxsIdDetails", "Signed by")); + comment += QString("
%1: ").arg(QApplication::translate("GxsIdDetails", "Node")); if (details.mFlags & RS_IDENTITY_FLAGS_PGP_KNOWN) { @@ -985,8 +985,8 @@ QString nickname ; else comment += QApplication::translate("GxsIdDetails", "unknown Key"); } - else - comment += QString("
%1: %2").arg(QApplication::translate("GxsIdDetails", "Authentication"), QApplication::translate("GxsIdDetails", "anonymous")); + //else + // comment += QString("
%1: %2").arg(QApplication::translate("GxsIdDetails", "Node:"), QApplication::translate("GxsIdDetails", "anonymous")); if(details.mReputation.mFriendsPositiveVotes || details.mReputation.mFriendsNegativeVotes) { diff --git a/retroshare-gui/src/gui/gxs/GxsIdDetails.h b/retroshare-gui/src/gui/gxs/GxsIdDetails.h index 6a48d73c5..8c6c8edac 100644 --- a/retroshare-gui/src/gui/gxs/GxsIdDetails.h +++ b/retroshare-gui/src/gui/gxs/GxsIdDetails.h @@ -63,6 +63,7 @@ class GxsIdDetails : public QObject Q_OBJECT public: + static const int ICON_TYPE_NONE = 0x0000 ; static const int ICON_TYPE_AVATAR = 0x0001 ; static const int ICON_TYPE_PGP = 0x0002 ; static const int ICON_TYPE_RECOGN = 0x0004 ; @@ -76,7 +77,7 @@ public: static void cleanup(); /* Information */ - static bool MakeIdDesc(const RsGxsId &id, bool doIcons, QString &desc, QList &icons, QString& comment); + static bool MakeIdDesc(const RsGxsId &id, bool doIcons, QString &desc, QList &icons, QString& comment, uint32_t icon_types=ICON_TYPE_ALL); static QString getName(const RsIdentityDetails &details); static QString getComment(const RsIdentityDetails &details); diff --git a/retroshare-gui/src/gui/gxsforums/GxsForumModel.cpp b/retroshare-gui/src/gui/gxsforums/GxsForumModel.cpp new file mode 100644 index 000000000..35edee9ff --- /dev/null +++ b/retroshare-gui/src/gui/gxsforums/GxsForumModel.cpp @@ -0,0 +1,1320 @@ +/******************************************************************************* + * retroshare-gui/src/gui/gxsforums/GxsForumModel.cpp * + * * + * Copyright 2018 by Cyril Soler * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation, either version 3 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Affero General Public License for more details. * + * * + * You should have received a copy of the GNU Affero General Public License * + * along with this program. If not, see . * + * * + *******************************************************************************/ + +#include +#include +#include +#include + +#include "util/qtthreadsutils.h" +#include "util/HandleRichText.h" +#include "util/DateTime.h" +#include "gui/gxs/GxsIdDetails.h" +#include "GxsForumModel.h" +#include "retroshare/rsgxsflags.h" +#include "retroshare/rsgxsforums.h" +#include "retroshare/rsexpr.h" + +//#define DEBUG_FORUMMODEL + +Q_DECLARE_METATYPE(RsMsgMetaData); + +std::ostream& operator<<(std::ostream& o, const QModelIndex& i);// defined elsewhere + +const QString RsGxsForumModel::FilterString("filtered"); + +RsGxsForumModel::RsGxsForumModel(QObject *parent) + : QAbstractItemModel(parent) +{ + initEmptyHierarchy(mPosts); + + mUseChildTS=false; + mFilteringEnabled=false; + mTreeMode = TREE_MODE_TREE; +} + +void RsGxsForumModel::preMods() +{ + emit layoutAboutToBeChanged(); +} +void RsGxsForumModel::postMods() +{ + emit dataChanged(createIndex(0,0,(void*)NULL), createIndex(0,COLUMN_THREAD_NB_COLUMNS-1,(void*)NULL)); +} + +void RsGxsForumModel::setTreeMode(TreeMode mode) +{ + if(mode == mTreeMode) + return; + + preMods(); + mTreeMode = mode; + postMods(); +} + +void RsGxsForumModel::setSortMode(SortMode mode) +{ + preMods(); + + mSortMode = mode; + + postMods(); +} + +void RsGxsForumModel::initEmptyHierarchy(std::vector& posts) +{ + preMods(); + + posts.resize(1); // adds a sentinel item + posts[0].mTitle = "Root sentinel post" ; + posts[0].mParent = 0; + + postMods(); +} + +int RsGxsForumModel::rowCount(const QModelIndex& parent) const +{ + if(parent.column() > 0) + return 0; + + if(mPosts.empty()) // security. Should never happen. + return 0; + + if(!parent.isValid()) + return getChildrenCount(NULL); + else + return getChildrenCount(parent.internalPointer()); +} + +int RsGxsForumModel::columnCount(const QModelIndex &parent) const +{ + return COLUMN_THREAD_NB_COLUMNS ; +} + +std::vector > RsGxsForumModel::getPostVersions(const RsGxsMessageId& mid) const +{ + auto it = mPostVersions.find(mid); + + if(it != mPostVersions.end()) + return it->second; + else + return std::vector >(); +} + +bool RsGxsForumModel::getPostData(const QModelIndex& i,ForumModelPostEntry& fmpe) const +{ + if(!i.isValid()) + return true; + + void *ref = i.internalPointer(); + uint32_t entry = 0; + + if(!convertRefPointerToTabEntry(ref,entry) || entry >= mPosts.size()) + return false ; + + fmpe = mPosts[entry]; + + return true; + +} + +bool RsGxsForumModel::hasChildren(const QModelIndex &parent) const +{ + if(!parent.isValid()) + return true; + + if(mTreeMode == TREE_MODE_FLAT) + return false; + + void *ref = parent.internalPointer(); + uint32_t entry = 0; + + if(!convertRefPointerToTabEntry(ref,entry) || entry >= mPosts.size()) + { +#ifdef DEBUG_FORUMMODEL + std::cerr << "hasChildren-2(" << parent << ") : " << false << std::endl; +#endif + return false ; + } + +#ifdef DEBUG_FORUMMODEL + std::cerr << "hasChildren-3(" << parent << ") : " << !mPosts[entry].mChildren.empty() << std::endl; +#endif + return !mPosts[entry].mChildren.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 > (1<<30)) // make sure the pointer is an int that fits in 32bits and not too big which would look suspicious + { + std::cerr << "(EE) trying to make a ForumModelIndex out of a number that is larger than 2^32-1 !" << 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_THREAD_NB_COLUMNS) + return QModelIndex(); + + void *ref = getChildRef(parent.internalPointer(),row); +#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& index) const +{ + if(!index.isValid()) + return QModelIndex(); + + if(mTreeMode == TREE_MODE_FLAT) + return QModelIndex(); + + void *child_ref = index.internalPointer(); + int row=0; + + void *parent_ref = getParentRef(child_ref,row) ; + + if(parent_ref == NULL) // root + return QModelIndex() ; + + return createIndex(row,0,parent_ref); +} + +Qt::ItemFlags RsGxsForumModel::flags(const QModelIndex& index) const +{ + if (!index.isValid()) + return 0; + + return QAbstractItemModel::flags(index); +} + +void *RsGxsForumModel::getChildRef(void *ref,int row) const +{ + ForumModelIndex entry ; + + if(!convertRefPointerToTabEntry(ref,entry) || entry >= mPosts.size()) + return NULL ; + + void *new_ref; + + if(mTreeMode == TREE_MODE_FLAT) + if(entry == 0) + { + convertTabEntryToRefPointer(row+1,new_ref); + return new_ref; + } + else + return NULL ; + + if(row >= mPosts[entry].mChildren.size()) + return NULL; + + convertTabEntryToRefPointer(mPosts[entry].mChildren[row],new_ref); + + return new_ref; +} + +void *RsGxsForumModel::getParentRef(void *ref,int& row) const +{ + ForumModelIndex ref_entry; + + if(mTreeMode == TREE_MODE_FLAT) + return NULL; + + if(!convertRefPointerToTabEntry(ref,ref_entry) || ref_entry >= mPosts.size()) + return NULL ; + + ForumModelIndex parent_entry = mPosts[ref_entry].mParent; + + if(parent_entry == 0) // top level index + { + row = 0; + return NULL ; + } + else + { + void *parent_ref; + convertTabEntryToRefPointer(parent_entry,parent_ref); + row = mPosts[parent_entry].prow; + + return parent_ref; + } +} + +int RsGxsForumModel::getChildrenCount(void *ref) const +{ + uint32_t entry = 0 ; + + if(!convertRefPointerToTabEntry(ref,entry) || entry >= mPosts.size()) + return 0 ; + + if(mTreeMode == TREE_MODE_FLAT) + if(entry == 0) + return ((int)mPosts.size())-1; + else + return 0; + else + return mPosts[entry].mChildren.size(); +} + +QVariant RsGxsForumModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if(role == Qt::DisplayRole) + switch(section) + { + case COLUMN_THREAD_TITLE: return tr("Title"); + case COLUMN_THREAD_DATE: return tr("Date"); + case COLUMN_THREAD_AUTHOR: return tr("Author"); + case COLUMN_THREAD_DISTRIBUTION: return tr("Distribution"); + default: + return QVariant(); + } + + if(role == Qt::DecorationRole) + switch(section) + { + case COLUMN_THREAD_DISTRIBUTION: return QIcon(":/icons/flag_green.png"); + case COLUMN_THREAD_READ: return QIcon(":/images/message-state-read.png"); + default: + return QVariant(); + } + + return QVariant(); +} + +QVariant RsGxsForumModel::data(const QModelIndex &index, int role) const +{ +#ifdef DEBUG_FORUMMODEL + std::cerr << "calling data(" << index << ") role=" << role << std::endl; +#endif + + if(!index.isValid()) + return QVariant(); + + switch(role) + { + case Qt::SizeHintRole: return sizeHintRole(index.column()) ; + case Qt::StatusTipRole:return QVariant(); + default: break; + } + + 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 ForumModelPostEntry& fmpe(mPosts[entry]); + + if(role == Qt::FontRole) + { + QFont font ; + font.setBold( (fmpe.mPostFlags & (ForumModelPostEntry::FLAG_POST_HAS_UNREAD_CHILDREN | ForumModelPostEntry::FLAG_POST_IS_PINNED)) || IS_MSG_UNREAD(fmpe.mMsgStatus)); + return QVariant(font); + } + + if(role == UnreadChildrenRole) + return bool(fmpe.mPostFlags & ForumModelPostEntry::FLAG_POST_HAS_UNREAD_CHILDREN); + +#ifdef DEBUG_FORUMMODEL + std::cerr << " [ok]" << std::endl; +#endif + + switch(role) + { + case Qt::DisplayRole: return displayRole (fmpe,index.column()) ; + case Qt::DecorationRole: return decorationRole(fmpe,index.column()) ; + case Qt::ToolTipRole: return toolTipRole (fmpe,index.column()) ; + case Qt::UserRole: return userRole (fmpe,index.column()) ; + case Qt::TextColorRole: return textColorRole (fmpe,index.column()) ; + case Qt::BackgroundRole: return backgroundRole(fmpe,index.column()) ; + + case FilterRole: return filterRole (fmpe,index.column()) ; + case ThreadPinnedRole: return pinnedRole (fmpe,index.column()) ; + case MissingRole: return missingRole (fmpe,index.column()) ; + case StatusRole: return statusRole (fmpe,index.column()) ; + case SortRole: return sortRole (fmpe,index.column()) ; + default: + return QVariant(); + } +} + +QVariant RsGxsForumModel::textColorRole(const ForumModelPostEntry& fmpe,int column) const +{ + if( (fmpe.mPostFlags & ForumModelPostEntry::FLAG_POST_IS_MISSING)) + return QVariant(mTextColorMissing); + + if(IS_MSG_UNREAD(fmpe.mMsgStatus) || (fmpe.mPostFlags & ForumModelPostEntry::FLAG_POST_IS_PINNED)) + return QVariant(mTextColorUnread); + else + return QVariant(mTextColorRead); + + return QVariant(); +} + +QVariant RsGxsForumModel::statusRole(const ForumModelPostEntry& fmpe,int column) const +{ + if(column != COLUMN_THREAD_DATA) + return QVariant(); + + return QVariant(fmpe.mMsgStatus); +} + +QVariant RsGxsForumModel::filterRole(const ForumModelPostEntry& fmpe,int column) const +{ + if(!mFilteringEnabled || (fmpe.mPostFlags & ForumModelPostEntry::FLAG_POST_CHILDREN_PASSES_FILTER)) + return QVariant(FilterString); + + return QVariant(QString()); +} + +uint32_t RsGxsForumModel::recursUpdateFilterStatus(ForumModelIndex i,int column,const QStringList& strings) +{ + QString s ; + uint32_t count = 0; + + switch(column) + { + default: + case COLUMN_THREAD_DATE: + case COLUMN_THREAD_TITLE: s = displayRole(mPosts[i],column).toString(); + break; + case COLUMN_THREAD_AUTHOR: + { + QString comment ; + QList icons; + + GxsIdDetails::MakeIdDesc(mPosts[i].mAuthorId, false,s, icons, comment,GxsIdDetails::ICON_TYPE_NONE); + } + break; + } + + if(!strings.empty()) + { + mPosts[i].mPostFlags &= ~(ForumModelPostEntry::FLAG_POST_PASSES_FILTER | ForumModelPostEntry::FLAG_POST_CHILDREN_PASSES_FILTER); + + for(auto iter(strings.begin()); iter != strings.end(); ++iter) + if(s.contains(*iter,Qt::CaseInsensitive)) + { + mPosts[i].mPostFlags |= ForumModelPostEntry::FLAG_POST_PASSES_FILTER | ForumModelPostEntry::FLAG_POST_CHILDREN_PASSES_FILTER; + + count++; + break; + } + } + else + { + mPosts[i].mPostFlags |= ForumModelPostEntry::FLAG_POST_PASSES_FILTER |ForumModelPostEntry::FLAG_POST_CHILDREN_PASSES_FILTER; + count++; + } + + for(uint32_t j=0;j 0) + mPosts[i].mPostFlags |= ForumModelPostEntry::FLAG_POST_CHILDREN_PASSES_FILTER; + } + + return count; +} + + +void RsGxsForumModel::setFilter(int column,const QStringList& strings,uint32_t& count) +{ + preMods(); + + if(!strings.empty()) + { + count = recursUpdateFilterStatus(ForumModelIndex(0),column,strings); + mFilteringEnabled = true; + } + else + { + count=0; + mFilteringEnabled = false; + } + + postMods(); +} + +QVariant RsGxsForumModel::missingRole(const ForumModelPostEntry& fmpe,int column) const +{ + if(fmpe.mPostFlags & ForumModelPostEntry::FLAG_POST_IS_MISSING) + return QVariant(true); + else + return QVariant(false); +} + +QVariant RsGxsForumModel::toolTipRole(const ForumModelPostEntry& fmpe,int column) const +{ + if(column == COLUMN_THREAD_DISTRIBUTION) + switch(fmpe.mReputationWarningLevel) + { + case 3: return QVariant(tr("Information for this identity is currently missing.")) ; + case 2: return QVariant(tr("You have banned this ID. The message will not be\ndisplayed nor forwarded to your friends.")) ; + case 1: return QVariant(tr("You have not set an opinion for this person,\n and your friends do not vote positively: Spam regulation \nprevents the message to be forwarded to your friends.")) ; + case 0: return QVariant(tr("Message will be forwarded to your friends.")) ; + default: + return QVariant("[ERROR: missing reputation level information - contact the developers]"); + } + + if(column == COLUMN_THREAD_AUTHOR) + { + QString str,comment ; + QList icons; + + if(!GxsIdDetails::MakeIdDesc(fmpe.mAuthorId, true, str, icons, comment,GxsIdDetails::ICON_TYPE_AVATAR)) + return QVariant(); + + int S = QFontMetricsF(QApplication::font()).height(); + QImage pix( (*icons.begin()).pixmap(QSize(4*S,4*S)).toImage()); + + QString embeddedImage; + if(RsHtml::makeEmbeddedImage(pix.scaled(QSize(4*S,4*S), Qt::KeepAspectRatio, Qt::SmoothTransformation), embeddedImage, 8*S * 8*S)) + comment = "
" + embeddedImage + "" + comment + "
"; + + return comment; + } + + return QVariant(); +} + +QVariant RsGxsForumModel::pinnedRole(const ForumModelPostEntry& fmpe,int column) const +{ + if(fmpe.mPostFlags & ForumModelPostEntry::FLAG_POST_IS_PINNED) + return QVariant(true); + else + return QVariant(false); +} + +QVariant RsGxsForumModel::backgroundRole(const ForumModelPostEntry& fmpe,int column) const +{ + if(fmpe.mPostFlags & ForumModelPostEntry::FLAG_POST_IS_PINNED) + return QVariant(QBrush(QColor(255,200,180))); + + if(mFilteringEnabled && (fmpe.mPostFlags & ForumModelPostEntry::FLAG_POST_PASSES_FILTER)) + return QVariant(QBrush(QColor(255,240,210))); + + return QVariant(); +} + +QVariant RsGxsForumModel::sizeHintRole(int col) const +{ + float factor = QFontMetricsF(QApplication::font()).height()/14.0f ; + + switch(col) + { + default: + case COLUMN_THREAD_TITLE: return QVariant( QSize(factor * 170, factor*14 )); + case COLUMN_THREAD_DATE: return QVariant( QSize(factor * 75 , factor*14 )); + case COLUMN_THREAD_AUTHOR: return QVariant( QSize(factor * 75 , factor*14 )); + case COLUMN_THREAD_DISTRIBUTION: return QVariant( QSize(factor * 15 , factor*14 )); + } +} + +QVariant RsGxsForumModel::authorRole(const ForumModelPostEntry& fmpe,int column) const +{ + if(column == COLUMN_THREAD_DATA) + return QVariant(QString::fromStdString(fmpe.mAuthorId.toStdString())); + + return QVariant(); +} + +QVariant RsGxsForumModel::sortRole(const ForumModelPostEntry& fmpe,int column) const +{ + switch(column) + { + case COLUMN_THREAD_DATE: if(mSortMode == SORT_MODE_PUBLISH_TS) + return QVariant(QString::number(fmpe.mPublishTs)); // we should probably have leading zeroes here + else + return QVariant(QString::number(fmpe.mMostRecentTsInThread)); // we should probably have leading zeroes here + + case COLUMN_THREAD_READ: return QVariant((bool)IS_MSG_UNREAD(fmpe.mMsgStatus)); + case COLUMN_THREAD_DISTRIBUTION: return decorationRole(fmpe,column); + case COLUMN_THREAD_AUTHOR: + { + QString str,comment ; + QList icons; + GxsIdDetails::MakeIdDesc(fmpe.mAuthorId, false, str, icons, comment,GxsIdDetails::ICON_TYPE_NONE); + + return QVariant(str); + } + default: + return displayRole(fmpe,column); + } +} + +QVariant RsGxsForumModel::displayRole(const ForumModelPostEntry& fmpe,int col) const +{ + switch(col) + { + case COLUMN_THREAD_TITLE: if(fmpe.mPostFlags & ForumModelPostEntry::FLAG_POST_IS_REDACTED) + return QVariant(tr("[ ... Redacted message ... ]")); + else if(fmpe.mPostFlags & ForumModelPostEntry::FLAG_POST_IS_PINNED) + return QVariant(tr("[PINNED] ") + QString::fromUtf8(fmpe.mTitle.c_str())); + else + return QVariant(QString::fromUtf8(fmpe.mTitle.c_str())); + + case COLUMN_THREAD_READ:return QVariant(); + case COLUMN_THREAD_DATE:{ + if(fmpe.mPostFlags & ForumModelPostEntry::FLAG_POST_IS_MISSING) + return QVariant(QString()); + + QDateTime qtime; + qtime.setTime_t(fmpe.mPublishTs); + + return QVariant(DateTime::formatDateTime(qtime)); + } + + case COLUMN_THREAD_DISTRIBUTION: + case COLUMN_THREAD_AUTHOR: return QVariant(); + case COLUMN_THREAD_MSGID: return QVariant(); +#ifdef TODO + if (filterColumn == COLUMN_THREAD_CONTENT) { + // need content for filter + QTextDocument doc; + doc.setHtml(QString::fromUtf8(msg.mMsg.c_str())); + item->setText(COLUMN_THREAD_CONTENT, doc.toPlainText().replace(QString("\n"), QString(" "))); + } +#endif + default: + return QVariant("[ TODO ]"); + } + + + return QVariant("[ERROR]"); +} + +QVariant RsGxsForumModel::userRole(const ForumModelPostEntry& fmpe,int col) const +{ + switch(col) + { + case COLUMN_THREAD_AUTHOR: return QVariant(QString::fromStdString(fmpe.mAuthorId.toStdString())); + case COLUMN_THREAD_MSGID: return QVariant(QString::fromStdString(fmpe.mMsgId.toStdString())); + default: + return QVariant(); + } +} + +QVariant RsGxsForumModel::decorationRole(const ForumModelPostEntry& fmpe,int col) const +{ + if(col == COLUMN_THREAD_DISTRIBUTION) + return QVariant(fmpe.mReputationWarningLevel); + else if(col == COLUMN_THREAD_READ) + return QVariant(fmpe.mMsgStatus); + else + return QVariant(); +} + +const RsGxsGroupId& RsGxsForumModel::currentGroupId() const +{ + return mForumGroup.mMeta.mGroupId; +} + +void RsGxsForumModel::updateForum(const RsGxsGroupId& forum_group_id) +{ + if(forum_group_id.isNull()) + return; + + update_posts(forum_group_id); +} + +void RsGxsForumModel::clear() +{ + preMods(); + + mPosts.clear(); + mPostVersions.clear(); + + postMods(); + emit forumLoaded(); +} + +void RsGxsForumModel::setPosts(const RsGxsForumGroup& group, const std::vector& posts,const std::map > >& post_versions) +{ + preMods(); + + beginRemoveRows(QModelIndex(),0,mPosts[0].mChildren.size()-1); + endRemoveRows(); + + mForumGroup = group; + mPosts = posts; + mPostVersions = post_versions; + + // now update prow for all posts + + for(uint32_t i=0;i forumIds; + std::vector msg_metas; + std::vector groups; + + forumIds.push_back(group_id); + + if(!rsGxsForums->getForumsInfo(forumIds,groups)) + { + std::cerr << __PRETTY_FUNCTION__ << " failed to retrieve forum group info for forum " << group_id << std::endl; + return; + } + + if(!rsGxsForums->getForumMsgMetaData(group_id,msg_metas)) + { + std::cerr << __PRETTY_FUNCTION__ << " failed to retrieve forum message info for forum " << group_id << std::endl; + return; + } + + // 2 - sort the messages into a proper hierarchy + + auto post_versions = new std::map > >() ; + std::vector *vect = new std::vector(); + RsGxsForumGroup group = groups[0]; + + computeMessagesHierarchy(group,msg_metas,*vect,*post_versions); + + // 3 - update the model in the UI thread. + + RsQThreadUtils::postToObject( [group,vect,post_versions,this]() + { + /* Here it goes any code you want to be executed on the Qt Gui + * thread, for example to update the data model with new information + * after a blocking call to RetroShare API complete, note that + * Qt::QueuedConnection is important! + */ + + setPosts(group,*vect,*post_versions) ; + + delete vect; + delete post_versions; + + + }, this ); + + }); +} + +ForumModelIndex RsGxsForumModel::addEntry(std::vector& posts,const ForumModelPostEntry& entry,ForumModelIndex parent) +{ + uint32_t N = posts.size(); + posts.push_back(entry); + + posts[N].mParent = parent; + posts[parent].mChildren.push_back(N); +#ifdef DEBUG_FORUMMODEL + std::cerr << "Added new entry " << N << " children of " << parent << std::endl; +#endif + if(N == parent) + std::cerr << "(EE) trying to add a post as its own parent!" << std::endl; + return ForumModelIndex(N); +} + +void RsGxsForumModel::generateMissingItem(const RsGxsMessageId &msgId,ForumModelPostEntry& entry) +{ + entry.mPostFlags = ForumModelPostEntry::FLAG_POST_IS_MISSING ; + entry.mTitle = std::string(tr("[ ... Missing Message ... ]").toUtf8()); + entry.mMsgId = msgId; + entry.mAuthorId.clear(); + entry.mPublishTs=0; + entry.mReputationWarningLevel = 3; +} + +void RsGxsForumModel::convertMsgToPostEntry(const RsGxsForumGroup& mForumGroup,const RsMsgMetaData& msg, bool useChildTS, ForumModelPostEntry& fentry) +{ + fentry.mTitle = msg.mMsgName; + fentry.mAuthorId = msg.mAuthorId; + fentry.mMsgId = msg.mMsgId; + fentry.mPublishTs = msg.mPublishTs; + fentry.mPostFlags = 0; + fentry.mMsgStatus = msg.mMsgStatus; + + if(mForumGroup.mPinnedPosts.ids.find(msg.mMsgId) != mForumGroup.mPinnedPosts.ids.end()) + fentry.mPostFlags |= ForumModelPostEntry::FLAG_POST_IS_PINNED; + + // Early check for a message that should be hidden because its author + // is flagged with a bad reputation + + computeReputationLevel(mForumGroup.mMeta.mSignFlags,fentry); +} + +void RsGxsForumModel::computeReputationLevel(uint32_t forum_sign_flags,ForumModelPostEntry& fentry) +{ + uint32_t idflags =0; + RsReputations::ReputationLevel reputation_level = rsReputations->overallReputationLevel(fentry.mAuthorId,&idflags) ; + bool redacted = false; + + if(reputation_level == RsReputations::REPUTATION_LOCALLY_NEGATIVE) + fentry.mPostFlags |= ForumModelPostEntry::FLAG_POST_IS_REDACTED; + else + fentry.mPostFlags &= ~ForumModelPostEntry::FLAG_POST_IS_REDACTED; + + // We use a specific item model for forums in order to handle the post pinning. + + if(reputation_level == RsReputations::REPUTATION_UNKNOWN) + fentry.mReputationWarningLevel = 3 ; + else if(reputation_level == RsReputations::REPUTATION_LOCALLY_NEGATIVE) + fentry.mReputationWarningLevel = 2 ; + else if(reputation_level < rsGxsForums->minReputationForForwardingMessages(forum_sign_flags,idflags)) + fentry.mReputationWarningLevel = 1 ; + else + fentry.mReputationWarningLevel = 0 ; +} + +static bool decreasing_time_comp(const std::pair& e1,const std::pair& e2) { return e2.first < e1.first ; } + +void RsGxsForumModel::computeMessagesHierarchy(const RsGxsForumGroup& forum_group, + const std::vector& msgs_metas_array, + std::vector& posts, + std::map > >& mPostVersions + ) +{ + std::cerr << "updating messages data with " << msgs_metas_array.size() << " messages" << std::endl; + +#ifdef DEBUG_FORUMS + std::cerr << "Retrieved group data: " << std::endl; + std::cerr << " Group ID: " << forum_group.mMeta.mGroupId << std::endl; + std::cerr << " Admin lst: " << forum_group.mAdminList.ids.size() << " elements." << std::endl; + for(auto it(forum_group.mAdminList.ids.begin());it!=forum_group.mAdminList.ids.end();++it) + std::cerr << " " << *it << std::endl; + std::cerr << " Pinned Post: " << forum_group.mPinnedPosts.ids.size() << " messages." << std::endl; + for(auto it(forum_group.mPinnedPosts.ids.begin());it!=forum_group.mPinnedPosts.ids.end();++it) + std::cerr << " " << *it << std::endl; +#endif + + /* get messages */ + std::map msgs; + + for(uint32_t i=0;i > threadStack; + std::map > kids_array ; + std::set missing_parents; + + // First of all, remove all older versions of posts. This is done by first adding all posts into a hierarchy structure + // and then removing all posts which have a new versions available. The older versions are kept appart. + +#ifdef DEBUG_FORUMS + std::cerr << "GxsForumsFillThread::run() Collecting post versions" << std::endl; +#endif + mPostVersions.clear(); + std::list msg_stack ; + + for ( auto msgIt = msgs.begin(); msgIt != msgs.end();++msgIt) + { + if(!msgIt->second.mOrigMsgId.isNull() && msgIt->second.mOrigMsgId != msgIt->second.mMsgId) + { +#ifdef DEBUG_FORUMS + std::cerr << " Post " << msgIt->second.mMeta.mMsgId << " is a new version of " << msgIt->second.mMeta.mOrigMsgId << std::endl; +#endif + auto msgIt2 = msgs.find(msgIt->second.mOrigMsgId); + + // Ensuring that the post exists allows to only collect the existing data. + + if(msgIt2 == msgs.end()) + continue ; + + // Make sure that the author is the same than the original message, or is a moderator. This should always happen when messages are constructed using + // the UI but nothing can prevent a nasty user to craft a new version of a message with his own signature. + + if(msgIt2->second.mAuthorId != msgIt->second.mAuthorId) + { + if( !IS_FORUM_MSG_MODERATION(msgIt->second.mMsgFlags) ) // if authors are different the moderation flag needs to be set on the editing msg + continue ; + + if( forum_group.mAdminList.ids.find(msgIt->second.mAuthorId)==forum_group.mAdminList.ids.end()) // if author is not a moderator, continue + continue ; + } + + // always add the post a self version + + if(mPostVersions[msgIt->second.mOrigMsgId].empty()) + mPostVersions[msgIt->second.mOrigMsgId].push_back(std::make_pair(msgIt2->second.mPublishTs,msgIt2->second.mMsgId)) ; + + mPostVersions[msgIt->second.mOrigMsgId].push_back(std::make_pair(msgIt->second.mPublishTs,msgIt->second.mMsgId)) ; + } + } + + // The following code assembles all new versions of a given post into the same array, indexed by the oldest version of the post. + + for(auto it(mPostVersions.begin());it!=mPostVersions.end();++it) + { + auto& v(it->second) ; + + for(int32_t i=0;ifirst) + { + RsGxsMessageId sub_msg_id = v[i].second ; + + auto it2 = mPostVersions.find(sub_msg_id); + + if(it2 != mPostVersions.end()) + { + for(int32_t j=0;jsecond.size();++j) + if(it2->second[j].second != sub_msg_id) // dont copy it, since it is already present at slot i + v.push_back(it2->second[j]) ; + + mPostVersions.erase(it2) ; // it2 is never equal to it + } + } + } + } + + + // Now remove from msg ids, all posts except the most recent one. And make the mPostVersion be indexed by the most recent version of the post, + // which corresponds to the item in the tree widget. + +#ifdef DEBUG_FORUMS + std::cerr << "Final post versions: " << std::endl; +#endif + std::map > > mTmp; + std::map most_recent_versions ; + + for(auto it(mPostVersions.begin());it!=mPostVersions.end();++it) + { +#ifdef DEBUG_FORUMS + std::cerr << "Original post: " << it.key() << std::endl; +#endif + // Finally, sort the posts from newer to older + + std::sort(it->second.begin(),it->second.end(),decreasing_time_comp) ; + +#ifdef DEBUG_FORUMS + std::cerr << " most recent version " << (*it)[0].first << " " << (*it)[0].second << std::endl; +#endif + for(int32_t i=1;isecond.size();++i) + { + msgs.erase(it->second[i].second) ; + +#ifdef DEBUG_FORUMS + std::cerr << " older version " << (*it)[i].first << " " << (*it)[i].second << std::endl; +#endif + } + + mTmp[it->second[0].second] = it->second ; // index the versions map by the ID of the most recent post. + + // Now make sure that message parents are consistent. Indeed, an old post may have the old version of a post as parent. So we need to change that parent + // to the newest version. So we create a map of which is the most recent version of each message, so that parent messages can be searched in it. + + for(int i=1;isecond.size();++i) + most_recent_versions[it->second[i].second] = it->second[0].second ; + } + mPostVersions = mTmp ; + + // The next step is to find the top level thread messages. These are defined as the messages without + // any parent message ID. + + // this trick is needed because while we remove messages, the parents a given msg may already have been removed + // and wrongly understand as a missing parent. + + std::map kept_msgs; + + for ( auto msgIt = msgs.begin(); msgIt != msgs.end();++msgIt) + { + + if(msgIt->second.mParentId.isNull()) + { + + /* add all threads */ + const RsMsgMetaData& msg = msgIt->second; + +#ifdef DEBUG_FORUMS + std::cerr << "GxsForumsFillThread::run() Adding TopLevel Thread: mId: " << msg.mMsgId << std::endl; +#endif + + ForumModelPostEntry entry; + convertMsgToPostEntry(forum_group,msg, mUseChildTS, entry); + + ForumModelIndex entry_index = addEntry(posts,entry,0); + + //if (!mFlatView) + threadStack.push_back(std::make_pair(msg.mMsgId,entry_index)) ; + + //calculateExpand(msg, item); + //mItems.append(entry_index); + } + else + { +#ifdef DEBUG_FORUMS + std::cerr << "GxsForumsFillThread::run() Storing kid " << msgIt->first << " of message " << msgIt->second.mParentId << std::endl; +#endif + // The same missing parent may appear multiple times, so we first store them into a unique container. + + RsGxsMessageId parent_msg = msgIt->second.mParentId; + + if(msgs.find(parent_msg) == msgs.end()) + { + // also check that the message is not versionned + + std::map::const_iterator mrit = most_recent_versions.find(parent_msg) ; + + if(mrit != most_recent_versions.end()) + parent_msg = mrit->second ; + else + missing_parents.insert(parent_msg); + } + + kids_array[parent_msg].push_back(msgIt->first) ; + kept_msgs.insert(*msgIt) ; + } + } + + msgs = kept_msgs; + + // Also create a list of posts by time, when they are new versions of existing posts. Only the last one will have an item created. + + // Add a fake toplevel item for the parent IDs that we dont actually have. + + for(std::set::const_iterator it(missing_parents.begin());it!=missing_parents.end();++it) + { + // add dummy parent item + ForumModelPostEntry e ; + generateMissingItem(*it,e); + + ForumModelIndex e_index = addEntry(posts,e,0); // no parent -> parent is level 0 + //mItems.append( e_index ); + + threadStack.push_back(std::make_pair(*it,e_index)) ; + } +#ifdef DEBUG_FORUMS + std::cerr << "GxsForumsFillThread::run() Processing stack:" << std::endl; +#endif + // Now use a stack to go down the hierarchy + + while (!threadStack.empty()) + { + std::pair threadPair = threadStack.front(); + threadStack.pop_front(); + + std::map >::iterator it = kids_array.find(threadPair.first) ; + +#ifdef DEBUG_FORUMS + std::cerr << "GxsForumsFillThread::run() Node: " << threadPair.first << std::endl; +#endif + if(it == kids_array.end()) + continue ; + + + for(std::list::const_iterator it2(it->second.begin());it2!=it->second.end();++it2) + { + // We iterate through the top level thread items, and look for which message has the current item as parent. + // When found, the item is put in the thread list itself, as a potential new parent. + + auto mit = msgs.find(*it2) ; + + if(mit == msgs.end()) + { + std::cerr << "GxsForumsFillThread::run() Cannot find submessage " << *it2 << " !!!" << std::endl; + continue ; + } + + const RsMsgMetaData& msg(mit->second) ; +#ifdef DEBUG_FORUMS + std::cerr << "GxsForumsFillThread::run() adding sub_item " << msg.mMsgId << std::endl; +#endif + + + ForumModelPostEntry e ; + convertMsgToPostEntry(forum_group,msg,mUseChildTS,e) ; + ForumModelIndex e_index = addEntry(posts,e, threadPair.second); + + //calculateExpand(msg, item); + + /* add item to process list */ + threadStack.push_back(std::make_pair(msg.mMsgId, e_index)); + + msgs.erase(mit); + } + +#ifdef DEBUG_FORUMS + std::cerr << "GxsForumsFillThread::run() Erasing entry " << it->first << " from kids tab." << std::endl; +#endif + kids_array.erase(it) ; // This is not strictly needed, but it improves performance by reducing the search space. + } + +#ifdef DEBUG_FORUMS + std::cerr << "Kids array now has " << kids_array.size() << " elements" << std::endl; + for(std::map >::const_iterator it(kids_array.begin());it!=kids_array.end();++it) + { + std::cerr << "Node " << it->first << std::endl; + for(std::list::const_iterator it2(it->second.begin());it2!=it->second.end();++it2) + std::cerr << " " << *it2 << std::endl; + } + + std::cerr << "GxsForumsFillThread::run() stopped: " << (wasStopped() ? "yes" : "no") << std::endl; +#endif +} + +void RsGxsForumModel::setMsgReadStatus(const QModelIndex& i,bool read_status,bool with_children) +{ + if(!i.isValid()) + return ; + + preMods(); + + void *ref = i.internalPointer(); + uint32_t entry = 0; + + if(!convertRefPointerToTabEntry(ref,entry) || entry >= mPosts.size()) + return ; + + bool has_unread_below,has_read_below; + recursSetMsgReadStatus(entry,read_status,with_children) ; + recursUpdateReadStatusAndTimes(0,has_unread_below,has_read_below); + + postMods(); +} + +void RsGxsForumModel::recursSetMsgReadStatus(ForumModelIndex i,bool read_status,bool with_children) +{ + if(read_status) + mPosts[i].mMsgStatus = 0; + else + mPosts[i].mMsgStatus = GXS_SERV::GXS_MSG_STATUS_GUI_UNREAD; + + uint32_t token; + rsGxsForums->setMessageReadStatus(token,std::make_pair( mForumGroup.mMeta.mGroupId, mPosts[i].mMsgId ), read_status); + + if(!with_children) + return; + + for(uint32_t j=0;jsecond.size();++i) + if(it->second[i].second == mid) + postId = it->first; + + for(uint32_t i=0;i& entries,ForumModelIndex index,int depth) +{ + const ForumModelPostEntry& e(entries[index]); + + QDateTime qtime; + qtime.setTime_t(e.mPublishTs); + + std::cerr << std::string(depth*2,' ') << index << " : " << e.mAuthorId.toStdString() << " " + << QString("%1").arg((uint32_t)e.mPostFlags,8,16,QChar('0')).toStdString() << " " + << QString("%1").arg((uint32_t)e.mMsgStatus,8,16,QChar('0')).toStdString() << " " + << qtime.toString().toStdString() << " \"" << e.mTitle << "\"" << std::endl; + + for(uint32_t i=0;i= mPosts.size()) + return ; + + std::cerr << "Setting own opinion for author " << mPosts[entry].mAuthorId << " to " << op << std::endl; + RsGxsId author_id = mPosts[entry].mAuthorId; + + rsReputations->setOwnOpinion(author_id,op) ; + + // update opinions and distribution flags. No need to re-load all posts. + + for(uint32_t i=0;i * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation, either version 3 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Affero General Public License for more details. * + * * + * You should have received a copy of the GNU Affero General Public License * + * along with this program. If not, see . * + * * + *******************************************************************************/ + +#include "retroshare/rsgxsforums.h" +#include "retroshare/rsgxsifacetypes.h" +#include +#include + +// 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 ForumModelPostEntry +{ + ForumModelPostEntry() : mPublishTs(0),mMostRecentTsInThread(0),mPostFlags(0),mReputationWarningLevel(0),mMsgStatus(0),prow(0) {} + + enum { // flags for display of posts. To be used in mPostFlags + FLAG_POST_IS_PINNED = 0x0001, + FLAG_POST_IS_MISSING = 0x0002, + FLAG_POST_IS_REDACTED = 0x0004, + FLAG_POST_HAS_UNREAD_CHILDREN = 0x0008, + FLAG_POST_HAS_READ_CHILDREN = 0x0010, + FLAG_POST_PASSES_FILTER = 0x0020, + FLAG_POST_CHILDREN_PASSES_FILTER = 0x0040, + }; + + std::string mTitle ; + RsGxsId mAuthorId ; + RsGxsMessageId mMsgId; + uint32_t mPublishTs; + uint32_t mMostRecentTsInThread; + uint32_t mPostFlags; + int mReputationWarningLevel; + int mMsgStatus; + + std::vector mChildren; + ForumModelIndex mParent; + int prow ; // parent row +}; + +// 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 Columns { + COLUMN_THREAD_TITLE =0x00, + COLUMN_THREAD_READ =0x01, + COLUMN_THREAD_DATE =0x02, + COLUMN_THREAD_DISTRIBUTION =0x03, + COLUMN_THREAD_AUTHOR =0x04, + COLUMN_THREAD_CONTENT =0x05, + COLUMN_THREAD_MSGID =0x06, + COLUMN_THREAD_DATA =0x07, + COLUMN_THREAD_NB_COLUMNS =0x08, + }; + + enum Roles{ SortRole = Qt::UserRole+1, + ThreadPinnedRole = Qt::UserRole+2, + MissingRole = Qt::UserRole+3, + StatusRole = Qt::UserRole+4, + UnreadChildrenRole = Qt::UserRole+5, + FilterRole = Qt::UserRole+6, + }; + + enum TreeMode{ TREE_MODE_FLAT = 0x00, + TREE_MODE_TREE = 0x01, + }; + + enum SortMode{ SORT_MODE_PUBLISH_TS = 0x00, + SORT_MODE_CHILDREN_PUBLISH_TS = 0x01, + }; + + QModelIndex root() const{ return createIndex(0,0,(void*)NULL) ;} + QModelIndex getIndexOfMessage(const RsGxsMessageId& mid) const; + + static const QString FilterString ; + + std::vector > getPostVersions(const RsGxsMessageId& mid) const; + + // This method will asynchroneously update the data + void updateForum(const RsGxsGroupId& forumGroup); + const RsGxsGroupId& currentGroupId() const; + + void setTreeMode(TreeMode mode) ; + void setSortMode(SortMode mode) ; + + void setTextColorRead (QColor color) { mTextColorRead = color;} + void setTextColorUnread (QColor color) { mTextColorUnread = color;} + void setTextColorUnreadChildren(QColor color) { mTextColorUnreadChildren = color;} + void setTextColorNotSubscribed (QColor color) { mTextColorNotSubscribed = color;} + void setTextColorMissing (QColor color) { mTextColorMissing = color;} + + void setMsgReadStatus(const QModelIndex &i, bool read_status, bool with_children); + void setFilter(int column, const QStringList &strings, uint32_t &count) ; + void setAuthorOpinion(const QModelIndex& indx,RsReputations::Opinion op); + + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + bool hasChildren(const QModelIndex &parent = QModelIndex()) const override; + + bool getPostData(const QModelIndex& i,ForumModelPostEntry& fmpe) const ; + + QModelIndex index(int row, int column, const QModelIndex & parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex& child) const override; + Qt::ItemFlags flags(const QModelIndex& index) const override; + + void clear() ; + + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + QVariant sizeHintRole (int col) const; + QVariant displayRole (const ForumModelPostEntry& fmpe, int col) const; + QVariant decorationRole(const ForumModelPostEntry& fmpe, int col) const; + QVariant toolTipRole (const ForumModelPostEntry& fmpe, int col) const; + QVariant userRole (const ForumModelPostEntry& fmpe, int col) const; + QVariant pinnedRole (const ForumModelPostEntry& fmpe, int col) const; + QVariant missingRole (const ForumModelPostEntry& fmpe, int col) const; + QVariant statusRole (const ForumModelPostEntry& fmpe, int col) const; + QVariant authorRole (const ForumModelPostEntry& fmpe, int col) const; + QVariant sortRole (const ForumModelPostEntry& fmpe, int col) const; + QVariant fontRole (const ForumModelPostEntry& fmpe, int col) const; + QVariant filterRole (const ForumModelPostEntry& fmpe, int col) const; + QVariant textColorRole (const ForumModelPostEntry& fmpe, int col) const; + QVariant backgroundRole(const ForumModelPostEntry& fmpe, int col) const; + + /*! + * \brief debug_dump + * Dumps the hierarchy of posts in the terminal, to allow checking whether the internal representation is correct. + */ + void debug_dump(); + +signals: + void forumLoaded(); // emitted after the posts have been set. Can be used to updated the UI. + +private: + RsGxsForumGroup mForumGroup; + + bool mUseChildTS; + bool mFilteringEnabled; + TreeMode mTreeMode; + SortMode mSortMode; + + void preMods() ; + void postMods() ; + + void *getParentRef(void *ref,int& row) const; + void *getChildRef(void *ref,int row) const; + //bool hasIndex(int row,int column,const QModelIndex& parent)const; + int getChildrenCount(void *ref) const; + + static bool convertTabEntryToRefPointer(uint32_t entry,void *& ref); + static bool convertRefPointerToTabEntry(void *ref,uint32_t& entry); + static void computeReputationLevel(uint32_t forum_sign_flags, ForumModelPostEntry& entry); + + void update_posts(const RsGxsGroupId &group_id); + void setForumMessageSummary(const std::vector& messages); + void recursUpdateReadStatusAndTimes(ForumModelIndex i,bool& has_unread_below,bool& has_read_below); + uint32_t recursUpdateFilterStatus(ForumModelIndex i,int column,const QStringList& strings); + void recursSetMsgReadStatus(ForumModelIndex i,bool read_status,bool with_children); + + static void generateMissingItem(const RsGxsMessageId &msgId,ForumModelPostEntry& entry); + static ForumModelIndex addEntry(std::vector& posts,const ForumModelPostEntry& entry,ForumModelIndex parent); + static void convertMsgToPostEntry(const RsGxsForumGroup &mForumGroup, const RsMsgMetaData &msg, bool useChildTS, ForumModelPostEntry& fentry); + + void computeMessagesHierarchy(const RsGxsForumGroup& forum_group, const std::vector &msgs_array, std::vector& posts, std::map > > &mPostVersions); + void setPosts(const RsGxsForumGroup& group, const std::vector& posts,const std::map > >& post_versions); + void initEmptyHierarchy(std::vector& posts); + + std::vector mPosts ; // store the list of posts updated from rsForums. + std::map > > mPostVersions; + + QColor mTextColorRead ; + QColor mTextColorUnread ; + QColor mTextColorUnreadChildren; + QColor mTextColorNotSubscribed ; + QColor mTextColorMissing ; + + friend class const_iterator; +}; diff --git a/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp b/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp index d04d982c3..f2627ca54 100644 --- a/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp +++ b/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp @@ -24,9 +24,11 @@ #include #include +#include "util/qtthreadsutils.h" +#include "util/misc.h" #include "GxsForumThreadWidget.h" #include "ui_GxsForumThreadWidget.h" -#include "GxsForumsFillThread.h" +#include "GxsForumModel.h" #include "GxsForumsDialog.h" #include "gui/RetroShareLink.h" #include "gui/common/RSTreeWidgetItem.h" @@ -54,7 +56,7 @@ #include #include -//#define DEBUG_FORUMS +#define DEBUG_FORUMS /* Images for context menu icons */ #define IMAGE_MESSAGE ":/images/mail_new.png" @@ -64,7 +66,7 @@ #define IMAGE_DOWNLOAD ":/images/start.png" #define IMAGE_DOWNLOADALL ":/images/startall.png" #define IMAGE_COPYLINK ":/images/copyrslink.png" -#define IMAGE_BIOHAZARD ":/icons/yellow_biohazard64.png" +#define IMAGE_BIOHAZARD ":/icons/biohazard_red.png" #define IMAGE_WARNING_YELLOW ":/icons/warning_yellow_128.png" #define IMAGE_WARNING_RED ":/icons/warning_red_128.png" #define IMAGE_WARNING_UNKNOWN ":/icons/bullet_grey_128.png" @@ -78,19 +80,10 @@ #define VIEW_FLAT 2 /* Thread constants */ -#define COLUMN_THREAD_TITLE 0 -#define COLUMN_THREAD_READ 1 -#define COLUMN_THREAD_DATE 2 -#define COLUMN_THREAD_DISTRIBUTION 3 -#define COLUMN_THREAD_AUTHOR 4 -#define COLUMN_THREAD_SIGNED 5 -#define COLUMN_THREAD_CONTENT 6 -#define COLUMN_THREAD_COUNT 7 -#define COLUMN_THREAD_MSGID 8 -#define COLUMN_THREAD_NB_COLUMNS 9 -#define COLUMN_THREAD_DATA 0 // column for storing the userdata like parentid +// We need consts for that!! Defined in multiple places. +#ifdef TO_REMOVE #define ROLE_THREAD_MSGID Qt::UserRole #define ROLE_THREAD_STATUS Qt::UserRole + 1 #define ROLE_THREAD_MISSING Qt::UserRole + 2 @@ -102,6 +95,14 @@ #define ROLE_THREAD_PINNED Qt::UserRole + 7 #define ROLE_THREAD_COUNT 4 +#endif + +#ifdef DEBUG_FORUMS +static std::ostream& operator<<(std::ostream& o,const QModelIndex& q) +{ + return o << "(" << q.row() << "," << q.column() << "," << (void*)q.internalPointer() << ")" ; +} +#endif class DistributionItemDelegate: public QStyledItemDelegate { @@ -132,11 +133,11 @@ public: switch(warning_level) { + default: + case 3: case 0: icon = QIcon(IMAGE_VOID); break; case 1: icon = QIcon(IMAGE_WARNING_YELLOW); break; case 2: icon = QIcon(IMAGE_WARNING_RED); break; - default: - case 3: icon = QIcon(IMAGE_WARNING_UNKNOWN); break; } QPixmap pix = icon.pixmap(r.size()); @@ -147,60 +148,206 @@ public: } }; +class ReadStatusItemDelegate: public QStyledItemDelegate +{ +public: + ReadStatusItemDelegate() {} + + virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const + { + if(!index.isValid()) + { + std::cerr << "(EE) attempt to draw an invalid index." << std::endl; + return ; + } + + QStyleOptionViewItemV4 opt = option; + initStyleOption(&opt, index); + // disable default icon + opt.icon = QIcon(); + // draw default item + QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &opt, painter, 0); + + const QRect r = option.rect; + + QIcon icon ; + + // get pixmap + unsigned int read_status = qvariant_cast(index.data(Qt::DecorationRole)); + + bool unread = IS_MSG_UNREAD(read_status); + bool missing = index.sibling(index.row(),RsGxsForumModel::COLUMN_THREAD_DATA).data(RsGxsForumModel::MissingRole).toBool(); + + // set icon + if (missing) + icon = QIcon(); + else + { + if (unread) + icon = QIcon(":/images/message-state-unread.png"); + else + icon = QIcon(":/images/message-state-read.png"); + } + + QPixmap pix = icon.pixmap(r.size()); + + // draw pixmap at center of item + const QPoint p = QPoint((r.width() - pix.width())/2, (r.height() - pix.height())/2); + painter->drawPixmap(r.topLeft() + p, pix); + } +}; + +class AuthorItemDelegate: public QStyledItemDelegate +{ +public: + AuthorItemDelegate() {} + + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override + { + QStyleOptionViewItemV4 opt = option; + initStyleOption(&opt, index); + + // disable default icon + opt.icon = QIcon(); + const QRect r = option.rect; + + RsGxsId id(index.data(Qt::UserRole).toString().toStdString()); + QString str; + QList icons; + QString comment; + + QFontMetricsF fm(option.font); + float f = fm.height(); + + QIcon icon ; + + if(!GxsIdDetails::MakeIdDesc(id, true, str, icons, comment,GxsIdDetails::ICON_TYPE_AVATAR)) + icon = GxsIdDetails::getLoadingIcon(id); + else + icon = *icons.begin(); + + QPixmap pix = icon.pixmap(r.size()); + + return QSize(1.2*(pix.width() + fm.width(str)),std::max(1.1*pix.height(),1.4*fm.height())); + } + + virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex& index) const override + { + if(!index.isValid()) + { + std::cerr << "(EE) attempt to draw an invalid index." << std::endl; + return ; + } + + QStyleOptionViewItemV4 opt = option; + initStyleOption(&opt, index); + + // disable default icon + opt.icon = QIcon(); + // draw default item + QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &opt, painter, 0); + + const QRect r = option.rect; + + RsGxsId id(index.data(Qt::UserRole).toString().toStdString()); + QString str; + QList icons; + QString comment; + + QFontMetricsF fm(painter->font()); + float f = fm.height(); + + QIcon icon ; + + if(!GxsIdDetails::MakeIdDesc(id, true, str, icons, comment,GxsIdDetails::ICON_TYPE_AVATAR)) + icon = GxsIdDetails::getLoadingIcon(id); + else + icon = *icons.begin(); + + unsigned int warning_level = qvariant_cast(index.sibling(index.row(),RsGxsForumModel::COLUMN_THREAD_DISTRIBUTION).data(Qt::DecorationRole)); + + if(warning_level == 2) + { + str = tr("[Banned]"); + icon = QIcon(IMAGE_BIOHAZARD); + } + + if(index.data(RsGxsForumModel::MissingRole).toBool()) + painter->drawText(r.topLeft() + QPoint(f/2.0,f*1.0), tr("[None]")); + else + { + QPixmap pix = icon.pixmap(r.size()); + const QPoint p = QPoint(r.height()/2.0, (r.height() - pix.height())/2); + + // draw pixmap at center of item + painter->drawPixmap(r.topLeft() + p, pix); + painter->drawText(r.topLeft() + QPoint(r.height()+ f/2.0 + f/2.0,f*1.0), str); + } + } +}; + +class ForumPostSortFilterProxyModel: public QSortFilterProxyModel +{ +public: + ForumPostSortFilterProxyModel(const QHeaderView *header,QObject *parent = NULL): QSortFilterProxyModel(parent),m_header(header) {} + + bool lessThan(const QModelIndex& left, const QModelIndex& right) const override + { + bool left_is_not_pinned = ! left.data(RsGxsForumModel::ThreadPinnedRole).toBool(); + bool right_is_not_pinned = !right.data(RsGxsForumModel::ThreadPinnedRole).toBool(); + + if(left_is_not_pinned ^ right_is_not_pinned) + return (m_header->sortIndicatorOrder()==Qt::AscendingOrder)?right_is_not_pinned:left_is_not_pinned ; // always put pinned posts on top + + return left.data(RsGxsForumModel::SortRole) < right.data(RsGxsForumModel::SortRole) ; + } + + bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override + { + return sourceModel()->index(source_row,0,source_parent).data(RsGxsForumModel::FilterRole).toString() == RsGxsForumModel::FilterString ; + } + +private: + const QHeaderView *m_header ; +}; + + +void GxsForumThreadWidget::setTextColorRead (QColor color) { mTextColorRead = color; mThreadModel->setTextColorRead (color);} +void GxsForumThreadWidget::setTextColorUnread (QColor color) { mTextColorUnread = color; mThreadModel->setTextColorUnread (color);} +void GxsForumThreadWidget::setTextColorUnreadChildren(QColor color) { mTextColorUnreadChildren = color; mThreadModel->setTextColorUnreadChildren(color);} +void GxsForumThreadWidget::setTextColorNotSubscribed (QColor color) { mTextColorNotSubscribed = color; mThreadModel->setTextColorNotSubscribed (color);} +void GxsForumThreadWidget::setTextColorMissing (QColor color) { mTextColorMissing = color; mThreadModel->setTextColorMissing (color);} + GxsForumThreadWidget::GxsForumThreadWidget(const RsGxsGroupId &forumId, QWidget *parent) : GxsMessageFrameWidget(rsGxsForums, parent), ui(new Ui::GxsForumThreadWidget) { ui->setupUi(this); - mTokenTypeGroupData = nextTokenType(); - mTokenTypeInsertThreads = nextTokenType(); - mTokenTypeMessageData = nextTokenType(); - mTokenTypeReplyMessage = nextTokenType(); - mTokenTypeReplyForumMessage = nextTokenType(); - mTokenTypeShowAuthorInPeople = nextTokenType(); - mTokenTypeNegativeAuthor = nextTokenType(); - mTokenTypeNeutralAuthor = nextTokenType(); - mTokenTypePositiveAuthor = nextTokenType(); - mTokenTypeEditForumMessage = nextTokenType(); - setUpdateWhenInvisible(true); - /* Setup UI helper */ - mStateHelper->addWidget(mTokenTypeGroupData, ui->subscribeToolButton); - mStateHelper->addWidget(mTokenTypeGroupData, ui->newthreadButton); - - mStateHelper->addClear(mTokenTypeGroupData, ui->forumName); - - mStateHelper->addWidget(mTokenTypeInsertThreads, ui->progressBar, UISTATE_LOADING_VISIBLE); - mStateHelper->addWidget(mTokenTypeInsertThreads, ui->progressText, UISTATE_LOADING_VISIBLE); - mStateHelper->addWidget(mTokenTypeInsertThreads, ui->threadTreeWidget, UISTATE_ACTIVE_ENABLED); - mStateHelper->addLoadPlaceholder(mTokenTypeInsertThreads, ui->progressText); - mStateHelper->addWidget(mTokenTypeInsertThreads, ui->nextUnreadButton); - mStateHelper->addWidget(mTokenTypeInsertThreads, ui->previousButton); - mStateHelper->addWidget(mTokenTypeInsertThreads, ui->nextButton); - - mStateHelper->addClear(mTokenTypeInsertThreads, ui->threadTreeWidget); - - mStateHelper->addWidget(mTokenTypeMessageData, ui->newmessageButton); -// mStateHelper->addWidget(mTokenTypeMessageData, ui->postText); - mStateHelper->addWidget(mTokenTypeMessageData, ui->downloadButton); - - mStateHelper->addLoadPlaceholder(mTokenTypeMessageData, ui->postText); - //mStateHelper->addLoadPlaceholder(mTokenTypeMessageData, ui->threadTitle); - - mSubscribeFlags = 0; - mSignFlags = 0; - mInProcessSettings = false; + //mUpdating = false; mUnreadCount = 0; mNewCount = 0; mInMsgAsReadUnread = false; - mThreadCompareRole = new RSTreeWidgetItemCompareRole; - mThreadCompareRole->setRole(COLUMN_THREAD_DATE, ROLE_THREAD_SORT); + mThreadModel = new RsGxsForumModel(this); + mThreadProxyModel = new ForumPostSortFilterProxyModel(ui->threadTreeWidget->header(),this); + mThreadProxyModel->setSourceModel(mThreadModel); + mThreadProxyModel->setSortRole(RsGxsForumModel::SortRole); + ui->threadTreeWidget->setModel(mThreadProxyModel); - ui->threadTreeWidget->setItemDelegateForColumn(COLUMN_THREAD_DISTRIBUTION,new DistributionItemDelegate()) ; + mThreadProxyModel->setFilterRole(RsGxsForumModel::FilterRole); + mThreadProxyModel->setFilterRegExp(QRegExp(QString(RsGxsForumModel::FilterString))) ; + + ui->threadTreeWidget->setSortingEnabled(true); + + ui->threadTreeWidget->setItemDelegateForColumn(RsGxsForumModel::COLUMN_THREAD_DISTRIBUTION,new DistributionItemDelegate()) ; + ui->threadTreeWidget->setItemDelegateForColumn(RsGxsForumModel::COLUMN_THREAD_AUTHOR,new AuthorItemDelegate()) ; + ui->threadTreeWidget->setItemDelegateForColumn(RsGxsForumModel::COLUMN_THREAD_READ,new ReadStatusItemDelegate()) ; + + ui->threadTreeWidget->header()->setSortIndicatorShown(true); connect(ui->versions_CB, SIGNAL(currentIndexChanged(int)), this, SLOT(changedVersion())); connect(ui->threadTreeWidget, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(threadListCustomPopupMenu(QPoint))); @@ -211,14 +358,18 @@ GxsForumThreadWidget::GxsForumThreadWidget(const RsGxsGroupId &forumId, QWidget connect(ui->newmessageButton, SIGNAL(clicked()), this, SLOT(replytoforummessage())); connect(ui->newthreadButton, SIGNAL(clicked()), this, SLOT(createthread())); + connect(mThreadModel,SIGNAL(forumLoaded()),this,SLOT(postForumLoading())); + ui->newmessageButton->setText(tr("Reply")); ui->newthreadButton->setText(tr("New thread")); - connect(ui->threadTreeWidget, SIGNAL(itemSelectionChanged()), this, SLOT(changedThread())); - connect(ui->threadTreeWidget, SIGNAL(itemClicked(QTreeWidgetItem*,int)), this, SLOT(clickedThread(QTreeWidgetItem*,int))); + connect(ui->threadTreeWidget, SIGNAL(clicked(QModelIndex)), this, SLOT(clickedThread(QModelIndex))); + connect(ui->threadTreeWidget->selectionModel(), SIGNAL(currentChanged(const QModelIndex&,const QModelIndex&)), this, SLOT(changedSelection(const QModelIndex&,const QModelIndex&))); connect(ui->viewBox, SIGNAL(currentIndexChanged(int)), this, SLOT(changedViewBox())); - connect(ui->expandButton, SIGNAL(clicked()), this, SLOT(togglethreadview())); + //connect(ui->expandButton, SIGNAL(clicked()), this, SLOT(togglethreadview())); + ui->expandButton->hide(); + connect(ui->previousButton, SIGNAL(clicked()), this, SLOT(previousMessage())); connect(ui->nextButton, SIGNAL(clicked()), this, SLOT(nextMessage())); connect(ui->nextUnreadButton, SIGNAL(clicked()), this, SLOT(nextUnreadMessage())); @@ -235,42 +386,39 @@ GxsForumThreadWidget::GxsForumThreadWidget(const RsGxsGroupId &forumId, QWidget itemDelegate->setOnlyPlainText(true); ui->threadTreeWidget->setItemDelegate(itemDelegate); - /* Set header resize modes and initial section sizes */ - QHeaderView * ttheader = ui->threadTreeWidget->header () ; - QHeaderView_setSectionResizeModeColumn(ttheader, COLUMN_THREAD_TITLE, QHeaderView::Interactive); - QHeaderView_setSectionResizeModeColumn(ttheader, COLUMN_THREAD_DISTRIBUTION, QHeaderView::ResizeToContents); - - float f = QFontMetricsF(font()).height()/14.0f ; - - ttheader->resizeSection (COLUMN_THREAD_DATE, 140*f); - ttheader->resizeSection (COLUMN_THREAD_TITLE, 440*f); - ttheader->resizeSection (COLUMN_THREAD_DISTRIBUTION, 24*f); - ttheader->resizeSection (COLUMN_THREAD_AUTHOR, 150*f); - - /* Set text of column "Read" to empty - without this the column has a number as header text */ - QTreeWidgetItem *headerItem = ui->threadTreeWidget->headerItem(); - headerItem->setText(COLUMN_THREAD_READ, "") ; - headerItem->setText(COLUMN_THREAD_DISTRIBUTION, ""); - headerItem->setData(COLUMN_THREAD_READ,Qt::UserRole, tr("Read status")) ; // this is used to display drop menus. - headerItem->setData(COLUMN_THREAD_DISTRIBUTION,Qt::UserRole, tr("Distribution")); - /* add filter actions */ - ui->filterLineEdit->addFilter(QIcon(), tr("Title"), COLUMN_THREAD_TITLE, tr("Search Title")); - ui->filterLineEdit->addFilter(QIcon(), tr("Date"), COLUMN_THREAD_DATE, tr("Search Date")); - ui->filterLineEdit->addFilter(QIcon(), tr("Author"), COLUMN_THREAD_AUTHOR, tr("Search Author")); - ui->filterLineEdit->addFilter(QIcon(), tr("Content"), COLUMN_THREAD_CONTENT, tr("Search Content")); - // see processSettings - //ui->filterLineEdit->setCurrentFilter(COLUMN_THREAD_TITLE); + ui->filterLineEdit->addFilter(QIcon(), tr("Title"), RsGxsForumModel::COLUMN_THREAD_TITLE, tr("Search Title")); + ui->filterLineEdit->addFilter(QIcon(), tr("Date"), RsGxsForumModel::COLUMN_THREAD_DATE, tr("Search Date")); + ui->filterLineEdit->addFilter(QIcon(), tr("Author"), RsGxsForumModel::COLUMN_THREAD_AUTHOR, tr("Search Author")); mLastViewType = -1; // load settings processSettings(true); + float f = QFontMetricsF(font()).height()/14.0f ; + + /* Set header resize modes and initial section sizes */ + + QHeaderView * ttheader = ui->threadTreeWidget->header () ; + ttheader->resizeSection (RsGxsForumModel::COLUMN_THREAD_DATE, 140*f); + ttheader->resizeSection (RsGxsForumModel::COLUMN_THREAD_TITLE, 440*f); + ttheader->resizeSection (RsGxsForumModel::COLUMN_THREAD_DISTRIBUTION, 24*f); + ttheader->resizeSection (RsGxsForumModel::COLUMN_THREAD_AUTHOR, 150*f); + ttheader->resizeSection (RsGxsForumModel::COLUMN_THREAD_READ, 24*f); + + QHeaderView_setSectionResizeModeColumn(ttheader, RsGxsForumModel::COLUMN_THREAD_TITLE, QHeaderView::Interactive); + QHeaderView_setSectionResizeModeColumn(ttheader, RsGxsForumModel::COLUMN_THREAD_DATE, QHeaderView::Interactive); + QHeaderView_setSectionResizeModeColumn(ttheader, RsGxsForumModel::COLUMN_THREAD_AUTHOR, QHeaderView::Interactive); + QHeaderView_setSectionResizeModeColumn(ttheader, RsGxsForumModel::COLUMN_THREAD_READ, QHeaderView::Fixed); + QHeaderView_setSectionResizeModeColumn(ttheader, RsGxsForumModel::COLUMN_THREAD_DISTRIBUTION, QHeaderView::Fixed); + + ttheader->setCascadingSectionResizes(true); + /* Set header sizes for the fixed columns and resize modes, must be set after processSettings */ - ttheader->resizeSection (COLUMN_THREAD_READ, 24*f); - QHeaderView_setSectionResizeModeColumn(ttheader, COLUMN_THREAD_READ, QHeaderView::Fixed); - ttheader->hideSection (COLUMN_THREAD_CONTENT); + ttheader->hideSection (RsGxsForumModel::COLUMN_THREAD_CONTENT); + ttheader->hideSection (RsGxsForumModel::COLUMN_THREAD_MSGID); + ttheader->hideSection (RsGxsForumModel::COLUMN_THREAD_DATA); ui->progressBar->hide(); ui->progressText->hide(); @@ -279,7 +427,7 @@ GxsForumThreadWidget::GxsForumThreadWidget(const RsGxsGroupId &forumId, QWidget setGroupId(forumId); - ui->threadTreeWidget->installEventFilter(this) ; + //ui->threadTreeWidget->installEventFilter(this) ; ui->postText->clear() ; ui->by_label->setId(RsGxsId()) ; @@ -294,9 +442,9 @@ GxsForumThreadWidget::GxsForumThreadWidget(const RsGxsGroupId &forumId, QWidget ui->subscribeToolButton->setToolTip(tr( "

Subscribing to the forum will gather \ available posts from your subscribed friends, and make the \ forum visible to all other friends.

Afterwards you can unsubscribe from the context menu of the forum list at left.

")); - ui->threadTreeWidget->enableColumnCustomize(true); - - ui->threadTreeWidget->sortItems(COLUMN_THREAD_DATE, Qt::DescendingOrder); +#ifdef SUSPENDED_CODE + ui->threadTreeWidget->enableColumnCustomize(true); +#endif } void GxsForumThreadWidget::blank() @@ -312,35 +460,31 @@ void GxsForumThreadWidget::blank() ui->by_label->hide(); ui->postText->setImageBlockWidget(ui->imageBlockWidget) ; ui->postText->resetImagesStatus(Settings->getForumLoadEmbeddedImages()); +#ifdef SUSPENDED_CODE ui->threadTreeWidget->clear(); +#endif ui->forumName->setText(""); + //mThreadModel->clear(); + +#ifdef SUSPENDED_CODE mStateHelper->setWidgetEnabled(ui->newthreadButton, false); mStateHelper->setWidgetEnabled(ui->previousButton, false); mStateHelper->setWidgetEnabled(ui->nextButton, false); +#endif ui->versions_CB->hide(); } GxsForumThreadWidget::~GxsForumThreadWidget() { - if (mFillThread) { - mFillThread->stop(); - delete(mFillThread); - mFillThread = NULL; - } - // save settings processSettings(false); delete ui; - - delete(mThreadCompareRole); } void GxsForumThreadWidget::processSettings(bool load) { - mInProcessSettings = true; - QHeaderView *header = ui->threadTreeWidget->header(); Settings->beginGroup(QString("ForumThreadWidget")); @@ -354,7 +498,7 @@ void GxsForumThreadWidget::processSettings(bool load) togglethreadview_internal(); // filterColumn - ui->filterLineEdit->setCurrentFilter(Settings->value("filterColumn", COLUMN_THREAD_TITLE).toInt()); + ui->filterLineEdit->setCurrentFilter(Settings->value("filterColumn", RsGxsForumModel::COLUMN_THREAD_TITLE).toInt()); // index of viewBox ui->viewBox->setCurrentIndex(Settings->value("viewBox", VIEW_THREADED).toInt()); @@ -375,18 +519,21 @@ void GxsForumThreadWidget::processSettings(bool load) } Settings->endGroup(); - mInProcessSettings = false; +} + +void GxsForumThreadWidget::changedSelection(const QModelIndex& current,const QModelIndex&) +{ + changedThread(current); } void GxsForumThreadWidget::groupIdChanged() { - ui->forumName->setText(groupId().isNull () ? "" : tr("Loading")); + ui->forumName->setText(groupId().isNull () ? "" : tr("Loading...")); + mNewCount = 0; mUnreadCount = 0; - emit groupChanged(this); - - fillComplete(); + updateDisplay(true); } QString GxsForumThreadWidget::groupName(bool withUnreadCount) @@ -402,10 +549,6 @@ QString GxsForumThreadWidget::groupName(bool withUnreadCount) QIcon GxsForumThreadWidget::groupIcon() { - if (mStateHelper->isLoading(mTokenTypeGroupData) || mFillThread) { - return QIcon(":/images/kalarm.png"); - } - if (mNewCount) { return QIcon(":/images/message-state-new.png"); } @@ -413,12 +556,13 @@ QIcon GxsForumThreadWidget::groupIcon() return QIcon(); } +#ifdef TO_REMOVE void GxsForumThreadWidget::changeEvent(QEvent *e) { RsGxsUpdateBroadcastWidget::changeEvent(e); switch (e->type()) { case QEvent::StyleChange: - calculateIconsAndFonts(); + //calculateIconsAndFonts(); break; default: // remove compiler warnings @@ -459,60 +603,157 @@ static void removeMessages(std::map > &ms } } } +#endif + +void GxsForumThreadWidget::saveExpandedItems(QList& expanded_items) const +{ + expanded_items.clear(); + + for(int row = 0; row < mThreadProxyModel->rowCount(); ++row) + { + std::string path = mThreadProxyModel->index(row,0).data(Qt::DisplayRole).toString().toStdString(); + + recursSaveExpandedItems(mThreadProxyModel->index(row,0),expanded_items); + } +} + +void GxsForumThreadWidget::recursSaveExpandedItems(const QModelIndex& index, QList& expanded_items) const +{ + if(ui->threadTreeWidget->isExpanded(index)) + { + for(int row=0;rowrowCount(index);++row) + recursSaveExpandedItems(index.child(row,0),expanded_items) ; + + RsGxsMessageId message_id(index.sibling(index.row(),RsGxsForumModel::COLUMN_THREAD_MSGID).data(Qt::UserRole).toString().toStdString()); + expanded_items.push_back(message_id); + } +} + +void GxsForumThreadWidget::recursRestoreExpandedItems(const QModelIndex& index, const QList& expanded_items) +{ + for(auto it(expanded_items.begin());it!=expanded_items.end();++it) + ui->threadTreeWidget->setExpanded( mThreadProxyModel->mapFromSource(mThreadModel->getIndexOfMessage(*it)) ,true) ; +} void GxsForumThreadWidget::updateDisplay(bool complete) { - if (complete) { - /* Fill complete */ - requestGroupData(); - insertThreads(); - insertMessage(); +#ifdef DEBUG_FORUMS + std::cerr << "udateDisplay: groupId()=" << groupId()<< std::endl; +#endif +#ifdef TO_REMOVE + if(mUpdating) + { +#ifdef DEBUG_FORUMS + std::cerr << " Already updating. Return!"<< std::endl; +#endif + return; + } +#endif - mIgnoredMsgId.clear(); + if(groupId().isNull()) + { +#ifdef DEBUG_FORUMS + std::cerr << " group_id=0. Return!"<< std::endl; +#endif + return; + } + + if(mForumGroup.mMeta.mGroupId.isNull() && !groupId().isNull()) + { +#ifdef DEBUG_FORUMS + std::cerr << " inconsistent group data. Reloading!"<< std::endl; +#endif + complete = true; + } + + if(!complete) + { +#ifdef DEBUG_FORUMS + std::cerr << " checking changed group data and msgs"<< std::endl; +#endif + + const std::set &grpIdsMeta = getGrpIdsMeta(); + + if(grpIdsMeta.find(groupId())!=grpIdsMeta.end()) + { +#ifdef DEBUG_FORUMS + std::cerr << " grpMeta change. reloading!" << std::endl; +#endif + complete = true; + } + + const std::set &grpIds = getGrpIds(); + + if (grpIds.find(groupId())!=grpIds.end()) + { +#ifdef DEBUG_FORUMS + std::cerr << " grp data change. reloading!" << std::endl; +#endif + complete = true; + } + else + { + // retrieve the list of modified msg ids + // if current group is listed in the map, reload the whole hierarchy + + std::map > msgIds; + getAllMsgIds(msgIds); + + // if (!mIgnoredMsgId.empty()) /* Filter ignored messages */ + // removeMessages(msgIds, mIgnoredMsgId); + + if (msgIds.find(groupId()) != msgIds.end()) + { +#ifdef DEBUG_FORUMS + std::cerr << " msg data change. reloading!" << std::endl; +#endif + complete=true; + } + } + } + + if(complete) // need to update the group data, reload the messages etc. + { + saveExpandedItems(mSavedExpandedMessages); + + if(groupId() != mThreadModel->currentGroupId()) + mThreadId.clear(); + + updateGroupData(); + mThreadModel->updateForum(groupId()); return; } +} - bool updateGroup = false; - const std::set &grpIdsMeta = getGrpIdsMeta(); +QModelIndex GxsForumThreadWidget::GxsForumThreadWidget::getCurrentIndex() const +{ + QModelIndexList selectedIndexes = ui->threadTreeWidget->selectionModel()->selectedIndexes(); - if(grpIdsMeta.find(groupId())!=grpIdsMeta.end()) - updateGroup = true; + if(selectedIndexes.size() != RsGxsForumModel::COLUMN_THREAD_NB_COLUMNS) // check that a single row is selected + return QModelIndex(); - const std::set &grpIds = getGrpIds(); - if (grpIds.find(groupId())!=grpIds.end()){ - updateGroup = true; - /* Update threads */ - insertThreads(); - } else { - std::map > msgIds; - getAllMsgIds(msgIds); + return *selectedIndexes.begin(); +} +bool GxsForumThreadWidget::getCurrentPost(ForumModelPostEntry& fmpe) const +{ + QModelIndex indx = getCurrentIndex() ; - if (!mIgnoredMsgId.empty()) { - /* Filter ignored messages */ - removeMessages(msgIds, mIgnoredMsgId); - } + if(!indx.isValid()) + return false ; - if (msgIds.find(groupId()) != msgIds.end()) { - /* Update threads */ - insertThreads(); - } - } - - if (updateGroup) { - requestGroupData(); - } + return mThreadModel->getPostData(mThreadProxyModel->mapToSource(indx),fmpe); } void GxsForumThreadWidget::threadListCustomPopupMenu(QPoint /*point*/) { - if (mFillThread) { - return; - } - QMenu contextMnu(this); - QList selectedItems = ui->threadTreeWidget->selectedItems(); + ForumModelPostEntry current_post ; + bool has_current_post = getCurrentPost(current_post); +#ifdef DEBUG_FORUMS + std::cerr << "Clicked on msg " << current_post.mMsgId << std::endl; +#endif QAction *editAct = new QAction(QIcon(IMAGE_MESSAGEEDIT), tr("Edit"), &contextMnu); connect(editAct, SIGNAL(triggered()), this, SLOT(editforummessage())); @@ -528,21 +769,21 @@ void GxsForumThreadWidget::threadListCustomPopupMenu(QPoint /*point*/) QAction *flagaspositiveAct = new QAction(QIcon(IMAGE_POSITIVE_OPINION), tr("Give positive opinion"), &contextMnu); flagaspositiveAct->setToolTip(tr("This will block/hide messages from this person, and notify friend nodes.")) ; - flagaspositiveAct->setData(mTokenTypePositiveAuthor) ; + flagaspositiveAct->setData(RsReputations::OPINION_POSITIVE) ; connect(flagaspositiveAct, SIGNAL(triggered()), this, SLOT(flagperson())); QAction *flagasneutralAct = new QAction(QIcon(IMAGE_NEUTRAL_OPINION), tr("Give neutral opinion"), &contextMnu); flagasneutralAct->setToolTip(tr("Doing this, you trust your friends to decide to forward this message or not.")) ; - flagasneutralAct->setData(mTokenTypeNeutralAuthor) ; + flagasneutralAct->setData(RsReputations::OPINION_NEUTRAL) ; connect(flagasneutralAct, SIGNAL(triggered()), this, SLOT(flagperson())); QAction *flagasnegativeAct = new QAction(QIcon(IMAGE_NEGATIVE_OPINION), tr("Give negative opinion"), &contextMnu); flagasnegativeAct->setToolTip(tr("This will block/hide messages from this person, and notify friend nodes.")) ; - flagasnegativeAct->setData(mTokenTypeNegativeAuthor) ; + flagasnegativeAct->setData(RsReputations::OPINION_NEGATIVE) ; connect(flagasnegativeAct, SIGNAL(triggered()), this, SLOT(flagperson())); QAction *newthreadAct = new QAction(QIcon(IMAGE_MESSAGE), tr("Start New Thread"), &contextMnu); - newthreadAct->setEnabled (IS_GROUP_SUBSCRIBED(mSubscribeFlags)); + newthreadAct->setEnabled (IS_GROUP_SUBSCRIBED(mForumGroup.mMeta.mSubscribeFlags)); connect(newthreadAct , SIGNAL(triggered()), this, SLOT(createthread())); QAction* expandAll = new QAction(tr("Expand all"), &contextMnu); @@ -566,41 +807,16 @@ void GxsForumThreadWidget::threadListCustomPopupMenu(QPoint /*point*/) QAction *showinpeopleAct = new QAction(QIcon(":/images/info16.png"), tr("Show author in people tab"), &contextMnu); connect(showinpeopleAct, SIGNAL(triggered()), this, SLOT(showInPeopleTab())); - if (IS_GROUP_SUBSCRIBED(mSubscribeFlags)) { - QList rows; - QList rowsRead; - QList rowsUnread; - int nCount = getSelectedMsgCount(&rows, &rowsRead, &rowsUnread); + if (IS_GROUP_SUBSCRIBED(mForumGroup.mMeta.mSubscribeFlags)) + { + markMsgAsReadChildren->setEnabled(current_post.mPostFlags & ForumModelPostEntry::FLAG_POST_HAS_UNREAD_CHILDREN); + markMsgAsUnreadChildren->setEnabled(current_post.mPostFlags & ForumModelPostEntry::FLAG_POST_HAS_READ_CHILDREN); - if (rowsUnread.isEmpty()) { - markMsgAsRead->setDisabled(true); - } - if (rowsRead.isEmpty()) { - markMsgAsUnread->setDisabled(true); - } - - bool hasUnreadChildren = false; - bool hasReadChildren = false; - int rowCount = rows.count(); - for (int i = 0; i < rowCount; ++i) { - if (hasUnreadChildren || rows[i]->data(COLUMN_THREAD_DATA, ROLE_THREAD_UNREADCHILDREN).toBool()) { - hasUnreadChildren = true; - } - if (hasReadChildren || rows[i]->data(COLUMN_THREAD_DATA, ROLE_THREAD_READCHILDREN).toBool()) { - hasReadChildren = true; - } - } - markMsgAsReadChildren->setEnabled(hasUnreadChildren); - markMsgAsUnreadChildren->setEnabled(hasReadChildren); - - if (nCount == 1) { - replyAct->setEnabled (true); - replyauthorAct->setEnabled (true); - } else { - replyAct->setDisabled (true); - replyauthorAct->setDisabled (true); - } - } else { + replyAct->setEnabled (true); + replyauthorAct->setEnabled (true); + } + else + { markMsgAsRead->setDisabled(true); markMsgAsReadChildren->setDisabled(true); markMsgAsUnread->setDisabled(true); @@ -609,17 +825,14 @@ void GxsForumThreadWidget::threadListCustomPopupMenu(QPoint /*point*/) replyauthorAct->setDisabled (true); } - if(selectedItems.size() == 1) + if(has_current_post) { - QTreeWidgetItem *item = *selectedItems.begin(); - GxsIdRSTreeWidgetItem *gxsIdItem = dynamic_cast(item); - - bool is_pinned = mForumGroup.mPinnedPosts.ids.find( RsGxsMessageId(item->data(COLUMN_THREAD_MSGID,Qt::DisplayRole).toString().toStdString()) ) != mForumGroup.mPinnedPosts.ids.end(); + bool is_pinned = mForumGroup.mPinnedPosts.ids.find( current_post.mMsgId ) != mForumGroup.mPinnedPosts.ids.end(); if(!is_pinned) { RsGxsId author_id; - if(gxsIdItem && gxsIdItem->getId(author_id) && rsIdentity->isOwnId(author_id)) + if(rsIdentity->isOwnId(current_post.mAuthorId)) contextMnu.addAction(editAct); else { @@ -638,7 +851,7 @@ void GxsForumThreadWidget::threadListCustomPopupMenu(QPoint /*point*/) } } - if(IS_GROUP_ADMIN(mSubscribeFlags) && (*selectedItems.begin())->parent() == NULL) + if(IS_GROUP_ADMIN(mForumGroup.mMeta.mSubscribeFlags) && (current_post.mParent == 0)) contextMnu.addAction(pinUpPostAct); } @@ -655,38 +868,31 @@ void GxsForumThreadWidget::threadListCustomPopupMenu(QPoint /*point*/) contextMnu.addAction(expandAll); contextMnu.addAction(collapseAll); - if(selectedItems.size() == 1) + if(has_current_post) { - QTreeWidgetItem *item = *selectedItems.begin(); - GxsIdRSTreeWidgetItem *gxsIdItem = dynamic_cast(item); +#ifdef DEBUG_FORUMS + std::cerr << "Author is: " << current_post.mAuthorId << std::endl; +#endif + contextMnu.addSeparator(); - RsGxsId author_id; - if(gxsIdItem && gxsIdItem->getId(author_id)) + RsReputations::Opinion op ; + + if(!rsIdentity->isOwnId(current_post.mAuthorId) && rsReputations->getOwnOpinion(current_post.mAuthorId,op)) { - std::cerr << "Author is: " << author_id << std::endl; + QMenu *submenu1 = contextMnu.addMenu(tr("Author's reputation")) ; - contextMnu.addSeparator(); + if(op != RsReputations::OPINION_POSITIVE) + submenu1->addAction(flagaspositiveAct); - RsReputations::Opinion op ; + if(op != RsReputations::OPINION_NEUTRAL) + submenu1->addAction(flagasneutralAct); - if(!rsIdentity->isOwnId(author_id) && rsReputations->getOwnOpinion(author_id,op)) - { - QMenu *submenu1 = contextMnu.addMenu(tr("Author's reputation")) ; - - if(op != RsReputations::OPINION_POSITIVE) - submenu1->addAction(flagaspositiveAct); - - if(op != RsReputations::OPINION_NEUTRAL) - submenu1->addAction(flagasneutralAct); - - if(op != RsReputations::OPINION_NEGATIVE) - submenu1->addAction(flagasnegativeAct); - } - - contextMnu.addAction(showinpeopleAct); - contextMnu.addAction(replyauthorAct); + if(op != RsReputations::OPINION_NEGATIVE) + submenu1->addAction(flagasnegativeAct); } + contextMnu.addAction(showinpeopleAct); + contextMnu.addAction(replyauthorAct); } contextMnu.exec(QCursor::pos()); @@ -711,6 +917,7 @@ void GxsForumThreadWidget::contextMenuTextBrowser(QPoint point) delete(contextMnu); } +#ifdef TODO bool GxsForumThreadWidget::eventFilter(QObject *obj, QEvent *event) { if (obj == ui->threadTreeWidget) { @@ -719,14 +926,16 @@ bool GxsForumThreadWidget::eventFilter(QObject *obj, QEvent *event) if (keyEvent && keyEvent->key() == Qt::Key_Space) { // Space pressed QTreeWidgetItem *item = ui->threadTreeWidget->currentItem(); - clickedThread (item, COLUMN_THREAD_READ); + clickedThread (item, RsGxsForumModel::COLUMN_THREAD_READ); return true; // eat event } } } // pass the event on to the parent class return RsGxsUpdateBroadcastWidget::eventFilter(obj, event); + return RsGxsUpdateBroadcastWidget::eventFilter(obj, event); } +#endif void GxsForumThreadWidget::togglethreadview() { @@ -738,201 +947,96 @@ void GxsForumThreadWidget::togglethreadview() void GxsForumThreadWidget::togglethreadview_internal() { - if (ui->expandButton->isChecked()) { +// if (ui->expandButton->isChecked()) { ui->postText->setVisible(true); ui->expandButton->setIcon(QIcon(QString(":/images/edit_remove24.png"))); ui->expandButton->setToolTip(tr("Hide")); - } else { - ui->postText->setVisible(false); - ui->expandButton->setIcon(QIcon(QString(":/images/edit_add24.png"))); - ui->expandButton->setToolTip(tr("Expand")); - } +// } else { +// ui->postText->setVisible(false); +// ui->expandButton->setIcon(QIcon(QString(":/images/edit_add24.png"))); +// ui->expandButton->setToolTip(tr("Expand")); +// } } void GxsForumThreadWidget::changedVersion() { + //if(mUpdating) + // return; + mThreadId = RsGxsMessageId(ui->versions_CB->itemData(ui->versions_CB->currentIndex()).toString().toStdString()) ; - if (mFillThread) { - return; - } ui->postText->resetImagesStatus(Settings->getForumLoadEmbeddedImages()) ; insertMessage(); } -void GxsForumThreadWidget::changedThread() +void GxsForumThreadWidget::changedThread(QModelIndex index) { - /* just grab the ids of the current item */ - QTreeWidgetItem *item = ui->threadTreeWidget->currentItem(); + //if(mUpdating) + // return; - if (!item || !item->isSelected()) { - mThreadId.clear(); - mOrigThreadId.clear(); - } else { + if(!index.isValid()) + return; - mThreadId = mOrigThreadId = RsGxsMessageId(item->data(COLUMN_THREAD_MSGID, Qt::DisplayRole).toString().toStdString()); - } + RsGxsMessageId new_id(index.sibling(index.row(),RsGxsForumModel::COLUMN_THREAD_MSGID).data(Qt::UserRole).toString().toStdString()); - if (mFillThread) { - return; - } - ui->postText->resetImagesStatus(Settings->getForumLoadEmbeddedImages()) ; + if(new_id == mThreadId) + return; + + mThreadId = mOrigThreadId = new_id; + +#ifdef DEBUG_FORUMS + std::cerr << "Switched to new thread ID " << mThreadId << std::endl; +#endif + //ui->postText->resetImagesStatus(Settings->getForumLoadEmbeddedImages()) ; insertMessage(); + + QModelIndex src_index = mThreadProxyModel->mapToSource(index); +#ifdef DEBUG_FORUMS + std::cerr << "Setting message read status to true" << std::endl; +#endif + mThreadModel->setMsgReadStatus(src_index, true,false); } -void GxsForumThreadWidget::clickedThread(QTreeWidgetItem *item, int column) +void GxsForumThreadWidget::clickedThread(QModelIndex index) { - if (item == NULL) { +#ifdef DEBUG_FORUMS + std::cerr << "Clicked on message ID " << mThreadId << ", index=" << index << std::endl; +#endif + +// if(mUpdating) +// { +//#ifdef DEBUG_FORUMS +// std::cerr << " early return because mUpdating=true" << std::endl; +//#endif +// return; +// } + + if(!index.isValid()) + { +#ifdef DEBUG_FORUMS + std::cerr << " early return because index is invalid" << std::endl; +#endif return; + } + + + if (index.column() == RsGxsForumModel::COLUMN_THREAD_READ) + { + ForumModelPostEntry fmpe; + + QModelIndex src_index = mThreadProxyModel->mapToSource(index); + + mThreadModel->getPostData(src_index,fmpe); +#ifdef DEBUG_FORUMS + std::cerr << "Setting message read status to false" << std::endl; +#endif + mThreadModel->setMsgReadStatus(src_index, IS_MSG_UNREAD(fmpe.mMsgStatus),false); } - - if (mFillThread) { - return; - } - - if (groupId().isNull() || !IS_GROUP_SUBSCRIBED(mSubscribeFlags)) { - return; - } - - if (column == COLUMN_THREAD_READ) { - QList rows; - rows.append(item); - uint32_t status = item->data(COLUMN_THREAD_DATA, ROLE_THREAD_STATUS).toUInt(); - setMsgReadStatus(rows, IS_MSG_UNREAD(status)); - } -} - -void GxsForumThreadWidget::calculateIconsAndFonts(QTreeWidgetItem *item, bool &hasReadChilddren, bool &hasUnreadChilddren) -{ - uint32_t status = item->data(COLUMN_THREAD_DATA, ROLE_THREAD_STATUS).toUInt(); - - bool isNew = IS_MSG_NEW(status); - bool unread = IS_MSG_UNREAD(status); - bool missing = item->data(COLUMN_THREAD_DATA, ROLE_THREAD_MISSING).toBool(); - RsGxsMessageId msgId(item->data(COLUMN_THREAD_MSGID,Qt::DisplayRole).toString().toStdString()); - - // set icon - if (missing) { - item->setIcon(COLUMN_THREAD_READ, QIcon()); - item->setIcon(COLUMN_THREAD_TITLE, QIcon()); - } else { - if (unread) { - item->setIcon(COLUMN_THREAD_READ, QIcon(":/images/message-state-unread.png")); - } else { - item->setIcon(COLUMN_THREAD_READ, QIcon(":/images/message-state-read.png")); - } - if (isNew) { - item->setIcon(COLUMN_THREAD_TITLE, QIcon(":/images/message-state-new.png")); - } else { - item->setIcon(COLUMN_THREAD_TITLE, QIcon()); - } - } - - int index; - int itemCount = item->childCount(); - - bool myReadChilddren = false; - bool myUnreadChilddren = false; - - for (index = 0; index < itemCount; ++index) { - calculateIconsAndFonts(item->child(index), myReadChilddren, myUnreadChilddren); - } - - bool is_pinned = mForumGroup.mPinnedPosts.ids.find(msgId) != mForumGroup.mPinnedPosts.ids.end(); - - // set font - for (int i = 0; i < COLUMN_THREAD_COUNT; ++i) { - QFont qf = item->font(i); - - if (!IS_GROUP_SUBSCRIBED(mSubscribeFlags)) { - qf.setBold(false); - item->setForeground(i, textColorNotSubscribed()); - } else if (unread || isNew) { - qf.setBold(true); - item->setForeground(i, textColorUnread()); - } else if (myUnreadChilddren) { - qf.setBold(true); - item->setForeground(i, textColorUnreadChildren()); - } else { - qf.setBold(false); - item->setForeground(i, textColorRead()); - } - if (missing) { - /* Missing message */ - item->setForeground(i, textColorMissing()); - } - if(is_pinned) - { - qf.setBold(true); - item->setForeground(i, textColorUnread()); - item->setData(i,Qt::BackgroundRole, QBrush(QColor(255,200,180))) ; - } - else - item->setData(i,Qt::BackgroundRole, QBrush()); - - item->setFont(i, qf); - } - - item->setData(COLUMN_THREAD_DATA, ROLE_THREAD_READCHILDREN, hasReadChilddren || myReadChilddren); - item->setData(COLUMN_THREAD_DATA, ROLE_THREAD_UNREADCHILDREN, hasUnreadChilddren || myUnreadChilddren); - - hasReadChilddren = hasReadChilddren || myReadChilddren || !unread; - hasUnreadChilddren = hasUnreadChilddren || myUnreadChilddren || unread; -} - -void GxsForumThreadWidget::calculateUnreadCount() -{ - unsigned int unreadCount = 0; - unsigned int newCount = 0; - - QTreeWidgetItemIterator itemIterator(ui->threadTreeWidget); - QTreeWidgetItem *item = NULL; - while ((item = *itemIterator) != NULL) { - ++itemIterator; - - uint32_t status = item->data(COLUMN_THREAD_DATA, ROLE_THREAD_STATUS).toUInt(); - if (IS_MSG_UNREAD(status)) { - ++unreadCount; - } - if (IS_MSG_NEW(status)) { - ++newCount; - } - } - - bool changed = false; - if (mUnreadCount != unreadCount) { - mUnreadCount = unreadCount; - changed = true; - } - if (mNewCount != newCount) { - mNewCount = newCount; - changed = true; - } - - if (changed) { - emit groupChanged(this); - } -} - -void GxsForumThreadWidget::calculateIconsAndFonts(QTreeWidgetItem *item /*= NULL*/) -{ - bool dummy1 = false; - bool dummy2 = false; - - if (item) { - calculateIconsAndFonts(item, dummy1, dummy2); - return; - } - - int index; - int itemCount = ui->threadTreeWidget->topLevelItemCount(); - - for (index = 0; index < itemCount; ++index) { - dummy1 = false; - dummy2 = false; - calculateIconsAndFonts(ui->threadTreeWidget->topLevelItem(index), dummy1, dummy2); - } +#ifdef DEBUG_FORUMS + else + std::cerr << " doing nothing" << std::endl; +#endif } static void cleanupItems (QList &items) @@ -946,15 +1050,6 @@ static void cleanupItems (QList &items) items.clear(); } -void GxsForumThreadWidget::insertGroupData() -{ -#ifdef DEBUG_FORUMS - std::cerr << "GxsForumThreadWidget::insertGroupData" << std::endl; -#endif - GxsIdDetails::process(mForumGroup.mMeta.mAuthorId, &loadAuthorIdCallback, this); - calculateIconsAndFonts(); -} - static QString getDurationString(uint32_t days) { switch(days) @@ -971,67 +1066,52 @@ static QString getDurationString(uint32_t days) } } -/*static*/ void GxsForumThreadWidget::loadAuthorIdCallback(GxsIdDetailsType type, const RsIdentityDetails &details, QObject *object, const QVariant &) +void GxsForumThreadWidget::updateForumDescription() { - GxsForumThreadWidget *tw = dynamic_cast(object); - if(!tw) + if (!mThreadId.isNull()) return; - QString author; - switch (type) { - case GXS_ID_DETAILS_TYPE_EMPTY: - author = GxsIdDetails::getEmptyIdText(); - break; - case GXS_ID_DETAILS_TYPE_FAILED: - author = GxsIdDetails::getFailedText(details.mId); - break; - case GXS_ID_DETAILS_TYPE_LOADING: - author = GxsIdDetails::getLoadingText(details.mId); - break; - case GXS_ID_DETAILS_TYPE_BANNED: - author = tr("[Banned]") ; - break ; - case GXS_ID_DETAILS_TYPE_DONE: - author = GxsIdDetails::getName(details); - break; - } + RsIdentityDetails details; - const RsGxsForumGroup& group = tw->mForumGroup; + rsIdentity->getIdDetails(mForumGroup.mMeta.mAuthorId,details); - tw->mSubscribeFlags = group.mMeta.mSubscribeFlags; - tw->mSignFlags = group.mMeta.mSignFlags; - tw->ui->forumName->setText(QString::fromUtf8(group.mMeta.mGroupName.c_str())); + QString author = GxsIdDetails::getName(details); + + const RsGxsForumGroup& group = mForumGroup; + + ui->forumName->setText(QString::fromUtf8(group.mMeta.mGroupName.c_str())); QString anti_spam_features1 ; - if(IS_GROUP_PGP_KNOWN_AUTHED(tw->mSignFlags)) anti_spam_features1 = tr("Anonymous/unknown posts forwarded if reputation is positive"); - else if(IS_GROUP_PGP_AUTHED(tw->mSignFlags)) anti_spam_features1 = tr("Anonymous posts forwarded if reputation is positive"); - - tw->mForumDescription = QString("%1: \t%2
").arg(tr("Forum name"), QString::fromUtf8( group.mMeta.mGroupName.c_str())); - tw->mForumDescription += QString("%1: %2
").arg(tr("Description"), - group.mDescription.empty()? - tr("[None]
") - :(QString::fromUtf8(group.mDescription.c_str())+"
")); - tw->mForumDescription += QString("%1: \t%2
").arg(tr("Subscribers")).arg(group.mMeta.mPop); - tw->mForumDescription += QString("%1: \t%2
").arg(tr("Posts (at neighbor nodes)")).arg(group.mMeta.mVisibleMsgCount); + QString forum_description; + + if(IS_GROUP_PGP_KNOWN_AUTHED(mForumGroup.mMeta.mSignFlags)) anti_spam_features1 = tr("Anonymous/unknown posts forwarded if reputation is positive"); + else if(IS_GROUP_PGP_AUTHED(mForumGroup.mMeta.mSignFlags)) anti_spam_features1 = tr("Anonymous posts forwarded if reputation is positive"); + + forum_description = QString("%1: \t%2
").arg(tr("Forum name"), QString::fromUtf8( group.mMeta.mGroupName.c_str())); + forum_description += QString("%1: %2
").arg(tr("Description"), group.mDescription.empty()? tr("[None]
") :(QString::fromUtf8(group.mDescription.c_str())+"
")); + forum_description += QString("%1: \t%2
").arg(tr("Subscribers")).arg(group.mMeta.mPop); + forum_description += QString("%1: \t%2
").arg(tr("Posts (at neighbor nodes)")).arg(group.mMeta.mVisibleMsgCount); + if(group.mMeta.mLastPost==0) - tw->mForumDescription += QString("%1: \t%2
").arg(tr("Last post")).arg(tr("Never")); + forum_description += QString("%1: \t%2
").arg(tr("Last post")).arg(tr("Never")); else - tw->mForumDescription += QString("%1: \t%2
").arg(tr("Last post")).arg(DateTime::formatLongDateTime(group.mMeta.mLastPost)); - tw->mForumDescription += QString("%1: \t%2
").arg(tr("Synchronization")).arg(getDurationString( rsGxsForums->getSyncPeriod(group.mMeta.mGroupId)/86400 )) ; - tw->mForumDescription += QString("%1: \t%2
").arg(tr("Storage")).arg(getDurationString( rsGxsForums->getStoragePeriod(group.mMeta.mGroupId)/86400)); + forum_description += QString("%1: \t%2
").arg(tr("Last post")).arg(DateTime::formatLongDateTime(group.mMeta.mLastPost)); + + forum_description += QString("%1: \t%2
").arg(tr("Synchronization")).arg(getDurationString( rsGxsForums->getSyncPeriod(group.mMeta.mGroupId)/86400 )) ; + forum_description += QString("%1: \t%2
").arg(tr("Storage")).arg(getDurationString( rsGxsForums->getStoragePeriod(group.mMeta.mGroupId)/86400)); QString distrib_string = tr("[unknown]"); switch(group.mMeta.mCircleType) { case GXS_CIRCLE_TYPE_PUBLIC: distrib_string = tr("Public") ; break ; - case GXS_CIRCLE_TYPE_EXTERNAL: + case GXS_CIRCLE_TYPE_EXTERNAL: { RsGxsCircleDetails det ; - + // !! What we need here is some sort of CircleLabel, which loads the circle and updates the label when done. - - if(rsGxsCircles->getCircleDetails(group.mMeta.mCircleId,det)) + + if(rsGxsCircles->getCircleDetails(group.mMeta.mCircleId,det)) distrib_string = tr("Restricted to members of circle \"")+QString::fromUtf8(det.mCircleName.c_str()) +"\""; else distrib_string = tr("Restricted to members of circle ")+QString::fromStdString(group.mMeta.mCircleId.toStdString()) ; @@ -1055,15 +1135,15 @@ static QString getDurationString(uint32_t days) default: std::cerr << "(EE) badly initialised group distribution ID = " << group.mMeta.mCircleType << std::endl; } - - tw->mForumDescription += QString("%1: \t%2
").arg(tr("Distribution"), distrib_string); - tw->mForumDescription += QString("%1: \t%2
").arg(tr("Contact"), author); - + + forum_description += QString("%1: \t%2
").arg(tr("Distribution"), distrib_string); + forum_description += QString("%1: \t%2
").arg(tr("Contact"), author); + if(!anti_spam_features1.isNull()) - tw->mForumDescription += QString("%1: \t%2
").arg(tr("Anti-spam")).arg(anti_spam_features1); - - tw->ui->subscribeToolButton->setSubscribed(IS_GROUP_SUBSCRIBED(tw->mSubscribeFlags)); - tw->mStateHelper->setWidgetEnabled(tw->ui->newthreadButton, (IS_GROUP_SUBSCRIBED(tw->mSubscribeFlags))); + forum_description += QString("%1: \t%2
").arg(tr("Anti-spam")).arg(anti_spam_features1); + + ui->subscribeToolButton->setSubscribed(IS_GROUP_SUBSCRIBED(mForumGroup.mMeta.mSubscribeFlags)); + mStateHelper->setWidgetEnabled(ui->newthreadButton, (IS_GROUP_SUBSCRIBED(mForumGroup.mMeta.mSubscribeFlags))); if(!group.mAdminList.ids.empty()) { @@ -1077,639 +1157,66 @@ static QString getDurationString(uint32_t days) admin_list_str += (admin_list_str.isNull()?"":", ") + QString::fromUtf8(det.mNickname.c_str()) ; } - tw->mForumDescription += QString("%1: %2").arg(tr("Moderators"), admin_list_str); + forum_description += QString("%1: %2").arg(tr("Moderators"), admin_list_str); } - if (tw->mThreadId.isNull() && !tw->mStateHelper->isLoading(tw->mTokenTypeMessageData)) - tw->ui->postText->setText(tw->mForumDescription); -} - -void GxsForumThreadWidget::fillThreadFinished() -{ -#ifdef DEBUG_FORUMS - std::cerr << "GxsForumThreadWidget::fillThreadFinished" << std::endl; -#endif - - // thread has finished - GxsForumsFillThread *thread = dynamic_cast(sender()); - if (thread) { - if (thread == mFillThread) { - // current thread has finished, hide progressbar and release thread - mFillThread = NULL; - - mStateHelper->setLoading(mTokenTypeInsertThreads, false); - emit groupChanged(this); - } - - if (thread->wasStopped()) { - // thread was stopped -#ifdef DEBUG_FORUMS - std::cerr << "GxsForumThreadWidget::fillThreadFinished Thread was stopped" << std::endl; -#endif - } else { -#ifdef DEBUG_FORUMS - std::cerr << "GxsForumThreadWidget::fillThreadFinished Add messages" << std::endl; -#endif - - mStateHelper->setActive(mTokenTypeInsertThreads, true); - ui->threadTreeWidget->setSortingEnabled(false); - - GxsIdDetails::enableProcess(false); - - /* add all messages in! */ - if (mLastViewType != thread->mViewType || mLastForumID != groupId()) { - ui->threadTreeWidget->clear(); - mLastViewType = thread->mViewType; - mLastForumID = groupId(); - ui->threadTreeWidget->insertTopLevelItems(0, thread->mItems); - mPostVersions = thread->mPostVersions; - - // clear list - thread->mItems.clear(); - } else { - mPostVersions = thread->mPostVersions; - fillThreads(thread->mItems, thread->mExpandNewMessages, thread->mItemToExpand); - - // cleanup list - cleanupItems(thread->mItems); - } - - /* Move value from ROLE_THREAD_AUTHOR to GxsIdRSTreeWidgetItem::setId */ - QTreeWidgetItemIterator itemIterator(ui->threadTreeWidget); - QTreeWidgetItem *item = NULL; - while ((item = *itemIterator) != NULL) { - ++itemIterator; - - QString gxsId = item->data(COLUMN_THREAD_DATA, ROLE_THREAD_AUTHOR).toString(); - if (gxsId.isEmpty()) { - continue; - } - - item->setData(COLUMN_THREAD_DATA, ROLE_THREAD_AUTHOR, QVariant()); - - GxsIdRSTreeWidgetItem *gxsIdItem = dynamic_cast(item); - if (gxsIdItem) { - gxsIdItem->setId(RsGxsId(gxsId.toStdString()), COLUMN_THREAD_AUTHOR, false); - } - } - - GxsIdDetails::enableProcess(true); - - ui->threadTreeWidget->setSortingEnabled(true); - - if (thread->mFocusMsgId.empty() == false) { - /* Search exisiting item */ - QTreeWidgetItemIterator itemIterator(ui->threadTreeWidget); - QTreeWidgetItem *item = NULL; - while ((item = *itemIterator) != NULL) { - ++itemIterator; - - if (item->data(COLUMN_THREAD_MSGID,Qt::DisplayRole).toString().toStdString() == thread->mFocusMsgId) { - ui->threadTreeWidget->setCurrentItem(item); - ui->threadTreeWidget->setFocus(); - break; - } - } - } - - QList::iterator itemIt; - for (itemIt = thread->mItemToExpand.begin(); itemIt != thread->mItemToExpand.end(); ++itemIt) { - if ((*itemIt)->isHidden() == false) { - (*itemIt)->setExpanded(true); - } - } - thread->mItemToExpand.clear(); - - if (ui->filterLineEdit->text().isEmpty() == false) { - filterItems(ui->filterLineEdit->text()); - } - calculateIconsAndFonts(); - calculateUnreadCount(); - emit groupChanged(this); - - if (!mNavigatePendingMsgId.isNull()) { - navigate(mNavigatePendingMsgId); - mNavigatePendingMsgId.clear(); - } - } - -#ifdef DEBUG_FORUMS - std::cerr << "GxsForumThreadWidget::fillThreadFinished Delete thread" << std::endl; -#endif - - thread->deleteLater(); - thread = NULL; - } - -#ifdef DEBUG_FORUMS - std::cerr << "GxsForumThreadWidget::fillThreadFinished done" << std::endl; -#endif -} - -void GxsForumThreadWidget::fillThreadProgress(int current, int count) -{ - // show fill progress - if (count) { - int max = ui->progressBar->maximum(); - ui->progressBar->setValue(current * max / count); - } -} - -void GxsForumThreadWidget::fillThreadStatus(QString text) -{ - ui->progressText->setText(text); -} - -//#define DEBUG_PINNED_POST_SORTING 1 - -class ForumThreadItem: public GxsIdRSTreeWidgetItem -{ -public: - ForumThreadItem(QHeaderView *header,const RSTreeWidgetItemCompareRole *compareRole, uint32_t icon_mask,QTreeWidget *parent = NULL) - : GxsIdRSTreeWidgetItem(compareRole,icon_mask,parent), m_header(header) {} - - bool operator<(const QTreeWidgetItem& other) const - { - bool left_is_not_pinned = ! data(COLUMN_THREAD_DATE,ROLE_THREAD_PINNED).toBool(); - bool right_is_not_pinned = !other.data(COLUMN_THREAD_DATE,ROLE_THREAD_PINNED).toBool(); -#ifdef DEBUG_PINNED_POST_SORTING - std::cerr << "Comparing item date \"" << data(COLUMN_THREAD_DATE,Qt::DisplayRole).toString().toStdString() << "\" (" - << data(COLUMN_THREAD_DATE,ROLE_THREAD_SORT).toUInt() << ", \"" << data(COLUMN_THREAD_DATE,ROLE_THREAD_SORT).toString().toStdString() << "\" --> " << left_is_not_pinned << ") to \"" - << other.data(COLUMN_THREAD_DATE,Qt::DisplayRole).toString().toStdString() << "\" (" - << other.data(COLUMN_THREAD_DATE,ROLE_THREAD_SORT).toUInt() << ", \"" << other.data(COLUMN_THREAD_DATE,ROLE_THREAD_SORT).toString().toStdString() << "\" --> " << right_is_not_pinned << ") "; -#endif - - if(left_is_not_pinned ^ right_is_not_pinned) - { -#ifdef DEBUG_PINNED_POST_SORTING - std::cerr << "Local: " << ((m_header->sortIndicatorOrder()==Qt::AscendingOrder)?right_is_not_pinned:left_is_not_pinned) << std::endl; -#endif - return (m_header->sortIndicatorOrder()==Qt::AscendingOrder)?right_is_not_pinned:left_is_not_pinned ; // always put pinned posts on top - } - -#ifdef DEBUG_PINNED_POST_SORTING - std::cerr << "Remote: " << GxsIdRSTreeWidgetItem::operator<(other) << std::endl; -#endif - return GxsIdRSTreeWidgetItem::operator<(other); - } - -private: - QHeaderView *m_header ; -}; - -QTreeWidgetItem *GxsForumThreadWidget::convertMsgToThreadWidget(const RsGxsForumMsg &msg, bool useChildTS, uint32_t filterColumn, QTreeWidgetItem *parent) -{ - // Early check for a message that should be hidden because its author - // is flagged with a bad reputation - - bool is_pinned = mForumGroup.mPinnedPosts.ids.find(msg.mMeta.mMsgId) != mForumGroup.mPinnedPosts.ids.end(); - - uint32_t idflags =0; - RsReputations::ReputationLevel reputation_level = rsReputations->overallReputationLevel(msg.mMeta.mAuthorId,&idflags) ; - bool redacted = false; - - redacted = (reputation_level == RsReputations::REPUTATION_LOCALLY_NEGATIVE); - - // We use a specific item model for forums in order to handle the post pinning. - - GxsIdRSTreeWidgetItem *item = new ForumThreadItem(ui->threadTreeWidget->header(),mThreadCompareRole,GxsIdDetails::ICON_TYPE_AVATAR ); - item->moveToThread(ui->threadTreeWidget->thread()); - - if(redacted) - item->setText(COLUMN_THREAD_TITLE, tr("[ ... Redacted message ... ]")); - else if(is_pinned) - item->setText(COLUMN_THREAD_TITLE, tr("[PINNED] ") + QString::fromUtf8(msg.mMeta.mMsgName.c_str())); - else - item->setText(COLUMN_THREAD_TITLE, QString::fromUtf8(msg.mMeta.mMsgName.c_str())); - - QString rep_tooltip_str ; - uint32_t rep_warning_level ; - - if(reputation_level == RsReputations::REPUTATION_UNKNOWN) - { - rep_warning_level = 3 ; - rep_tooltip_str = tr("Information for this identity is currently missing.") ; - } - else if(reputation_level == RsReputations::REPUTATION_LOCALLY_NEGATIVE) - { - rep_warning_level = 2 ; - rep_tooltip_str = tr("You have banned this ID. The message will not be\ndisplayed nor forwarded to your friends.") ; - } - else if(reputation_level < rsGxsForums->minReputationForForwardingMessages(mForumGroup.mMeta.mSignFlags,idflags)) - { - rep_warning_level = 1 ; - rep_tooltip_str = tr("You have not set an opinion for this person,\n and your friends do not vote positively: Spam regulation \nprevents the message to be forwarded to your friends.") ; - } - else - { - rep_warning_level = 0 ; - rep_tooltip_str = tr("Message will be forwarded to your friends.") ; - } - - item->setData(COLUMN_THREAD_DISTRIBUTION,Qt::ToolTipRole,rep_tooltip_str) ; - item->setData(COLUMN_THREAD_DISTRIBUTION,Qt::DecorationRole,rep_warning_level) ; - - //msg.mMeta.mChildTs Was not updated when received new child - // so do it here. - QDateTime qtime; - qtime.setTime_t(msg.mMeta.mPublishTs); - - QString itemText = DateTime::formatDateTime(qtime); - // This is an attempt to put pinned posts on the top. We should rather use a QSortFilterProxyModel here. - QString itemSort = QString::number(msg.mMeta.mPublishTs);//Don't need to format it as for sort. - -//#define SHOW_COMBINED_DATES 1 - - if (useChildTS) - { - for(QTreeWidgetItem *grandParent = parent; grandParent!=NULL; grandParent = grandParent->parent()) - { - //Update Parent Child TimeStamp - QString oldTSSort = grandParent->data(COLUMN_THREAD_DATE, ROLE_THREAD_SORT).toString(); - - QString oldCTSSort = oldTSSort.split("|").at(0); - QString oldPTSSort = oldTSSort.contains("|") ? oldTSSort.split(" | ").at(1) : oldCTSSort; -#ifdef SHOW_COMBINED_DATES - QString oldTSText = grandParent->text(COLUMN_THREAD_DATE); - QString oldCTSText = oldTSText.split("|").at(0); - QString oldPTSText = oldTSText.contains("|") ? oldTSText.split(" | ").at(1) : oldCTSText;//If first time parent get only its mPublishTs - #endif - if (oldCTSSort.toDouble() < itemSort.toDouble()) - { -#ifdef SHOW_COMBINED_DATES - grandParent->setText(COLUMN_THREAD_DATE, DateTime::formatDateTime(qtime) + " | " + oldPTSText); -#endif - grandParent->setData(COLUMN_THREAD_DATE, ROLE_THREAD_SORT, itemSort + " | " + oldPTSSort); - } - } - } - - item->setText(COLUMN_THREAD_DATE, itemText); - item->setData(COLUMN_THREAD_DATE,ROLE_THREAD_SORT, itemSort); - - if(is_pinned) - item->setData(COLUMN_THREAD_DATE,ROLE_THREAD_PINNED, QVariant(true)); // this is used by the sorting model to put all posts on top - else - item->setData(COLUMN_THREAD_DATE,ROLE_THREAD_PINNED, QVariant(false)); - - // Set later with GxsIdRSTreeWidgetItem::setId - item->setData(COLUMN_THREAD_DATA, ROLE_THREAD_AUTHOR, QString::fromStdString(msg.mMeta.mAuthorId.toStdString())); - -//#TODO -#if 0 - text = QString::fromUtf8(authorName.c_str()); - - if (text.isEmpty()) - { - item->setText(COLUMN_THREAD_AUTHOR, tr("Anonymous")); - } - else - { - item->setText(COLUMN_THREAD_AUTHOR, text); - } -#endif -//#TODO -#ifdef TOGXS - if (msgInfo.mMeta.mMsgFlags & RS_DISTRIB_AUTHEN_REQ) - { - item->setText(COLUMN_THREAD_SIGNED, tr("signed")); - item->setIcon(COLUMN_THREAD_SIGNED, QIcon(":/images/mail-signed.png")); - } - else - { - item->setText(COLUMN_THREAD_SIGNED, tr("none")); - item->setIcon(COLUMN_THREAD_SIGNED, QIcon(":/images/mail-signature-unknown.png")); - } -#endif - - if (filterColumn == COLUMN_THREAD_CONTENT) { - // need content for filter - QTextDocument doc; - doc.setHtml(QString::fromUtf8(msg.mMsg.c_str())); - item->setText(COLUMN_THREAD_CONTENT, doc.toPlainText().replace(QString("\n"), QString(" "))); - } - - item->setData(COLUMN_THREAD_MSGID,Qt::DisplayRole, QString::fromStdString(msg.mMeta.mMsgId.toStdString())); -//#TODO -#if 0 - if (IS_GROUP_SUBSCRIBED(subscribeFlags) && !(msginfo.mMsgFlags & RS_DISTRIB_MISSING_MSG)) { - rsGxsForums->getMessageStatus(msginfo.forumId, msginfo.msgId, status); - } else { - // show message as read - status = RSGXS_MSG_STATUS_READ; - } -#endif - item->setData(COLUMN_THREAD_DATA, ROLE_THREAD_STATUS, msg.mMeta.mMsgStatus); - item->setData(COLUMN_THREAD_DATA, ROLE_THREAD_MISSING, false); - - if (parent) parent->addChild(item); - return item; -} - -QTreeWidgetItem *GxsForumThreadWidget::generateMissingItem(const RsGxsMessageId &msgId) -{ - GxsIdRSTreeWidgetItem *item = new GxsIdRSTreeWidgetItem(mThreadCompareRole,GxsIdDetails::ICON_TYPE_AVATAR); - - item->setText(COLUMN_THREAD_TITLE, tr("[ ... Missing Message ... ]")); - item->setData(COLUMN_THREAD_MSGID,Qt::DisplayRole, QString::fromStdString(msgId.toStdString())); - item->setData(COLUMN_THREAD_DATA, ROLE_THREAD_MISSING, true); - - item->setId(RsGxsId(), COLUMN_THREAD_AUTHOR, false); // fixed up columnId() - - return item; -} - -void GxsForumThreadWidget::insertThreads() -{ -#ifdef DEBUG_FORUMS - /* get the current Forum */ - std::cerr << "GxsForumThreadWidget::insertThreads()" << std::endl; -#endif - - mNavigatePendingMsgId.clear(); - ui->progressBar->reset(); - - if (mFillThread) { -#ifdef DEBUG_FORUMS - std::cerr << "GxsForumThreadWidget::insertThreads() stop current fill thread" << std::endl; -#endif - // stop current fill thread - GxsForumsFillThread *thread = mFillThread; - mFillThread = NULL; - thread->stop(); - - mStateHelper->setLoading(mTokenTypeInsertThreads, false); - } - - if (groupId().isNull()) - { - /* not an actual forum - clear */ - mStateHelper->setActive(mTokenTypeInsertThreads, false); - mStateHelper->clear(mTokenTypeInsertThreads); - - /* clear last stored forumID */ - mLastForumID.clear(); - -#ifdef DEBUG_FORUMS - std::cerr << "GxsForumThreadWidget::insertThreads() Current Thread Invalid" << std::endl; -#endif - - return; - } - -#ifdef DEBUG_FORUMS - std::cerr << "GxsForumThreadWidget::insertThreads() Start filling Forum threads" << std::endl; -#endif - - mStateHelper->setLoading(mTokenTypeInsertThreads, true); - - // create fill thread - mFillThread = new GxsForumsFillThread(this); - - // set data - mFillThread->mCompareRole = mThreadCompareRole; - mFillThread->mForumId = groupId(); - mFillThread->mFilterColumn = ui->filterLineEdit->currentFilter(); - mFillThread->mExpandNewMessages = Settings->getForumExpandNewMessages(); - mFillThread->mViewType = ui->viewBox->currentIndex(); - if (mLastViewType != mFillThread->mViewType || mLastForumID != groupId()) { - mFillThread->mFillComplete = true; - } - - mFillThread->mFlatView = false; - mFillThread->mUseChildTS = false; - - switch (mFillThread->mViewType) { - case VIEW_LAST_POST: - mFillThread->mUseChildTS = true; - break; - case VIEW_FLAT: - mFillThread->mFlatView = true; - break; - case VIEW_THREADED: - break; - } - - ui->threadTreeWidget->setRootIsDecorated(!mFillThread->mFlatView); - - // connect thread - connect(mFillThread, SIGNAL(finished()), this, SLOT(fillThreadFinished()), Qt::BlockingQueuedConnection); - connect(mFillThread, SIGNAL(status(QString)), this, SLOT(fillThreadStatus(QString))); - connect(mFillThread, SIGNAL(progress(int,int)), this, SLOT(fillThreadProgress(int,int))); - -#ifdef DEBUG_FORUMS - std::cerr << "ForumsDialog::insertThreads() Start fill thread" << std::endl; -#endif - - // start thread - mFillThread->start(); - emit groupChanged(this); -} - -static void copyItem(QTreeWidgetItem *item, const QTreeWidgetItem *newItem) -{ - int i; - for (i = 0; i < COLUMN_THREAD_COUNT; ++i) { - if (i != COLUMN_THREAD_AUTHOR) { - /* Copy text */ - item->setText(i, newItem->text(i)); - } - } - for (i = 0; i < ROLE_THREAD_COUNT; ++i) { - item->setData(COLUMN_THREAD_DATA, Qt::UserRole + i, newItem->data(COLUMN_THREAD_DATA, Qt::UserRole + i)); - } - - item->setData(COLUMN_THREAD_DISTRIBUTION,Qt::DecorationRole,newItem->data(COLUMN_THREAD_DISTRIBUTION,Qt::DecorationRole)); - item->setData(COLUMN_THREAD_DISTRIBUTION,Qt::ToolTipRole, newItem->data(COLUMN_THREAD_DISTRIBUTION,Qt::ToolTipRole )); - item->setData(COLUMN_THREAD_MSGID, Qt::DisplayRole, newItem->data(COLUMN_THREAD_MSGID, Qt::DisplayRole )); -} - -void GxsForumThreadWidget::fillThreads(QList &threadList, bool expandNewMessages, QList &itemToExpand) -{ -#ifdef DEBUG_FORUMS - std::cerr << "GxsForumThreadWidget::fillThreads()" << std::endl; -#endif - - int index = 0; - QTreeWidgetItem *threadItem; - - // store new items in a map, so as to allow a fast search - - std::map newThreadMap ; - - for(QList::iterator newThread = threadList.begin (); newThread != threadList.end (); ++newThread) - newThreadMap[(*newThread)->data(COLUMN_THREAD_MSGID,Qt::DisplayRole).toString()] = *newThread ; - - // delete not existing - while (index < ui->threadTreeWidget->topLevelItemCount()) - { - threadItem = ui->threadTreeWidget->topLevelItem(index); - - if(newThreadMap.find(threadItem->data(COLUMN_THREAD_MSGID,Qt::DisplayRole).toString()) == newThreadMap.end()) - delete(ui->threadTreeWidget->takeTopLevelItem(index)); - else - ++index; - } - - //(csoler) QTreeWidget::findItems apparently does not always work so I need to make the search manually, which I do using a map for efficiency reasons. - std::map oldThreadMap; - for(int i=0; ithreadTreeWidget->topLevelItemCount(); ++i) - oldThreadMap[ui->threadTreeWidget->topLevelItem(i)->data(COLUMN_THREAD_MSGID, Qt::DisplayRole).toString()] = ui->threadTreeWidget->topLevelItem(i); - - // iterate all new threads - for (QList::iterator newThread = threadList.begin (); newThread != threadList.end (); ++newThread) { - // search existing thread -#ifdef DEBUG_FORUMS - std::cerr << "Makign a search for string \"" << (*newThread)->data(COLUMN_THREAD_MSGID,Qt::DisplayRole).toString().toStdString() << "\"" << std::endl; -#endif - - std::map::const_iterator it = oldThreadMap.find((*newThread)->data(COLUMN_THREAD_MSGID,Qt::DisplayRole).toString()) ; - - if(it != oldThreadMap.end()) - { - threadItem = it->second ; - - // set child data - copyItem(threadItem, *newThread); - - // fill recursive - fillChildren(threadItem, *newThread, expandNewMessages, itemToExpand); - } - else - { - // add new thread - ui->threadTreeWidget->addTopLevelItem (*newThread); - threadItem = *newThread; - *newThread = NULL; - } - - uint32_t status = threadItem->data(COLUMN_THREAD_DATA, ROLE_THREAD_STATUS).toUInt(); - if (expandNewMessages && IS_MSG_UNREAD(status)) { - QTreeWidgetItem *parentItem = threadItem; - while ((parentItem = parentItem->parent()) != NULL) { - if (std::find(itemToExpand.begin(), itemToExpand.end(), parentItem) == itemToExpand.end()) { - itemToExpand.push_back(parentItem); - } - } - } - } - -#ifdef DEBUG_FORUMS - std::cerr << "GxsForumThreadWidget::fillThreads() done" << std::endl; -#endif -} - -void GxsForumThreadWidget::fillChildren(QTreeWidgetItem *parentItem, QTreeWidgetItem *newParentItem, bool expandNewMessages, QList &itemToExpand) -{ - int index = 0; - int newIndex; - int newCount = newParentItem->childCount(); - - QTreeWidgetItem *childItem; - QTreeWidgetItem *newChildItem; - - std::map newParentItemMap, parentItemMap ; - - for(index = 0; index < newParentItem->childCount(); ++index) newParentItemMap[newParentItem->child(index)->data(COLUMN_THREAD_MSGID,Qt::DisplayRole).toString()] = newParentItem->child(index); - for(index = 0; index < parentItem->childCount(); ++index) parentItemMap[ parentItem->child(index)->data(COLUMN_THREAD_MSGID,Qt::DisplayRole).toString()] = parentItem->child(index); - - // delete not existing - while (index < parentItem->childCount()) - { - childItem = parentItem->child(index); - - if(newParentItemMap.find(childItem->data(COLUMN_THREAD_MSGID,Qt::DisplayRole).toString()) == newParentItemMap.end()) - delete(parentItem->takeChild (index)); - else - ++index; - } - - // iterate all new children - for (newIndex = 0; newIndex < newParentItem->childCount(); ++newIndex) - { - newChildItem = newParentItem->child(newIndex); - - // search existing child - - std::map::const_iterator it = parentItemMap.find(newChildItem->data(COLUMN_THREAD_MSGID,Qt::DisplayRole).toString()) ; - - if(it != parentItemMap.end()) - { - // set child data - copyItem(it->second, newChildItem); - - // fill recursive - fillChildren(it->second, newChildItem, expandNewMessages, itemToExpand); - childItem = it->second; - } - else - { - // add new child - childItem = newParentItem->takeChild(newIndex); - parentItem->addChild(childItem); - newIndex--; - newCount--; - } - - uint32_t status = childItem->data(COLUMN_THREAD_DATA, ROLE_THREAD_STATUS).toUInt(); - if (expandNewMessages && IS_MSG_UNREAD(status)) { - QTreeWidgetItem *parentItem = childItem; - while ((parentItem = parentItem->parent()) != NULL) { - if (std::find(itemToExpand.begin(), itemToExpand.end(), parentItem) == itemToExpand.end()) { - itemToExpand.push_back(parentItem); - } - } - } - } + ui->postText->setText(forum_description); } void GxsForumThreadWidget::insertMessage() { +#ifdef DEBUG_FORUMS + std::cerr << "Inserting message, threadId=" << mThreadId <setActive(mTokenTypeMessageData, false); - mStateHelper->clear(mTokenTypeMessageData); - +#ifdef DEBUG_FORUMS + std::cerr << " groupId()=NULL !! That's a bug." << std::endl; +#endif ui->versions_CB->hide(); ui->time_label->show(); ui->postText->clear(); - //ui->threadTitle->clear(); return; } if (mThreadId.isNull()) { - mStateHelper->setActive(mTokenTypeMessageData, false); - mStateHelper->clear(mTokenTypeMessageData); - +#ifdef DEBUG_FORUMS + std::cerr << " mThreadId=NULL !! That's a bug." << std::endl; +#endif ui->versions_CB->hide(); ui->time_label->show(); - //ui->threadTitle->setText(tr("Forum Description")); - ui->postText->setText(mForumDescription); + ui->postText->setText(QString::fromUtf8(mForumGroup.mDescription.c_str())); return; } - mStateHelper->setActive(mTokenTypeMessageData, true); + // We use this instead of getCurrentIndex() because right here the currentIndex() is not set yet. - QTreeWidgetItem *item = ui->threadTreeWidget->currentItem(); - if (item) { - QTreeWidgetItem *parentItem = item->parent(); - int index = parentItem ? parentItem->indexOfChild(item) : ui->threadTreeWidget->indexOfTopLevelItem(item); - int count = parentItem ? parentItem->childCount() : ui->threadTreeWidget->topLevelItemCount(); - mStateHelper->setWidgetEnabled(ui->previousButton, (index > 0)); - mStateHelper->setWidgetEnabled(ui->nextButton, (index < count - 1)); + QModelIndex index = mThreadProxyModel->mapFromSource(mThreadModel->getIndexOfMessage(mOrigThreadId)); + + if (index.isValid()) + { + QModelIndex parentIndex = index.parent(); + int curr_index = index.row(); + int count = mThreadProxyModel->rowCount(parentIndex); + + ui->previousButton->setEnabled(curr_index > 0); + ui->nextButton->setEnabled(curr_index < count - 1); } else { +#ifdef DEBUG_FORUMS + std::cerr << " current index invalid! That's a bug." << std::endl; +#endif // there is something wrong - mStateHelper->setWidgetEnabled(ui->previousButton, false); - mStateHelper->setWidgetEnabled(ui->nextButton, false); + ui->previousButton->setEnabled(false); + ui->nextButton->setEnabled(false); ui->versions_CB->hide(); ui->time_label->show(); return; } - mStateHelper->setWidgetEnabled(ui->newmessageButton, (IS_GROUP_SUBSCRIBED(mSubscribeFlags) && mThreadId.isNull() == false)); + mStateHelper->setWidgetEnabled(ui->newmessageButton, (IS_GROUP_SUBSCRIBED(mForumGroup.mMeta.mSubscribeFlags) && mThreadId.isNull() == false)); /* blank text, incase we get nothing */ ui->postText->clear(); @@ -1722,32 +1229,37 @@ void GxsForumThreadWidget::insertMessage() // add/show combobox for versions, if applicable, and enable it. If no older versions of the post available, hide the combobox. - std::cerr << "Looking into existing versions for post " << mThreadId << ", thread history: " << mPostVersions.size() << std::endl; - - QMap > >::const_iterator it = mPostVersions.find(mOrigThreadId) ; + std::vector > post_versions = mThreadModel->getPostVersions(mOrigThreadId); +#ifdef DEBUG_FORUMS + std::cerr << "Looking into existing versions for post " << mOrigThreadId << ", thread history: " << post_versions.size() << std::endl; +#endif ui->versions_CB->blockSignals(true) ; while(ui->versions_CB->count() > 0) ui->versions_CB->removeItem(0); - if(it != mPostVersions.end()) + if(!post_versions.empty()) { - std::cerr << (*it).size() << " versions found " << std::endl; +#ifdef DEBUG_FORUMS + std::cerr << post_versions.size() << " versions found " << std::endl; +#endif ui->versions_CB->setVisible(true) ; ui->time_label->hide(); int current_index = 0 ; - for(int i=0;i<(*it).size();++i) + for(int i=0;iversions_CB->insertItem(i, ((i==0)?tr("(Latest) "):tr("(Old) "))+" "+DateTime::formatLongDateTime( (*it)[i].first)); - ui->versions_CB->setItemData(i,QString::fromStdString((*it)[i].second.toStdString())); + ui->versions_CB->insertItem(i, ((i==0)?tr("(Latest) "):tr("(Old) "))+" "+DateTime::formatLongDateTime( post_versions[i].first)); + ui->versions_CB->setItemData(i,QString::fromStdString(post_versions[i].second.toStdString())); - std::cerr << " added new post version " << (*it)[i].first << " " << (*it)[i].second << std::endl; +#ifdef DEBUG_FORUMS + std::cerr << " added new post version " << post_versions[i].first << " " << post_versions[i].second << std::endl; +#endif - if(mThreadId == (*it)[i].second) + if(mThreadId == post_versions[i].second) current_index = i ; } @@ -1762,8 +1274,9 @@ void GxsForumThreadWidget::insertMessage() ui->versions_CB->blockSignals(false) ; /* request Post */ - RsGxsGrpMsgIdPair msgId = std::make_pair(groupId(), mThreadId); - requestMessageData(msgId); + updateMessageData(mThreadId); + +// markMsgAsRead(); } void GxsForumThreadWidget::insertMessageData(const RsGxsForumMsg &msg) @@ -1782,39 +1295,32 @@ void GxsForumThreadWidget::insertMessageData(const RsGxsForumMsg &msg) std::cerr << std::endl; std::cerr << std::endl; - mStateHelper->setActive(mTokenTypeMessageData, false); - mStateHelper->clear(mTokenTypeMessageData); - return; } uint32_t overall_reputation = rsReputations->overallReputationLevel(msg.mMeta.mAuthorId) ; bool redacted = (overall_reputation == RsReputations::REPUTATION_LOCALLY_NEGATIVE) ; - mStateHelper->setActive(mTokenTypeMessageData, true); - - QTreeWidgetItem *item = ui->threadTreeWidget->currentItem(); - bool setToReadOnActive = Settings->getForumMsgSetToReadOnActivate(); - uint32_t status = item->data(COLUMN_THREAD_DATA, ROLE_THREAD_STATUS).toUInt(); - - QList row; - row.append(item); + uint32_t status = msg.mMeta.mMsgStatus ;//item->data(RsGxsForumModel::COLUMN_THREAD_DATA, ROLE_THREAD_STATUS).toUInt(); +#ifdef TO_REMOVE + QModelIndex index = getCurrentIndex(); if (IS_MSG_NEW(status)) { if (setToReadOnActive) { /* set to read */ - setMsgReadStatus(row, true); + mThreadModel->setMsgReadStatus(mThreadProxyModel->mapToSource(index),true,false); } else { /* set to unread by user */ - setMsgReadStatus(row, false); + mThreadModel->setMsgReadStatus(mThreadProxyModel->mapToSource(index),false,false); } } else { if (setToReadOnActive && IS_MSG_UNREAD(status)) { /* set to read */ - setMsgReadStatus(row, true); + mThreadModel->setMsgReadStatus(mThreadProxyModel->mapToSource(index), true,false); } } +#endif ui->time_label->setText(DateTime::formatLongDateTime(msg.mMeta.mPublishTs)); ui->by_label->setId(msg.mMeta.mAuthorId); @@ -1822,6 +1328,7 @@ void GxsForumThreadWidget::insertMessageData(const RsGxsForumMsg &msg) ui->lineLeft->show(); ui->by_text_label->show(); ui->by_label->show(); + ui->threadTreeWidget->setFocus(); if(redacted) { @@ -1841,42 +1348,59 @@ void GxsForumThreadWidget::insertMessageData(const RsGxsForumMsg &msg) QString extraTxt = RsHtml().formatText(ui->postText->document(), QString::fromUtf8(msg.mMsg.c_str()),flags); ui->postText->setHtml(extraTxt); } - // ui->threadTitle->setText(QString::fromUtf8(msg.mMeta.mMsgName.c_str())); } void GxsForumThreadWidget::previousMessage() { - QTreeWidgetItem *item = ui->threadTreeWidget->currentItem(); - if (item == NULL) { - return; - } + QModelIndex current_index = getCurrentIndex(); - QTreeWidgetItem *parentItem = item->parent(); - int index = parentItem ? parentItem->indexOfChild(item) : ui->threadTreeWidget->indexOfTopLevelItem(item); - if (index > 0) { - QTreeWidgetItem *previousItem = parentItem ? parentItem->child(index - 1) : ui->threadTreeWidget->topLevelItem(index - 1); - if (previousItem) { - ui->threadTreeWidget->setCurrentItem(previousItem); + if (!current_index.isValid()) + return; + + QModelIndex parentIndex = current_index.parent(); + + int index = current_index.row(); + int count = mThreadModel->rowCount(parentIndex) ; + + if (index > 0) + { + QModelIndex prevItem = mThreadProxyModel->index(index - 1,0,parentIndex) ; + + if (prevItem.isValid()) { + ui->threadTreeWidget->setCurrentIndex(prevItem); + ui->threadTreeWidget->setFocus(); + changedThread(prevItem); } } + ui->previousButton->setEnabled(index-1 > 0); + ui->nextButton->setEnabled(true); + } void GxsForumThreadWidget::nextMessage() { - QTreeWidgetItem *item = ui->threadTreeWidget->currentItem(); - if (item == NULL) { - return; - } + QModelIndex current_index = getCurrentIndex(); - QTreeWidgetItem *parentItem = item->parent(); - int index = parentItem ? parentItem->indexOfChild(item) : ui->threadTreeWidget->indexOfTopLevelItem(item); - int count = parentItem ? parentItem->childCount() : ui->threadTreeWidget->topLevelItemCount(); - if (index < count - 1) { - QTreeWidgetItem *nextItem = parentItem ? parentItem->child(index + 1) : ui->threadTreeWidget->topLevelItem(index + 1); - if (nextItem) { - ui->threadTreeWidget->setCurrentItem(nextItem); + if (!current_index.isValid()) + return; + + QModelIndex parentIndex = current_index.parent(); + + int index = current_index.row(); + int count = mThreadProxyModel->rowCount(parentIndex); + + if (index < count - 1) + { + QModelIndex nextItem = mThreadProxyModel->index(index + 1,0,parentIndex) ; + + if (nextItem.isValid()) { + ui->threadTreeWidget->setCurrentIndex(nextItem); + ui->threadTreeWidget->setFocus(); + changedThread(nextItem); } } + ui->previousButton->setEnabled(true); + ui->nextButton->setEnabled(index+1 < count - 1); } void GxsForumThreadWidget::downloadAllFiles() @@ -1895,192 +1419,50 @@ void GxsForumThreadWidget::downloadAllFiles() void GxsForumThreadWidget::nextUnreadMessage() { - QTreeWidgetItem *currentItem = ui->threadTreeWidget->currentItem(); + QModelIndex index = getCurrentIndex(); - while (true) { - QTreeWidgetItemIterator itemIterator = currentItem ? QTreeWidgetItemIterator(currentItem, QTreeWidgetItemIterator::NotHidden) : QTreeWidgetItemIterator(ui->threadTreeWidget, QTreeWidgetItemIterator::NotHidden); + if(!index.isValid()) + index = mThreadProxyModel->index(0,0); + else + { + if(index.data(RsGxsForumModel::UnreadChildrenRole).toBool()) + ui->threadTreeWidget->expand(index); - QTreeWidgetItem *item; - while ((item = *itemIterator) != NULL) { - ++itemIterator; + index = ui->threadTreeWidget->indexBelow(index); + } - if (item == currentItem) { - continue; - } + while(index.isValid() && !IS_MSG_UNREAD(index.sibling(index.row(),RsGxsForumModel::COLUMN_THREAD_DATA).data(RsGxsForumModel::StatusRole).toUInt())) + { + if(index.data(RsGxsForumModel::UnreadChildrenRole).toBool()) + ui->threadTreeWidget->expand(index); - uint32_t status = item->data(COLUMN_THREAD_DATA, ROLE_THREAD_STATUS).toUInt(); - if (IS_MSG_UNREAD(status)) { - ui->threadTreeWidget->setCurrentItem(item); - ui->threadTreeWidget->scrollToItem(item, QAbstractItemView::EnsureVisible); - return; - } - } - - if (currentItem == NULL) { - break; - } - - /* start from top */ - currentItem = NULL; - } -} - -/* get selected messages - the messages tree is single selected, but who knows ... */ -int GxsForumThreadWidget::getSelectedMsgCount(QList *rows, QList *rowsRead, QList *rowsUnread) -{ - if (rowsRead) rowsRead->clear(); - if (rowsUnread) rowsUnread->clear(); - - QList selectedItems = ui->threadTreeWidget->selectedItems(); - for(QList::iterator it = selectedItems.begin(); it != selectedItems.end(); ++it) { - if (rows) rows->append(*it); - if (rowsRead || rowsUnread) { - uint32_t status = (*it)->data(COLUMN_THREAD_DATA, ROLE_THREAD_STATUS).toUInt(); - if (IS_MSG_UNREAD(status)) { - if (rowsUnread) rowsUnread->append(*it); - } else { - if (rowsRead) rowsRead->append(*it); - } - } + index = ui->threadTreeWidget->indexBelow(index); } - return selectedItems.size(); -} - -void GxsForumThreadWidget::setMsgReadStatus(QList &rows, bool read) -{ - QList::iterator row; - std::list changedItems; - - mInMsgAsReadUnread = true; - - for (row = rows.begin(); row != rows.end(); ++row) { - if ((*row)->data(COLUMN_THREAD_DATA, ROLE_THREAD_MISSING).toBool()) { - /* Missing message */ - continue; - } - - uint32_t status = (*row)->data(COLUMN_THREAD_DATA, ROLE_THREAD_STATUS).toUInt(); - - uint32_t statusNew = (status & ~(GXS_SERV::GXS_MSG_STATUS_GUI_NEW | GXS_SERV::GXS_MSG_STATUS_GUI_UNREAD)); // orig status, without NEW AND UNREAD - if (!read) { - statusNew |= GXS_SERV::GXS_MSG_STATUS_GUI_UNREAD; - } - - if (status != statusNew) // is it different? - { - std::string msgId = (*row)->data(COLUMN_THREAD_MSGID,Qt::DisplayRole).toString().toStdString(); - - // NB: MUST BE PART OF ACTIVE THREAD--- OR ELSE WE MUST STORE GROUPID SOMEWHERE!. - // LIKE THIS BELOW... - //std::string grpId = (*Row)->data(COLUMN_THREAD_DATA, ROLE_THREAD_GROUPID).toString().toStdString(); - - RsGxsGrpMsgIdPair msgPair = std::make_pair( groupId(), RsGxsMessageId(msgId) ); - - uint32_t token; - rsGxsForums->setMessageReadStatus(token, msgPair, read); - - // Look if older version exist to mark them too - QMap > >::const_iterator it = mPostVersions.find(RsGxsMessageId(msgId)) ; - if(it != mPostVersions.end()) - { - std::cerr << (*it).size() << " versions found " << std::endl; - for(int i=0;i<(*it).size();++i) - { - RsGxsMessageId found = (*it)[i].second; - if(found != RsGxsMessageId(msgId)) - { - msgPair = std::make_pair( groupId(), found ); - rsGxsForums->setMessageReadStatus(token, msgPair, read); - } - } - } - - /* Add message id to ignore list for the next updateDisplay */ - mIgnoredMsgId.push_back(RsGxsMessageId(msgId)); - - (*row)->setData(COLUMN_THREAD_DATA, ROLE_THREAD_STATUS, statusNew); - - QTreeWidgetItem *parentItem = *row; - while (parentItem->parent()) { - parentItem = parentItem->parent(); - } - if (std::find(changedItems.begin(), changedItems.end(), parentItem) == changedItems.end()) { - changedItems.push_back(parentItem); - } - } - } - - mInMsgAsReadUnread = false; - - if (changedItems.size()) { - for (std::list::iterator it = changedItems.begin(); it != changedItems.end(); ++it) { - calculateIconsAndFonts(*it); - } - calculateUnreadCount(); - } -} - -void GxsForumThreadWidget::showInPeopleTab() -{ - if (groupId().isNull() || mThreadId.isNull()) { - QMessageBox::information(this, tr("RetroShare"),tr("You cant act on the author to a non-existant Message")); - return; - } - - RsGxsGrpMsgIdPair postId = std::make_pair(groupId(), mThreadId); - requestMsgData_ShowAuthorInPeople(postId) ; + ui->threadTreeWidget->setCurrentIndex(index); + ui->threadTreeWidget->scrollTo(index); + changedThread(index); } void GxsForumThreadWidget::markMsgAsReadUnread (bool read, bool children, bool forum) { - if (groupId().isNull() || !IS_GROUP_SUBSCRIBED(mSubscribeFlags)) { + if (groupId().isNull() || !IS_GROUP_SUBSCRIBED(mForumGroup.mMeta.mSubscribeFlags)) { return; } - /* get selected messages */ - QList rows; - if (forum) { - int itemCount = ui->threadTreeWidget->topLevelItemCount(); - for (int item = 0; item < itemCount; ++item) { - rows.push_back(ui->threadTreeWidget->topLevelItem(item)); - } - } else { - getSelectedMsgCount (&rows, NULL, NULL); + if(forum) + mThreadModel->setMsgReadStatus(mThreadModel->root(),read,children); + else + { + QModelIndexList selectedIndexes = ui->threadTreeWidget->selectionModel()->selectedIndexes(); + + if(selectedIndexes.size() != RsGxsForumModel::COLUMN_THREAD_NB_COLUMNS) // check that a single row is selected + return ; + + QModelIndex index = *selectedIndexes.begin(); + + mThreadModel->setMsgReadStatus(mThreadProxyModel->mapToSource(index),read,children); } - - if (children) { - /* add children */ - QList allRows; - - while (rows.isEmpty() == false) { - QTreeWidgetItem *row = rows.takeFirst(); - - /* add only items with the right state or with not RSGXS_MSG_STATUS_READ */ - uint32_t status = row->data(COLUMN_THREAD_DATA, ROLE_THREAD_STATUS).toUInt(); - bool isUnread = IS_MSG_UNREAD(status); - if (isUnread == read || IS_MSG_NEW(status)) { - allRows.append(row); - } - - for (int i = 0; i < row->childCount(); ++i) { - /* add child to main list and let the main loop do the work */ - rows.append(row->child(i)); - } - } - - if (allRows.isEmpty()) { - /* nothing to do */ - return; - } - - setMsgReadStatus(allRows, read); - - return; - } - - setMsgReadStatus(rows, read); } void GxsForumThreadWidget::markMsgAsRead() @@ -2110,38 +1492,21 @@ void GxsForumThreadWidget::setAllMessagesReadDo(bool read, uint32_t &/*token*/) bool GxsForumThreadWidget::navigate(const RsGxsMessageId &msgId) { - if (mStateHelper->isLoading(mTokenTypeInsertThreads)) { - mNavigatePendingMsgId = msgId; + QModelIndex source_index = mThreadModel->getIndexOfMessage(msgId); - /* No information if message is available */ - return true; - } + if(!source_index.isValid()) + { + mNavigatePendingMsgId = msgId; // not found. That means the forum may not be loaded yet. So we keep that post in mind, for after loading. + return true; // we have to return true here, otherwise the caller will intepret the async loading as an error. + } - QString msgIdString = QString::fromStdString(msgId.toStdString()); + QModelIndex indx = mThreadProxyModel->mapFromSource(source_index); - /* Search exisiting item */ - QTreeWidgetItemIterator itemIterator(ui->threadTreeWidget); - QTreeWidgetItem *item = NULL; - while ((item = *itemIterator) != NULL) { - ++itemIterator; - - if (item->data(COLUMN_THREAD_MSGID,Qt::DisplayRole).toString() == msgIdString) { - ui->threadTreeWidget->setCurrentItem(item); - ui->threadTreeWidget->setFocus(); - return true; - } - } - - return false; -} - -bool GxsForumThreadWidget::isLoading() -{ - if (mStateHelper->isLoading(mTokenTypeGroupData) || mFillThread) { - return true; - } - - return GxsMessageFrameWidget::isLoading(); + ui->threadTreeWidget->setCurrentIndex(indx); + ui->threadTreeWidget->scrollTo(indx); + ui->threadTreeWidget->setFocus(); + changedThread(indx); + return true; } void GxsForumThreadWidget::copyMessageLink() @@ -2150,9 +1515,10 @@ void GxsForumThreadWidget::copyMessageLink() return; } - QTreeWidgetItem *item = ui->threadTreeWidget->currentItem(); + ForumModelPostEntry fmpe ; + getCurrentPost(fmpe); - QString thread_title = (item != NULL)?item->text(COLUMN_THREAD_TITLE):QString() ; + QString thread_title = QString::fromUtf8(fmpe.mTitle.c_str()); RetroShareLink link = RetroShareLink::createGxsMessageLink(RetroShareLink::TYPE_FORUM, groupId(), mThreadId, thread_title); @@ -2171,12 +1537,11 @@ void GxsForumThreadWidget::subscribeGroup(bool subscribe) uint32_t token; rsGxsForums->subscribeToGroup(token, groupId(), subscribe); -// mTokenQueue->queueRequest(token, 0, RS_TOKREQ_ANSTYPE_ACK, TOKEN_TYPE_SUBSCRIBE_CHANGE); } void GxsForumThreadWidget::createmessage() { - if (groupId().isNull () || !IS_GROUP_SUBSCRIBED(mSubscribeFlags)) { + if (groupId().isNull () || !IS_GROUP_SUBSCRIBED(mForumGroup.mMeta.mSubscribeFlags)) { return; } @@ -2188,22 +1553,24 @@ void GxsForumThreadWidget::createmessage() void GxsForumThreadWidget::togglePinUpPost() { - if (groupId().isNull() || mThreadId.isNull()) + if (groupId().isNull() || mOrigThreadId.isNull()) return; - QTreeWidgetItem *item = ui->threadTreeWidget->currentItem(); + QModelIndex index = getCurrentIndex(); // normally this method is only called on top level items. We still check it just in case... - if(item->parent() != NULL) + if(mThreadProxyModel->mapToSource(index).parent() != mThreadModel->root()) { std::cerr << "(EE) togglePinUpPost() called on non top level post. This is inconsistent." << std::endl; return ; } - QString thread_title = (item != NULL)?item->text(COLUMN_THREAD_TITLE):QString() ; + QString thread_title = index.sibling(index.row(),RsGxsForumModel::COLUMN_THREAD_TITLE).data(Qt::DisplayRole).toString(); +#ifdef DEBUG_FORUMS std::cerr << "Toggling Pin-up state of post " << mThreadId.toStdString() << ": \"" << thread_title.toStdString() << "\"" << std::endl; +#endif if(mForumGroup.mPinnedPosts.ids.find(mThreadId) == mForumGroup.mPinnedPosts.ids.end()) mForumGroup.mPinnedPosts.ids.insert(mThreadId) ; @@ -2213,7 +1580,7 @@ void GxsForumThreadWidget::togglePinUpPost() uint32_t token; rsGxsForums->updateGroup(token,mForumGroup); - ui->threadTreeWidget->takeTopLevelItem(ui->threadTreeWidget->indexOfTopLevelItem(item)); // forces the re-creation of all posts widgets. A bit extreme. We should rather only delete item above + groupIdChanged(); // reloads all posts. We could also update the model directly, but the cost is so small now ;-) updateDisplay(true) ; } @@ -2256,59 +1623,67 @@ void GxsForumThreadWidget::flagperson() return; } - uint32_t token_type = qobject_cast(sender())->data().toUInt(); + RsReputations::Opinion opinion = static_cast(qobject_cast(sender())->data().toUInt()); - // Get Message ... then complete replyMessageData(). - RsGxsGrpMsgIdPair postId = std::make_pair(groupId(), mThreadId); + mThreadModel->setAuthorOpinion(mThreadProxyModel->mapToSource(getCurrentIndex()),opinion); +} - RsTokReqOptions opts; - opts.mReqType = GXS_REQUEST_TYPE_MSG_DATA; +void GxsForumThreadWidget::replytoforummessage() { async_msg_action( &GxsForumThreadWidget::replyForumMessageData ); } +void GxsForumThreadWidget::editforummessage() { async_msg_action( &GxsForumThreadWidget::editForumMessageData ); } +void GxsForumThreadWidget::reply_with_private_message() { async_msg_action( &GxsForumThreadWidget::replyMessageData ); } +void GxsForumThreadWidget::showInPeopleTab() { async_msg_action( &GxsForumThreadWidget::showAuthorInPeople ); } + +void GxsForumThreadWidget::async_msg_action(const MsgMethod &action) +{ + if (groupId().isNull() || mThreadId.isNull()) { + QMessageBox::information(this, tr("RetroShare"),tr("You cant reply to a non-existant Message")); + return; + } + + RsThread::async([this,action]() + { + // 1 - get message data from p3GxsForums #ifdef DEBUG_FORUMS - std::cerr << "GxsForumThreadWidget::requestMsgData_BanAuthor(" << postId.first << "," << postId.second << ")"; - std::cerr << std::endl; + std::cerr << "Retrieving post data for post " << mThreadId << std::endl; #endif - GxsMsgReq msgIds; - std::set &vect = msgIds[postId.first]; - vect.insert(postId.second); + std::set msgs_to_request ; + std::vector msgs; - uint32_t token; - mTokenQueue->requestMsgInfo(token, RS_TOKREQ_ANSTYPE_DATA, opts, msgIds, token_type); -} + msgs_to_request.insert(mThreadId); -void GxsForumThreadWidget::reply_with_private_message() -{ - if (groupId().isNull() || mThreadId.isNull()) { - QMessageBox::information(this, tr("RetroShare"),tr("You cant reply to a non-existant Message")); - return; - } + if(!rsGxsForums->getForumsContent(groupId(),msgs_to_request,msgs)) + { + std::cerr << __PRETTY_FUNCTION__ << " failed to retrieve forum group info for forum " << groupId() << std::endl; + return; + } - // Get Message ... then complete replyMessageData(). - RsGxsGrpMsgIdPair postId = std::make_pair(groupId(), mThreadId); - requestMsgData_ReplyWithPrivateMessage(postId); -} -void GxsForumThreadWidget::editforummessage() -{ - if (groupId().isNull() || mThreadId.isNull()) { - QMessageBox::information(this, tr("RetroShare"),tr("You cant reply to a non-existant Message")); - return; - } + if(msgs.size() != 1) + { + std::cerr << __PRETTY_FUNCTION__ << " more than 1 or no msgs selected in forum " << groupId() << std::endl; + return; + } - // Get Message ... then complete replyMessageData(). - RsGxsGrpMsgIdPair postId = std::make_pair(groupId(), mThreadId); - requestMsgData_EditForumMessage(postId); -} -void GxsForumThreadWidget::replytoforummessage() -{ - if (groupId().isNull() || mThreadId.isNull()) { - QMessageBox::information(this, tr("RetroShare"),tr("You cant reply to a non-existant Message")); - return; - } + // 2 - sort the messages into a proper hierarchy - // Get Message ... then complete replyMessageData(). - RsGxsGrpMsgIdPair postId = std::make_pair(groupId(), mThreadId); - requestMsgData_ReplyForumMessage(postId); + RsGxsForumMsg msg = msgs[0]; + + // 3 - update the model in the UI thread. + + RsQThreadUtils::postToObject( [msg,action,this]() + { + /* Here it goes any code you want to be executed on the Qt Gui + * thread, for example to update the data model with new information + * after a blocking call to RetroShare API complete, note that + * Qt::QueuedConnection is important! + */ + + (this->*action)(msg); + + }, this ); + + }); } void GxsForumThreadWidget::replyMessageData(const RsGxsForumMsg &msg) @@ -2339,18 +1714,6 @@ void GxsForumThreadWidget::replyMessageData(const RsGxsForumMsg &msg) } } -void GxsForumThreadWidget::showAuthorInPeople(const RsGxsForumMsg& msg) -{ - if ((msg.mMeta.mGroupId != groupId()) || (msg.mMeta.mMsgId != mThreadId)) - { - std::cerr << "GxsForumThreadWidget::replyMessageData() ERROR Message Ids have changed!"; - std::cerr << std::endl; - return; - } - RsGxsGrpMsgIdPair postId = std::make_pair(groupId(), mThreadId); - requestMsgData_ShowAuthorInPeople(postId); -} - void GxsForumThreadWidget::editForumMessageData(const RsGxsForumMsg& msg) { if ((msg.mMeta.mGroupId != groupId()) || (msg.mMeta.mMsgId != mThreadId)) @@ -2408,10 +1771,6 @@ void GxsForumThreadWidget::replyForumMessageData(const RsGxsForumMsg &msg) { CreateGxsForumMsg *cfm = new CreateGxsForumMsg(groupId(), mThreadId,RsGxsMessageId()); -// QTextDocument doc ; -// doc.setHtml(QString::fromUtf8(msg.mMsg.c_str()) ); -// std::string cited_text(doc.toPlainText().toStdString()) ; - RsHtml::makeQuotedText(ui->postText); cfm->insertPastedText(RsHtml::makeQuotedText(ui->postText)) ; @@ -2434,30 +1793,27 @@ void GxsForumThreadWidget::saveImage() void GxsForumThreadWidget::changedViewBox() { - if (mInProcessSettings) { - return; - } + ui->threadTreeWidget->selectionModel()->clear(); + ui->threadTreeWidget->selectionModel()->reset(); + mThreadId.clear(); // save index Settings->setValueToGroup("ForumThreadWidget", "viewBox", ui->viewBox->currentIndex()); - ui->threadTreeWidget->clear(); + if(ui->viewBox->currentIndex() == VIEW_FLAT) + mThreadModel->setTreeMode(RsGxsForumModel::TREE_MODE_FLAT); + else + mThreadModel->setTreeMode(RsGxsForumModel::TREE_MODE_TREE); - insertThreads(); + if(ui->viewBox->currentIndex() == VIEW_LAST_POST) + mThreadModel->setSortMode(RsGxsForumModel::SORT_MODE_CHILDREN_PUBLISH_TS); + else + mThreadModel->setSortMode(RsGxsForumModel::SORT_MODE_PUBLISH_TS); } void GxsForumThreadWidget::filterColumnChanged(int column) { - if (mInProcessSettings) { - return; - } - - if (column == COLUMN_THREAD_CONTENT) { - // need content ... refill - insertThreads(); - } else { - filterItems(ui->filterLineEdit->text()); - } + filterItems(ui->filterLineEdit->text()); // save index Settings->setValueToGroup("ForumThreadWidget", "filterColumn", column); @@ -2465,445 +1821,198 @@ void GxsForumThreadWidget::filterColumnChanged(int column) void GxsForumThreadWidget::filterItems(const QString& text) { + QStringList lst = text.split(" ",QString::SkipEmptyParts) ; + int filterColumn = ui->filterLineEdit->currentFilter(); - int count = ui->threadTreeWidget->topLevelItemCount(); - for (int index = 0; index < count; ++index) { - filterItem(ui->threadTreeWidget->topLevelItem(index), text, filterColumn); - } -} + uint32_t count; + mThreadModel->setFilter(filterColumn,lst,count) ; -bool GxsForumThreadWidget::filterItem(QTreeWidgetItem *item, const QString &text, int filterColumn) -{ - bool visible = true; + // We do this in order to trigger a new filtering action in the proxy model. + mThreadProxyModel->setFilterRegExp(QRegExp(QString(RsGxsForumModel::FilterString))) ; - if (text.isEmpty() == false) { - if (item->text(filterColumn).contains(text, Qt::CaseInsensitive) == false) { - visible = false; - } - } + if(!lst.empty()) + ui->threadTreeWidget->expandAll(); + else + ui->threadTreeWidget->collapseAll(); - int visibleChildCount = 0; - int count = item->childCount(); - for (int nIndex = 0; nIndex < count; ++nIndex) { - if (filterItem(item->child(nIndex), text, filterColumn)) { - ++visibleChildCount; - } - } - - if (visible || visibleChildCount) { - item->setHidden(false); - } else { - item->setHidden(true); - } - - return (visible || visibleChildCount); + if(count > 0) + ui->filterLineEdit->setToolTip(tr("No result.")) ; + else + ui->filterLineEdit->setToolTip(tr("Found %1 results.").arg(count)) ; } /*********************** **** **** **** ***********************/ /** Request / Response of Data ********************************/ /*********************** **** **** **** ***********************/ -void GxsForumThreadWidget::requestGroupData() -{ - mSubscribeFlags = 0; - mSignFlags = 0; - mForumDescription.clear(); - - mTokenQueue->cancelActiveRequestTokens(mTokenTypeGroupData); - - if (groupId().isNull()) { - mStateHelper->setActive(mTokenTypeGroupData, false); - mStateHelper->setLoading(mTokenTypeGroupData, false); - mStateHelper->clear(mTokenTypeGroupData); - - emit groupChanged(this); - - return; - } - - mStateHelper->setLoading(mTokenTypeGroupData, true); - emit groupChanged(this); - - RsTokReqOptions opts; - opts.mReqType = GXS_REQUEST_TYPE_GROUP_DATA; - - std::list grpIds; - grpIds.push_back(groupId()); - -#ifdef DEBUG_FORUMS - std::cerr << "GxsForumThreadWidget::requestGroupData(" << groupId() << ")"; - std::cerr << std::endl; -#endif - - uint32_t token; - mTokenQueue->requestGroupInfo(token, RS_TOKREQ_ANSTYPE_DATA, opts, grpIds, mTokenTypeGroupData); -} - -void GxsForumThreadWidget::loadGroupData(const uint32_t &token) +void GxsForumThreadWidget::postForumLoading() { #ifdef DEBUG_FORUMS - std::cerr << "GxsForumThreadWidget::loadGroup_CurrentForum()"; - std::cerr << std::endl; + std::cerr << "Post forum loading..." << std::endl; #endif + if(!mNavigatePendingMsgId.isNull() && mThreadModel->getIndexOfMessage(mNavigatePendingMsgId).isValid()) + { + mThreadId = mNavigatePendingMsgId; + mNavigatePendingMsgId.clear(); + } - std::vector groups; - rsGxsForums->getGroupData(token, groups); + QModelIndex source_index = mThreadModel->getIndexOfMessage(mThreadId); - mStateHelper->setLoading(mTokenTypeGroupData, false); - - if (groups.size() == 1) - { - mForumGroup = groups[0]; - insertGroupData(); - - mStateHelper->setActive(mTokenTypeGroupData, true); - - // Don't show the distribution column if the forum has no anti-spam - ui->threadTreeWidget->setColumnHidden(COLUMN_THREAD_DISTRIBUTION, !IS_GROUP_PGP_KNOWN_AUTHED(mForumGroup.mMeta.mSignFlags) && !(IS_GROUP_PGP_AUTHED(mForumGroup.mMeta.mSignFlags))); - ui->subscribeToolButton->setHidden(IS_GROUP_SUBSCRIBED(mSubscribeFlags)) ; + if(!mThreadId.isNull() && source_index.isValid()) + { + QModelIndex index = mThreadProxyModel->mapFromSource(source_index); + ui->threadTreeWidget->selectionModel()->setCurrentIndex(index,QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); + ui->threadTreeWidget->scrollTo(index); +#ifdef DEBUG_FORUMS + std::cerr << " re-selecting index of message " << mThreadId << " to " << source_index.row() << "," << source_index.column() << " " << (void*)source_index.internalPointer() << std::endl; +#endif } else - { - std::cerr << "GxsForumThreadWidget::loadGroupSummary_CurrentForum() ERROR Invalid Number of Groups..."; - std::cerr << std::endl; - - mStateHelper->setActive(mTokenTypeGroupData, false); - mStateHelper->clear(mTokenTypeGroupData); - } - - emit groupChanged(this); -} - -/*********************** **** **** **** ***********************/ -/*********************** **** **** **** ***********************/ - -void GxsForumThreadWidget::requestMessageData(const RsGxsGrpMsgIdPair &msgId) -{ - mStateHelper->setLoading(mTokenTypeMessageData, true); - - mTokenQueue->cancelActiveRequestTokens(mTokenTypeMessageData); - - RsTokReqOptions opts; - opts.mReqType = GXS_REQUEST_TYPE_MSG_DATA; - + { #ifdef DEBUG_FORUMS - std::cerr << "GxsForumThreadWidget::requestMessage(" << msgId.first << "," << msgId.second << ")"; - std::cerr << std::endl; + std::cerr << " previously message " << mThreadId << " not visible anymore -> de-selecting" << std::endl; #endif - - GxsMsgReq msgIds; - std::set &vect = msgIds[msgId.first]; - vect.insert(msgId.second); - - uint32_t token; - mTokenQueue->requestMsgInfo(token, RS_TOKREQ_ANSTYPE_DATA, opts, msgIds, mTokenTypeMessageData); -} - -void GxsForumThreadWidget::loadMessageData(const uint32_t &token) -{ - mStateHelper->setLoading(mTokenTypeMessageData, false); - -#ifdef DEBUG_FORUMS - std::cerr << "GxsForumThreadWidget::loadMessage()"; - std::cerr << std::endl; -#endif - - std::vector msgs; - if (rsGxsForums->getMsgData(token, msgs)) { - if (msgs.size() != 1) { - std::cerr << "GxsForumThreadWidget::loadMessage() ERROR Wrong number of answers"; - std::cerr << std::endl; - - mStateHelper->setActive(mTokenTypeMessageData, false); - mStateHelper->clear(mTokenTypeMessageData); - return; - } - insertMessageData(msgs[0]); - } else { - std::cerr << "GxsForumThreadWidget::loadMessage() ERROR Missing Message Data..."; - std::cerr << std::endl; - - mStateHelper->setActive(mTokenTypeMessageData, false); - mStateHelper->clear(mTokenTypeMessageData); + ui->threadTreeWidget->selectionModel()->clear(); + ui->threadTreeWidget->selectionModel()->reset(); + mThreadId.clear(); + //blank(); } + // we also need to restore expanded threads + + ui->forumName->setText(QString::fromUtf8(mForumGroup.mMeta.mGroupName.c_str())); + ui->threadTreeWidget->sortByColumn(RsGxsForumModel::COLUMN_THREAD_DATE, Qt::DescendingOrder); + ui->threadTreeWidget->update(); + + recursRestoreExpandedItems(mThreadProxyModel->mapFromSource(mThreadModel->root()),mSavedExpandedMessages); + //mUpdating = false; } - -/*********************** **** **** **** ***********************/ -/*********************** **** **** **** ***********************/ - - -void GxsForumThreadWidget::requestMsgData_ReplyWithPrivateMessage(const RsGxsGrpMsgIdPair &msgId) +void GxsForumThreadWidget::updateGroupData() { - RsTokReqOptions opts; - opts.mReqType = GXS_REQUEST_TYPE_MSG_DATA; + if(groupId().isNull()) + return; -#ifdef DEBUG_FORUMS - std::cerr << "GxsForumThreadWidget::requestMsgData_ReplyMessage(" << msgId.first << "," << msgId.second << ")"; - std::cerr << std::endl; -#endif + // ui->threadTreeWidget->selectionModel()->clear(); + // ui->threadTreeWidget->selectionModel()->reset(); + // mThreadProxyModel->clear(); - GxsMsgReq msgIds; - std::set &vect = msgIds[msgId.first]; - vect.insert(msgId.second); - - uint32_t token; - mTokenQueue->requestMsgInfo(token, RS_TOKREQ_ANSTYPE_DATA, opts, msgIds, mTokenTypeReplyMessage); -} - -void GxsForumThreadWidget::requestMsgData_ShowAuthorInPeople(const RsGxsGrpMsgIdPair& msgId) -{ - RsTokReqOptions opts; - opts.mReqType = GXS_REQUEST_TYPE_MSG_DATA; - -#ifdef DEBUG_FORUMS - std::cerr << "GxsForumThreadWidget::requestMsgData_ReplyMessage(" << msgId.first << "," << msgId.second << ")"; - std::cerr << std::endl; -#endif - - GxsMsgReq msgIds; - std::set &vect = msgIds[msgId.first]; - vect.insert(msgId.second); - - uint32_t token; - mTokenQueue->requestMsgInfo(token, RS_TOKREQ_ANSTYPE_DATA, opts, msgIds, mTokenTypeShowAuthorInPeople); -} -void GxsForumThreadWidget::requestMsgData_EditForumMessage(const RsGxsGrpMsgIdPair &msgId) -{ - RsTokReqOptions opts; - opts.mReqType = GXS_REQUEST_TYPE_MSG_DATA; - -#ifdef DEBUG_FORUMS - std::cerr << "GxsForumThreadWidget::requestMsgData_ReplyMessage(" << msgId.first << "," << msgId.second << ")"; - std::cerr << std::endl; -#endif - - GxsMsgReq msgIds; - std::set &vect = msgIds[msgId.first]; - vect.insert(msgId.second); - - uint32_t token; - mTokenQueue->requestMsgInfo(token, RS_TOKREQ_ANSTYPE_DATA, opts, msgIds, mTokenTypeEditForumMessage); -} -void GxsForumThreadWidget::requestMsgData_ReplyForumMessage(const RsGxsGrpMsgIdPair &msgId) -{ - RsTokReqOptions opts; - opts.mReqType = GXS_REQUEST_TYPE_MSG_DATA; - -#ifdef DEBUG_FORUMS - std::cerr << "GxsForumThreadWidget::requestMsgData_ReplyMessage(" << msgId.first << "," << msgId.second << ")"; - std::cerr << std::endl; -#endif - - GxsMsgReq msgIds; - std::set &vect = msgIds[msgId.first]; - vect.insert(msgId.second); - - uint32_t token; - mTokenQueue->requestMsgInfo(token, RS_TOKREQ_ANSTYPE_DATA, opts, msgIds, mTokenTypeReplyForumMessage); -} - -void GxsForumThreadWidget::loadMsgData_ReplyMessage(const uint32_t &token) -{ -#ifdef DEBUG_FORUMS - std::cerr << "GxsForumThreadWidget::loadMsgData_ReplyMessage()"; - std::cerr << std::endl; -#endif - - std::vector msgs; - if (rsGxsForums->getMsgData(token, msgs)) + RsThread::async([this]() { - if (msgs.size() != 1) + // 1 - get message data from p3GxsForums + + std::list forumIds; + std::vector groups; + + forumIds.push_back(groupId()); + + if(!rsGxsForums->getForumsInfo(forumIds,groups)) { - std::cerr << "GxsForumThreadWidget::loadMsgData_ReplyMessage() ERROR Wrong number of answers"; - std::cerr << std::endl; + std::cerr << __PRETTY_FUNCTION__ << " failed to retrieve forum group info for forum " << groupId() << std::endl; return; - } - replyMessageData(msgs[0]); - } - else - { - std::cerr << "GxsForumThreadWidget::loadMsgData_ReplyMessage() ERROR Missing Message Data..."; - std::cerr << std::endl; - } + } + + if(groups.size() != 1) + { + std::cerr << __PRETTY_FUNCTION__ << " obtained more than one group info for forum " << groupId() << std::endl; + return; + } + + // 2 - sort the messages into a proper hierarchy + + RsGxsForumGroup *group = new RsGxsForumGroup(groups[0]); // we use a pointer in order to avoid group deletion while we're in the thread. + + // 3 - update the model in the UI thread. + + RsQThreadUtils::postToObject( [group,this]() + { + /* Here it goes any code you want to be executed on the Qt Gui + * thread, for example to update the data model with new information + * after a blocking call to RetroShare API complete, note that + * Qt::QueuedConnection is important! + */ + + mForumGroup = *group; + delete group; + + ui->threadTreeWidget->setColumnHidden(RsGxsForumModel::COLUMN_THREAD_DISTRIBUTION, !IS_GROUP_PGP_KNOWN_AUTHED(mForumGroup.mMeta.mSignFlags) && !(IS_GROUP_PGP_AUTHED(mForumGroup.mMeta.mSignFlags))); + ui->subscribeToolButton->setHidden(IS_GROUP_SUBSCRIBED(mForumGroup.mMeta.mSubscribeFlags)) ; + + updateForumDescription(); + + }, this ); + + }); } -void GxsForumThreadWidget::loadMsgData_EditForumMessage(const uint32_t &token) +void GxsForumThreadWidget::updateMessageData(const RsGxsMessageId& msgId) { -#ifdef DEBUG_FORUMS - std::cerr << "GxsForumThreadWidget::loadMsgData_EditMessage()"; - std::cerr << std::endl; -#endif - - std::vector msgs; - if (rsGxsForums->getMsgData(token, msgs)) + RsThread::async([msgId,this]() { - if (msgs.size() != 1) + // 1 - get message data from p3GxsForums + + std::cerr << "Retrieving post data for post " << msgId << std::endl; + + std::set msgs_to_request ; + std::vector msgs; + + msgs_to_request.insert(msgId); + + if(!rsGxsForums->getForumsContent(groupId(),msgs_to_request,msgs)) { - std::cerr << "GxsForumThreadWidget::loadMsgData_EditMessage() ERROR Wrong number of answers"; - std::cerr << std::endl; + std::cerr << __PRETTY_FUNCTION__ << " failed to retrieve forum group info for forum " << groupId() << std::endl; return; - } + } - editForumMessageData(msgs[0]); - } - else - { - std::cerr << "GxsForumThreadWidget::loadMsgData_ReplyMessage() ERROR Missing Message Data..."; - std::cerr << std::endl; - } -} -void GxsForumThreadWidget::loadMsgData_ReplyForumMessage(const uint32_t &token) -{ -#ifdef DEBUG_FORUMS - std::cerr << "GxsForumThreadWidget::loadMsgData_ReplyMessage()"; - std::cerr << std::endl; -#endif + if(msgs.empty()) + { + std::cerr << __PRETTY_FUNCTION__ << " no posts for msgId " << msgId << ". Database corruption?" << std::endl; + return; + } + if(msgs.size() > 1) + { + std::cerr << __PRETTY_FUNCTION__ << " obtained more than one msg info for msgId " << msgId << ". This could be a bug. Only showing the first msg in the list." << std::endl; + std::cerr << "Messages are:" << std::endl; + for(auto it(msgs.begin());it!=msgs.end();++it) + std::cerr << (*it).mMeta << std::endl; + } - std::vector msgs; - if (rsGxsForums->getMsgData(token, msgs)) - { - if (msgs.size() != 1) + // 2 - sort the messages into a proper hierarchy + + RsGxsForumMsg *msg = new RsGxsForumMsg(msgs[0]); + + // 3 - update the model in the UI thread. + + RsQThreadUtils::postToObject( [msg,this]() { - std::cerr << "GxsForumThreadWidget::loadMsgData_ReplyMessage() ERROR Wrong number of answers"; - std::cerr << std::endl; - return; - } + /* Here it goes any code you want to be executed on the Qt Gui + * thread, for example to update the data model with new information + * after a blocking call to RetroShare API complete, note that + * Qt::QueuedConnection is important! + */ - replyForumMessageData(msgs[0]); - } - else - { - std::cerr << "GxsForumThreadWidget::loadMsgData_ReplyMessage() ERROR Missing Message Data..."; - std::cerr << std::endl; - } + insertMessageData(*msg); + + delete msg; + ui->threadTreeWidget->setColumnHidden(RsGxsForumModel::COLUMN_THREAD_DISTRIBUTION, !IS_GROUP_PGP_KNOWN_AUTHED(mForumGroup.mMeta.mSignFlags) && !(IS_GROUP_PGP_AUTHED(mForumGroup.mMeta.mSignFlags))); + ui->subscribeToolButton->setHidden(IS_GROUP_SUBSCRIBED(mForumGroup.mMeta.mSubscribeFlags)) ; + }, this ); + }); } -void GxsForumThreadWidget::loadMsgData_ShowAuthorInPeople(const uint32_t &token) +void GxsForumThreadWidget::showAuthorInPeople(const RsGxsForumMsg& msg) { -#ifdef DEBUG_FORUMS - std::cerr << "GxsForumThreadWidget::loadMsgData_ReplyMessage()"; - std::cerr << std::endl; -#endif - - std::vector msgs; - if (rsGxsForums->getMsgData(token, msgs)) + if(msg.mMeta.mAuthorId.isNull()) { - if (msgs.size() != 1) - { - std::cerr << "GxsForumThreadWidget::loadMsgData_showAuthorInPeople() ERROR Wrong number of answers"; - std::cerr << std::endl; - return; - } - - if(msgs[0].mMeta.mAuthorId.isNull()) - { - std::cerr << "GxsForumThreadWidget::loadMsgData_showAuthorInPeople() ERROR Missing Message Data..."; - std::cerr << std::endl; - } - - /* window will destroy itself! */ - IdDialog *idDialog = dynamic_cast(MainWindow::getPage(MainWindow::People)); - - if (!idDialog) - return ; - - MainWindow::showWindow(MainWindow::People); - idDialog->navigate(RsGxsId(msgs[0].mMeta.mAuthorId)); - } - else std::cerr << "GxsForumThreadWidget::loadMsgData_showAuthorInPeople() ERROR Missing Message Data..."; -} - -void GxsForumThreadWidget::loadMsgData_SetAuthorOpinion(const uint32_t &token,RsReputations::Opinion opinion) -{ -#ifdef DEBUG_FORUMS - std::cerr << "GxsForumThreadWidget::loadMsgData_BanAuthor()"; - std::cerr << std::endl; -#endif - - std::vector msgs; - if (rsGxsForums->getMsgData(token, msgs)) - { - if (msgs.size() != 1) - { - std::cerr << "GxsForumThreadWidget::loadMsgData_ReplyMessage() ERROR Wrong number of answers"; - std::cerr << std::endl; - return; - } - - std::cerr << " banning author id " << msgs[0].mMeta.mAuthorId << std::endl; - - rsReputations->setOwnOpinion(msgs[0].mMeta.mAuthorId,opinion) ; - } - else - { - std::cerr << "GxsForumThreadWidget::loadMsgData_ReplyMessage() ERROR Missing Message Data..."; std::cerr << std::endl; } - updateDisplay(true) ; - - // we should also update the icons so that they changed to the icon for banned peers. - - std::cerr << __PRETTY_FUNCTION__ << ": need to implement the update of GxsTreeWidgetItems icons too." << std::endl; -} -/*********************** **** **** **** ***********************/ -/*********************** **** **** **** ***********************/ - -void GxsForumThreadWidget::loadRequest(const TokenQueue *queue, const TokenRequest &req) -{ -#ifdef DEBUG_FORUMS - std::cerr << "GxsForumThreadWidget::loadRequest() UserType: " << req.mUserType; - std::cerr << std::endl; -#endif - - if (queue == mTokenQueue) - { - /* now switch on req */ - if (req.mUserType == mTokenTypeGroupData) { - loadGroupData(req.mToken); - return; - } - - if (req.mUserType == mTokenTypeMessageData) { - loadMessageData(req.mToken); - return; - } - - if (req.mUserType == mTokenTypeReplyMessage) { - loadMsgData_ReplyMessage(req.mToken); - return; - } - - if (req.mUserType == mTokenTypeReplyForumMessage) { - loadMsgData_ReplyForumMessage(req.mToken); - return; - } - - if (req.mUserType == mTokenTypeEditForumMessage) { - loadMsgData_EditForumMessage(req.mToken); - return; - } - if (req.mUserType == mTokenTypeShowAuthorInPeople) { - loadMsgData_ShowAuthorInPeople(req.mToken); - return; - } - - if (req.mUserType == mTokenTypePositiveAuthor) { - loadMsgData_SetAuthorOpinion(req.mToken,RsReputations::OPINION_POSITIVE); - return; - } - - if (req.mUserType == mTokenTypeNegativeAuthor) { - loadMsgData_SetAuthorOpinion(req.mToken,RsReputations::OPINION_NEGATIVE); - return; - } - - if (req.mUserType == mTokenTypeNeutralAuthor) { - loadMsgData_SetAuthorOpinion(req.mToken,RsReputations::OPINION_NEUTRAL); - return; - } - } - - GxsMessageFrameWidget::loadRequest(queue, req); + + /* window will destroy itself! */ + IdDialog *idDialog = dynamic_cast(MainWindow::getPage(MainWindow::People)); + + if (!idDialog) + return ; + + MainWindow::showWindow(MainWindow::People); + idDialog->navigate(RsGxsId(msg.mMeta.mAuthorId)); } diff --git a/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.h b/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.h index 40aa469cb..705d080a3 100644 --- a/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.h +++ b/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.h @@ -27,11 +27,16 @@ #include #include "gui/gxs/GxsIdDetails.h" +class QSortFilterProxyModel; class QTreeWidgetItem; class RSTreeWidgetItemCompareRole; class RsGxsForumMsg; class GxsForumsFillThread; +class QItemSelection; class RsGxsForumGroup; +class RsGxsForumModel; +class RsGxsForumMsg; +class ForumModelPostEntry; namespace Ui { class GxsForumThreadWidget; @@ -41,6 +46,8 @@ class GxsForumThreadWidget : public GxsMessageFrameWidget { Q_OBJECT + typedef void (GxsForumThreadWidget::*MsgMethod)(const RsGxsForumMsg&) ; + Q_PROPERTY(QColor textColorRead READ textColorRead WRITE setTextColorRead) Q_PROPERTY(QColor textColorUnread READ textColorUnread WRITE setTextColorUnread) Q_PROPERTY(QColor textColorUnreadChildren READ textColorUnreadChildren WRITE setTextColorUnreadChildren) @@ -57,32 +64,28 @@ public: QColor textColorNotSubscribed() const { return mTextColorNotSubscribed; } QColor textColorMissing() const { return mTextColorMissing; } - void setTextColorRead(QColor color) { mTextColorRead = color; } - void setTextColorUnread(QColor color) { mTextColorUnread = color; } - void setTextColorUnreadChildren(QColor color) { mTextColorUnreadChildren = color; } - void setTextColorNotSubscribed(QColor color) { mTextColorNotSubscribed = color; } - void setTextColorMissing(QColor color) { mTextColorMissing = color; } + void setTextColorRead (QColor color) ; + void setTextColorUnread (QColor color) ; + void setTextColorUnreadChildren(QColor color) ; + void setTextColorNotSubscribed (QColor color) ; + void setTextColorMissing (QColor color) ; /* GxsMessageFrameWidget */ virtual void groupIdChanged(); virtual QString groupName(bool withUnreadCount); virtual QIcon groupIcon(); virtual bool navigate(const RsGxsMessageId& msgId); - virtual bool isLoading(); unsigned int newCount() { return mNewCount; } unsigned int unreadCount() { return mUnreadCount; } - QTreeWidgetItem *convertMsgToThreadWidget(const RsGxsForumMsg &msg, bool useChildTS, uint32_t filterColumn, QTreeWidgetItem *parent); QTreeWidgetItem *generateMissingItem(const RsGxsMessageId &msgId); - // Callback for all Loads. - virtual void loadRequest(const TokenQueue *queue, const TokenRequest &req); virtual void blank(); protected: - bool eventFilter(QObject *obj, QEvent *ev); - void changeEvent(QEvent *e); + //bool eventFilter(QObject *obj, QEvent *ev); + //void changeEvent(QEvent *e); /* RsGxsUpdateBroadcastWidget */ virtual void updateDisplay(bool complete); @@ -95,9 +98,11 @@ private slots: void threadListCustomPopupMenu(QPoint point); void contextMenuTextBrowser(QPoint point); - void changedThread(); + void changedSelection(const QModelIndex &, const QModelIndex &); + void changedThread(QModelIndex index); void changedVersion(); - void clickedThread (QTreeWidgetItem *item, int column); + void clickedThread (QModelIndex index); + void postForumLoading(); void reply_with_private_message(); void replytoforummessage(); @@ -108,13 +113,11 @@ private slots: void replyForumMessageData(const RsGxsForumMsg &msg); void showAuthorInPeople(const RsGxsForumMsg& msg); + // This method is used to perform an asynchroneous action on the message data. Any of the methods above can be used as parameter. + void async_msg_action(const MsgMethod& method); + void saveImage(); - - //void print(); - //void printpreview(); - - //void removemessage(); void markMsgAsRead(); void markMsgAsReadChildren(); void markMsgAsUnread(); @@ -141,59 +144,41 @@ private slots: void filterColumnChanged(int column); void filterItems(const QString &text); - - void fillThreadFinished(); - void fillThreadProgress(int current, int count); - void fillThreadStatus(QString text); - private: void insertMessageData(const RsGxsForumMsg &msg); + bool getCurrentPost(ForumModelPostEntry& fmpe) const ; + QModelIndex getCurrentIndex() const; - void insertThreads(); void insertMessage(); + void insertGroupData(); - void fillThreads(QList &threadList, bool expandNewMessages, QList &itemToExpand); - void fillChildren(QTreeWidgetItem *parentItem, QTreeWidgetItem *newParentItem, bool expandNewMessages, QList &itemToExpand); + void recursRestoreExpandedItems(const QModelIndex& index, const QList& expanded_items); + void recursSaveExpandedItems(const QModelIndex& index, QList& expanded_items) const; + void saveExpandedItems(QList& expanded_items) const; int getSelectedMsgCount(QList *pRows, QList *pRowsRead, QList *pRowsUnread); void setMsgReadStatus(QList &rows, bool read); void markMsgAsReadUnread(bool read, bool children, bool forum); - void calculateIconsAndFonts(QTreeWidgetItem *item = NULL); - void calculateIconsAndFonts(QTreeWidgetItem *item, bool &hasReadChilddren, bool &hasUnreadChilddren); void calculateUnreadCount(); void togglethreadview_internal(); - bool filterItem(QTreeWidgetItem *item, const QString &text, int filterColumn); + //bool filterItem(QTreeWidgetItem *item, const QString &text, int filterColumn); void processSettings(bool bLoad); - void requestGroupData(); - void loadGroupData(const uint32_t &token); - void insertGroupData(); + void updateGroupData(); static void loadAuthorIdCallback(GxsIdDetailsType type, const RsIdentityDetails &details, QObject *object, const QVariant &/*data*/); - void requestMessageData(const RsGxsGrpMsgIdPair &msgId); - void requestMsgData_ReplyWithPrivateMessage(const RsGxsGrpMsgIdPair &msgId); - void requestMsgData_ShowAuthorInPeople(const RsGxsGrpMsgIdPair &msgId); - void requestMsgData_ReplyForumMessage(const RsGxsGrpMsgIdPair &msgId); - void requestMsgData_EditForumMessage(const RsGxsGrpMsgIdPair &msgId); - - void loadMessageData(const uint32_t &token); - void loadMsgData_ReplyMessage(const uint32_t &token); - void loadMsgData_ReplyForumMessage(const uint32_t &token); - void loadMsgData_EditForumMessage(const uint32_t &token); - void loadMsgData_ShowAuthorInPeople(const uint32_t &token); - void loadMsgData_SetAuthorOpinion(const uint32_t &token, RsReputations::Opinion opinion); + void updateMessageData(const RsGxsMessageId& msgId); + void updateForumDescription(); private: RsGxsGroupId mLastForumID; RsGxsMessageId mThreadId; RsGxsMessageId mOrigThreadId; RsGxsForumGroup mForumGroup; - QString mForumDescription; - int mSubscribeFlags; - int mSignFlags; + //bool mUpdating; bool mInProcessSettings; bool mInMsgAsReadUnread; int mLastViewType; @@ -202,17 +187,6 @@ private: unsigned int mUnreadCount; unsigned int mNewCount; - uint32_t mTokenTypeGroupData; - uint32_t mTokenTypeInsertThreads; - uint32_t mTokenTypeMessageData; - uint32_t mTokenTypeReplyMessage; - uint32_t mTokenTypeReplyForumMessage; - uint32_t mTokenTypeEditForumMessage; - uint32_t mTokenTypeShowAuthorInPeople; - uint32_t mTokenTypeNegativeAuthor; - uint32_t mTokenTypePositiveAuthor; - uint32_t mTokenTypeNeutralAuthor; - /* Color definitions (for standard see qss.default) */ QColor mTextColorRead; QColor mTextColorUnread; @@ -223,7 +197,9 @@ private: RsGxsMessageId mNavigatePendingMsgId; QList mIgnoredMsgId; - QMap > > mPostVersions ; // holds older versions of posts + RsGxsForumModel *mThreadModel; + QSortFilterProxyModel *mThreadProxyModel; + QList mSavedExpandedMessages; Ui::GxsForumThreadWidget *ui; }; diff --git a/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.ui b/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.ui index 56d08267f..f9d226c94 100644 --- a/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.ui +++ b/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.ui @@ -215,7 +215,7 @@ - + Qt::CustomContextMenu @@ -225,42 +225,9 @@ true - - - Title - - - - - - - - - :/images/message-state-header.png:/images/message-state-header.png - - - - - Date - - - - - - - - Distribution - - - - :/icons/flag-green.png:/icons/flag-green.png - - - - - Author - - + + true + @@ -272,7 +239,7 @@ - + @@ -286,28 +253,18 @@ Download all files + + Qt::LeftToRight + - - :/images/down.png:/images/down.png + + :/icons/global_switch_on_128.png:/icons/global_switch_on_128.png true - - - - - 0 - 0 - - - - Next unread - - - @@ -323,21 +280,21 @@ Reply Message - Reply + - :/images/mail_reply.png:/images/mail_reply.png + :/images/replymailall24-hover.png:/images/replymailall24-hover.png - Qt::ToolButtonTextBesideIcon + Qt::ToolButtonIconOnly true - + @@ -363,21 +320,14 @@ - - - - Qt::Vertical - - - - + Qt::Vertical - + Qt::Horizontal @@ -469,28 +419,55 @@ - + - + + + + Qt::Vertical + + + + + + + + + + + + + + By - - - - - + + + + + 0 + 0 + + + + Next unread message + + + + :/images/start.png:/images/start.png + @@ -558,11 +535,6 @@ QTextBrowser
gui/common/RSTextBrowser.h
- - RSTreeWidget - QTreeWidget -
gui/common/RSTreeWidget.h
-
ElidedLabel QLabel diff --git a/retroshare-gui/src/gui/gxsforums/GxsForumsFillThread.cpp b/retroshare-gui/src/gui/gxsforums/GxsForumsFillThread.cpp deleted file mode 100644 index 7545e4f07..000000000 --- a/retroshare-gui/src/gui/gxsforums/GxsForumsFillThread.cpp +++ /dev/null @@ -1,556 +0,0 @@ -/******************************************************************************* - * retroshare-gui/src/gui/gxsforums/GxsForumsFillThread.cpp * - * * - * Copyright 2012 Retroshare Team * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU Affero General Public License as * - * published by the Free Software Foundation, either version 3 of the * - * License, or (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU Affero General Public License for more details. * - * * - * You should have received a copy of the GNU Affero General Public License * - * along with this program. If not, see . * - * * - *******************************************************************************/ - -#include -#include - -#include "GxsForumsFillThread.h" -#include "GxsForumThreadWidget.h" - -#include "retroshare/rsgxsflags.h" -#include - -#include -#include - -//#define DEBUG_FORUMS - -#define PROGRESSBAR_MAX 100 - -GxsForumsFillThread::GxsForumsFillThread(GxsForumThreadWidget *parent) - : QThread(parent), mParent(parent) -{ - mStopped = false; - mCompareRole = NULL; - - mExpandNewMessages = true; - mFillComplete = false; - - mFilterColumn = 0; - - mViewType = 0; - mFlatView = false; - mUseChildTS = false; -} - -GxsForumsFillThread::~GxsForumsFillThread() -{ -#ifdef DEBUG_FORUMS - std::cerr << "GxsForumsFillThread::~GxsForumsFillThread" << std::endl; -#endif - - // remove all items (when items are available, the thread was terminated) - QList::iterator item; - for (item = mItems.begin (); item != mItems.end (); ++item) { - if (*item) { - delete (*item); - } - } - mItems.clear(); - - mItemToExpand.clear(); -} - -void GxsForumsFillThread::stop() -{ - disconnect(); - mStopped = true; - QApplication::processEvents(); -} - -void GxsForumsFillThread::calculateExpand(const RsGxsForumMsg &msg, QTreeWidgetItem *item) -{ - if (mFillComplete && mExpandNewMessages && IS_MSG_UNREAD(msg.mMeta.mMsgStatus)) { - QTreeWidgetItem *parentItem = item; - while ((parentItem = parentItem->parent()) != NULL) { - if (std::find(mItemToExpand.begin(), mItemToExpand.end(), parentItem) == mItemToExpand.end()) { - mItemToExpand.push_back(parentItem); - } - } - } -} - -static bool decreasing_time_comp(const QPair& e1,const QPair& e2) { return e2.first < e1.first ; } - -void GxsForumsFillThread::run() -{ - RsTokenService *service = rsGxsForums->getTokenService(); - uint32_t msg_token; - uint32_t grp_token; - - emit status(tr("Waiting")); - - { - /* get all messages of the forum */ - RsTokReqOptions opts; - opts.mReqType = GXS_REQUEST_TYPE_MSG_DATA; - - std::list grpIds; - grpIds.push_back(mForumId); - -#ifdef DEBUG_FORUMS - std::cerr << "GxsForumsFillThread::run() forum id " << mForumId << std::endl; -#endif - - service->requestMsgInfo(msg_token, RS_TOKREQ_ANSTYPE_DATA, opts, grpIds); - - /* wait for the answer */ - uint32_t requestStatus = RsTokenService::PENDING; - while (!wasStopped()) { - requestStatus = service->requestStatus(msg_token); - if (requestStatus == RsTokenService::FAILED || - requestStatus == RsTokenService::COMPLETE) { - break; - } - msleep(200); - } - - if (requestStatus == RsTokenService::FAILED) - { - deleteLater(); - return; - } - } - - // also get the forum meta data. - { - RsTokReqOptions opts; - opts.mReqType = GXS_REQUEST_TYPE_GROUP_DATA; - - std::list grpIds; - grpIds.push_back(mForumId); - -#ifdef DEBUG_FORUMS - std::cerr << "GxsForumsFillThread::run() forum id " << mForumId << std::endl; -#endif - - service->requestGroupInfo(grp_token, RS_TOKREQ_ANSTYPE_DATA, opts, grpIds); - - /* wait for the answer */ - uint32_t requestStatus = RsTokenService::PENDING; - while (!wasStopped()) { - requestStatus = service->requestStatus(grp_token); - if (requestStatus == RsTokenService::FAILED || - requestStatus == RsTokenService::COMPLETE) { - break; - } - msleep(200); - } - - if (requestStatus == RsTokenService::FAILED) - { - deleteLater(); - return; - } - } - - if (wasStopped()) - { -#ifdef DEBUG_FORUMS - std::cerr << "GxsForumsFillThread::run() thread stopped, cancel request" << std::endl; -#endif - - /* cancel request */ - service->cancelRequest(msg_token); - service->cancelRequest(grp_token); - deleteLater(); - return; - } - - emit status(tr("Retrieving")); - - std::vector forum_groups; - - if (!rsGxsForums->getGroupData(grp_token, forum_groups) || forum_groups.size() != 1) - { - deleteLater(); - return; - } - - RsGxsForumGroup forum_group = *forum_groups.begin(); - -//#ifdef DEBUG_FORUMS - std::cerr << "Retrieved group data: " << std::endl; - std::cerr << " Group ID: " << forum_group.mMeta.mGroupId << std::endl; - std::cerr << " Admin lst: " << forum_group.mAdminList.ids.size() << " elements." << std::endl; - for(auto it(forum_group.mAdminList.ids.begin());it!=forum_group.mAdminList.ids.end();++it) - std::cerr << " " << *it << std::endl; - std::cerr << " Pinned Post: " << forum_group.mPinnedPosts.ids.size() << " messages." << std::endl; - for(auto it(forum_group.mPinnedPosts.ids.begin());it!=forum_group.mPinnedPosts.ids.end();++it) - std::cerr << " " << *it << std::endl; -//#endif - - /* get messages */ - std::map msgs; - - { // This forces to delete msgs_array after the conversion to std::map. - - std::vector msgs_array; - - if (!rsGxsForums->getMsgData(msg_token, msgs_array)) - { - deleteLater(); - return; - } - - // now put everything into a map in order to make search log(n) - - for(uint32_t i=0;i > threadStack; - std::map > kids_array ; - std::set missing_parents; - - // First of all, remove all older versions of posts. This is done by first adding all posts into a hierarchy structure - // and then removing all posts which have a new versions available. The older versions are kept appart. - -#ifdef DEBUG_FORUMS - std::cerr << "GxsForumsFillThread::run() Collecting post versions" << std::endl; -#endif - mPostVersions.clear(); - std::list msg_stack ; - - for ( std::map::iterator msgIt = msgs.begin(); msgIt != msgs.end();++msgIt) - { - if(wasStopped()) - { - deleteLater(); - return; - } - if(!msgIt->second.mMeta.mOrigMsgId.isNull() && msgIt->second.mMeta.mOrigMsgId != msgIt->second.mMeta.mMsgId) - { -#ifdef DEBUG_FORUMS - std::cerr << " Post " << msgIt->second.mMeta.mMsgId << " is a new version of " << msgIt->second.mMeta.mOrigMsgId << std::endl; -#endif - std::map::iterator msgIt2 = msgs.find(msgIt->second.mMeta.mOrigMsgId); - - // Ensuring that the post exists allows to only collect the existing data. - - if(msgIt2 == msgs.end()) - continue ; - - // Make sure that the author is the same than the original message, or is a moderator. This should always happen when messages are constructed using - // the UI but nothing can prevent a nasty user to craft a new version of a message with his own signature. - - if(msgIt2->second.mMeta.mAuthorId != msgIt->second.mMeta.mAuthorId) - { - if( !IS_FORUM_MSG_MODERATION(msgIt->second.mMeta.mMsgFlags) ) // if authors are different the moderation flag needs to be set on the editing msg - continue ; - - if( forum_group.mAdminList.ids.find(msgIt->second.mMeta.mAuthorId)==forum_group.mAdminList.ids.end()) // if author is not a moderator, continue - continue ; - } - - // always add the post a self version - - if(mPostVersions[msgIt->second.mMeta.mOrigMsgId].empty()) - mPostVersions[msgIt->second.mMeta.mOrigMsgId].push_back(QPair(msgIt2->second.mMeta.mPublishTs,msgIt2->second.mMeta.mMsgId)) ; - - mPostVersions[msgIt->second.mMeta.mOrigMsgId].push_back(QPair(msgIt->second.mMeta.mPublishTs,msgIt->second.mMeta.mMsgId)) ; - } - } - - // The following code assembles all new versions of a given post into the same array, indexed by the oldest version of the post. - - for(QMap > >::iterator it(mPostVersions.begin());it!=mPostVersions.end();++it) - { - if(wasStopped()) - { - deleteLater(); - return; - } - QVector >& v(*it) ; - - for(int32_t i=0;i > >::iterator it2 = mPostVersions.find(sub_msg_id); - - if(it2 != mPostVersions.end()) - { - for(int32_t j=0;j<(*it2).size();++j) - if((*it2)[j].second != sub_msg_id) // dont copy it, since it is already present at slot i - v.append((*it2)[j]) ; - - mPostVersions.erase(it2) ; // it2 is never equal to it - } - } - } - } - - - // Now remove from msg ids, all posts except the most recent one. And make the mPostVersion be indexed by the most recent version of the post, - // which corresponds to the item in the tree widget. - -#ifdef DEBUG_FORUMS - std::cerr << "Final post versions: " << std::endl; -#endif - QMap > > mTmp; - std::map most_recent_versions ; - - for(QMap > >::iterator it(mPostVersions.begin());it!=mPostVersions.end();++it) - { -#ifdef DEBUG_FORUMS - std::cerr << "Original post: " << it.key() << std::endl; -#endif - if(wasStopped()) - { - deleteLater(); - return; - } - // Finally, sort the posts from newer to older - - qSort((*it).begin(),(*it).end(),decreasing_time_comp) ; - -#ifdef DEBUG_FORUMS - std::cerr << " most recent version " << (*it)[0].first << " " << (*it)[0].second << std::endl; -#endif - for(int32_t i=1;i<(*it).size();++i) - { - if(wasStopped()) - { - deleteLater(); - return; - } - msgs.erase((*it)[i].second) ; - -#ifdef DEBUG_FORUMS - std::cerr << " older version " << (*it)[i].first << " " << (*it)[i].second << std::endl; -#endif - } - - mTmp[(*it)[0].second] = *it ; // index the versions map by the ID of the most recent post. - - // Now make sure that message parents are consistent. Indeed, an old post may have the old version of a post as parent. So we need to change that parent - // to the newest version. So we create a map of which is the most recent version of each message, so that parent messages can be searched in it. - - for(int i=1;i<(*it).size();++i) - { - if(wasStopped()) - { - deleteLater(); - return; - } - most_recent_versions[(*it)[i].second] = (*it)[0].second ; - } - } - mPostVersions = mTmp ; - - // The next step is to find the top level thread messages. These are defined as the messages without - // any parent message ID. - - // this trick is needed because while we remove messages, the parents a given msg may already have been removed - // and wrongly understand as a missing parent. - - std::map kept_msgs; - - for ( std::map::iterator msgIt = msgs.begin(); msgIt != msgs.end();++msgIt) - { - - if (wasStopped()) - { - deleteLater(); - return; - } - if(mFlatView || msgIt->second.mMeta.mParentId.isNull()) - { - - /* add all threads */ - if (wasStopped()) - { - deleteLater(); - return; - } - - const RsGxsForumMsg& msg = msgIt->second; - -#ifdef DEBUG_FORUMS - std::cerr << "GxsForumsFillThread::run() Adding TopLevel Thread: mId: " << msg.mMeta.mMsgId << std::endl; -#endif - - QTreeWidgetItem *item = mParent->convertMsgToThreadWidget(msg, mUseChildTS, mFilterColumn,NULL); - - if (!mFlatView) - threadStack.push_back(std::make_pair(msg.mMeta.mMsgId,item)) ; - - calculateExpand(msg, item); - - mItems.append(item); - - if (++step >= steps) { - step = 0; - emit progress(++pos, PROGRESSBAR_MAX); - } - } - else - { -#ifdef DEBUG_FORUMS - std::cerr << "GxsForumsFillThread::run() Storing kid " << msgIt->first << " of message " << msgIt->second.mMeta.mParentId << std::endl; -#endif - // The same missing parent may appear multiple times, so we first store them into a unique container. - - RsGxsMessageId parent_msg = msgIt->second.mMeta.mParentId; - - if(msgs.find(parent_msg) == msgs.end()) - { - // also check that the message is not versionned - - std::map::const_iterator mrit = most_recent_versions.find(parent_msg) ; - - if(mrit != most_recent_versions.end()) - parent_msg = mrit->second ; - else - missing_parents.insert(parent_msg); - } - - kids_array[parent_msg].push_back(msgIt->first) ; - kept_msgs.insert(*msgIt) ; - } - } - - msgs = kept_msgs; - - // Also create a list of posts by time, when they are new versions of existing posts. Only the last one will have an item created. - - // Add a fake toplevel item for the parent IDs that we dont actually have. - - for(std::set::const_iterator it(missing_parents.begin());it!=missing_parents.end();++it) - { - // add dummy parent item - QTreeWidgetItem *parent = mParent->generateMissingItem(*it); - mItems.append( parent ); - - threadStack.push_back(std::make_pair(*it,parent)) ; - } -#ifdef DEBUG_FORUMS - std::cerr << "GxsForumsFillThread::run() Processing stack:" << std::endl; -#endif - // Now use a stack to go down the hierarchy - - while (!threadStack.empty()) - { - if (wasStopped()) - { - deleteLater(); - return; - } - - std::pair threadPair = threadStack.front(); - threadStack.pop_front(); - - std::map >::iterator it = kids_array.find(threadPair.first) ; - -#ifdef DEBUG_FORUMS - std::cerr << "GxsForumsFillThread::run() Node: " << threadPair.first << std::endl; -#endif - if(it == kids_array.end()) - continue ; - - - for(std::list::const_iterator it2(it->second.begin());it2!=it->second.end();++it2) - { - if(wasStopped()) - { - deleteLater(); - return; - } - // We iterate through the top level thread items, and look for which message has the current item as parent. - // When found, the item is put in the thread list itself, as a potential new parent. - - std::map::iterator mit = msgs.find(*it2) ; - - if(mit == msgs.end()) - { - std::cerr << "GxsForumsFillThread::run() Cannot find submessage " << *it2 << " !!!" << std::endl; - continue ; - } - - const RsGxsForumMsg& msg(mit->second) ; -#ifdef DEBUG_FORUMS - std::cerr << "GxsForumsFillThread::run() adding sub_item " << msg.mMeta.mMsgId << std::endl; -#endif - - QTreeWidgetItem *item = mParent->convertMsgToThreadWidget(msg, mUseChildTS, mFilterColumn, threadPair.second); - calculateExpand(msg, item); - - /* add item to process list */ - threadStack.push_back(std::make_pair(msg.mMeta.mMsgId, item)); - - if (++step >= steps) { - step = 0; - emit progress(++pos, PROGRESSBAR_MAX); - } - - msgs.erase(mit); - } - -#ifdef DEBUG_FORUMS - std::cerr << "GxsForumsFillThread::run() Erasing entry " << it->first << " from kids tab." << std::endl; -#endif - kids_array.erase(it) ; // This is not strictly needed, but it improves performance by reducing the search space. - } - -#ifdef DEBUG_FORUMS - std::cerr << "Kids array now has " << kids_array.size() << " elements" << std::endl; - for(std::map >::const_iterator it(kids_array.begin());it!=kids_array.end();++it) - { - std::cerr << "Node " << it->first << std::endl; - for(std::list::const_iterator it2(it->second.begin());it2!=it->second.end();++it2) - std::cerr << " " << *it2 << std::endl; - } - - std::cerr << "GxsForumsFillThread::run() stopped: " << (wasStopped() ? "yes" : "no") << std::endl; -#endif - if(wasStopped()) - deleteLater(); -} - - diff --git a/retroshare-gui/src/gui/gxsforums/GxsForumsFillThread.h b/retroshare-gui/src/gui/gxsforums/GxsForumsFillThread.h deleted file mode 100644 index 76428f382..000000000 --- a/retroshare-gui/src/gui/gxsforums/GxsForumsFillThread.h +++ /dev/null @@ -1,72 +0,0 @@ -/******************************************************************************* - * retroshare-gui/src/gui/gxsforums/GxsForumsFillThread.h * - * * - * Copyright 2012 Retroshare Team * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU Affero General Public License as * - * published by the Free Software Foundation, either version 3 of the * - * License, or (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU Affero General Public License for more details. * - * * - * You should have received a copy of the GNU Affero General Public License * - * along with this program. If not, see . * - * * - *******************************************************************************/ - -#ifndef GXSFORUMSFILLTHREAD_H -#define GXSFORUMSFILLTHREAD_H - -#include -#include -#include -#include "retroshare/rsgxsifacetypes.h" - -class GxsForumThreadWidget; -class RsGxsForumMsg; -class RSTreeWidgetItemCompareRole; -class QTreeWidgetItem; - -class GxsForumsFillThread : public QThread -{ - Q_OBJECT - -public: - GxsForumsFillThread(GxsForumThreadWidget *parent); - ~GxsForumsFillThread(); - - void run(); - void stop(); - bool wasStopped() { return mStopped; } - -signals: - void progress(int current, int count); - void status(QString text); - -public: - RsGxsGroupId mForumId; - int mFilterColumn; - bool mFillComplete; - int mViewType; - bool mFlatView; - bool mUseChildTS; - bool mExpandNewMessages; - std::string mFocusMsgId; - RSTreeWidgetItemCompareRole *mCompareRole; - - QList mItems; - QList mItemToExpand; - - QMap > > mPostVersions ; -private: - void calculateExpand(const RsGxsForumMsg &msg, QTreeWidgetItem *item); - - GxsForumThreadWidget *mParent; - volatile bool mStopped; -}; - -#endif // GXSFORUMSFILLTHREAD_H diff --git a/retroshare-gui/src/retroshare-gui.pro b/retroshare-gui/src/retroshare-gui.pro index 61baf713e..86171a916 100644 --- a/retroshare-gui/src/retroshare-gui.pro +++ b/retroshare-gui/src/retroshare-gui.pro @@ -1230,7 +1230,7 @@ gxsforums { gui/gxsforums/GxsForumGroupDialog.h \ 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,7 +1244,7 @@ gxsforums { gui/gxsforums/GxsForumGroupDialog.cpp \ gui/gxsforums/CreateGxsForumMsg.cpp \ gui/gxsforums/GxsForumThreadWidget.cpp \ - gui/gxsforums/GxsForumsFillThread.cpp \ + gui/gxsforums/GxsForumModel.cpp \ gui/gxsforums/GxsForumUserNotify.cpp \ gui/feeds/GxsForumGroupItem.cpp \ gui/feeds/GxsForumMsgItem.cpp