mirror of
https://github.com/RetroShare/RetroShare.git
synced 2024-10-01 02:35:48 -04:00
Merge pull request #2091 from csoler/v0.6-BugFixing_2
[WIP] Bug fixing before 0.6.6
This commit is contained in:
commit
e88dfecc55
@ -63,6 +63,72 @@ static const uint32_t INDEX_AUTHEN_ADMIN = 0x00000040; // admin key
|
||||
|
||||
#define GEN_EXCH_DEBUG 1
|
||||
|
||||
// Data flow in RsGenExchange
|
||||
//
|
||||
// publishGroup()
|
||||
// |
|
||||
// +--- stores in mGrpsToPublish
|
||||
//
|
||||
// updateGroup()
|
||||
// |
|
||||
// +--- stores in mGroupUpdatePublish
|
||||
//
|
||||
// tick()
|
||||
// |
|
||||
// +--- processRecvdData();
|
||||
// | |
|
||||
// | +-- processRecvdGroups();
|
||||
// | | |
|
||||
// | | +---- mGroupPendingValidate
|
||||
// | | |
|
||||
// | | +---- acceptance => validateGrp() => check existance => store in mGroupUpdates
|
||||
// | | |
|
||||
// | | +--- validateNxsGrp()
|
||||
// | |
|
||||
// | +-- processRecvdMessages();
|
||||
// | |
|
||||
// | +-- performUpdateValidation(); // validates group updates in mGroupUpdates received from friends
|
||||
// | |
|
||||
// | +---- mGroupUpdates => updateValid() => store new groups
|
||||
// | | |
|
||||
// | | +--- stamp keys, check admin keys are the same, validateNxsGrp()
|
||||
// | | |
|
||||
// | +---- stores RsGxsGroupChange in mNotifications +--- data signature check
|
||||
// |
|
||||
// +--- publishGrps(); // creates signatures and data for locally created groups
|
||||
// | |
|
||||
// | +-- reads mGrpsToPublish, creates the group, clears mGrpsToPublish
|
||||
// | |
|
||||
// | +--- generateGroupKeys()
|
||||
// | |
|
||||
// | +--- serviceCreateGroup()
|
||||
// | |
|
||||
// | +--- createGroup()
|
||||
// | |
|
||||
// | +--- createGroupSignatures()
|
||||
// |
|
||||
// +--- processGroupUpdatePublish(); // goes through list of group updates to perform locally
|
||||
// | |
|
||||
// | +-- reads/deletes mGroupUpdatePublish, stores in mGrpsToPublish
|
||||
// |
|
||||
// +--- processGrpMetaChanges();
|
||||
// | |
|
||||
// | +-- updates mNotifications, calls mDataAccess
|
||||
// |
|
||||
// +--- processGroupDelete();
|
||||
// |
|
||||
// +--- publishMsgs();
|
||||
// |
|
||||
// +--- processMsgMetaChanges();
|
||||
// | |
|
||||
// | +-- updates mNotifications, calls mDataAccess
|
||||
// |
|
||||
// +--- processMessageDelete();
|
||||
// |
|
||||
// +--- mDataAccess->processRequests();
|
||||
// |
|
||||
// +--- processRoutingClues() ;
|
||||
//
|
||||
static const uint32_t MSG_CLEANUP_PERIOD = 60*59; // 59 minutes
|
||||
static const uint32_t INTEGRITY_CHECK_PERIOD = 60*31; // 31 minutes
|
||||
|
||||
@ -2596,8 +2662,17 @@ void RsGenExchange::publishGrps()
|
||||
RS_STACK_MUTEX(mGenMtx) ;
|
||||
NxsGrpSignPendVect::iterator vit = mGrpsToPublish.begin();
|
||||
|
||||
typedef std::pair<bool, RsGxsGroupId> GrpNote;
|
||||
std::map<uint32_t, GrpNote> toNotify;
|
||||
typedef struct _GrpNote {
|
||||
|
||||
_GrpNote(bool success,bool is_update,const RsGxsGroupId& gid) : mSuccess(success),mIsUpdate(is_update),mGroupId(gid){}
|
||||
|
||||
bool mSuccess;
|
||||
bool mIsUpdate;
|
||||
RsGxsGroupId mGroupId;
|
||||
|
||||
} GrpNote;
|
||||
|
||||
std::map<uint32_t, GrpNote> toNotify; // used to notify about token processing if success or fails because of timeout
|
||||
|
||||
while( vit != mGrpsToPublish.end() )
|
||||
{
|
||||
@ -2611,8 +2686,7 @@ void RsGenExchange::publishGrps()
|
||||
if(now > (ggps.mStartTS + PENDING_SIGN_TIMEOUT) )
|
||||
{
|
||||
// timed out
|
||||
toNotify.insert(std::make_pair(
|
||||
token, GrpNote(false, RsGxsGroupId())));
|
||||
toNotify.insert(std::make_pair( token, GrpNote(false,ggps.mIsUpdate, RsGxsGroupId())));
|
||||
delete ggps.mItem;
|
||||
vit = mGrpsToPublish.erase(vit);
|
||||
|
||||
@ -2723,12 +2797,36 @@ void RsGenExchange::publishGrps()
|
||||
computeHash(grp->grp, grp->metaData->mHash);
|
||||
grp->metaData->mRecvTS = time(NULL);
|
||||
|
||||
if(ggps.mIsUpdate)
|
||||
mDataAccess->updateGroupData(grp);
|
||||
else
|
||||
mDataAccess->addGroupData(grp);
|
||||
// Also push in notifications. We do it here when we still have pointers on the old and new groups
|
||||
|
||||
delete grp ;
|
||||
RsGxsGroupChange *c = new RsGxsGroupChange(ggps.mIsUpdate?RsGxsNotify::TYPE_UPDATED:RsGxsNotify::TYPE_PUBLISHED, grpId,false);
|
||||
|
||||
c->mNewGroupItem = dynamic_cast<RsGxsGrpItem*>(mSerialiser->deserialise(grp->grp.bin_data,&grp->grp.bin_len));
|
||||
c->mNewGroupItem->meta = *grp->metaData; // grp will be deleted because mDataStore will destroy it on update
|
||||
|
||||
if(ggps.mIsUpdate)
|
||||
{
|
||||
RsNxsGrpDataTemporaryMap oldGrpDatas;
|
||||
oldGrpDatas.insert(std::make_pair(grpId, (RsNxsGrp*)NULL));
|
||||
|
||||
if(mDataStore->retrieveNxsGrps(oldGrpDatas,true,false) && oldGrpDatas.size() == 1)
|
||||
{
|
||||
auto oldGrp = oldGrpDatas[grpId];
|
||||
c->mOldGroupItem = dynamic_cast<RsGxsGrpItem*>(mSerialiser->deserialise(oldGrp->grp.bin_data,&oldGrp->grp.bin_len));
|
||||
c->mOldGroupItem->meta = *oldGrp->metaData;
|
||||
}
|
||||
}
|
||||
|
||||
mNotifications.push_back(c);
|
||||
|
||||
// now store (and incidently delete grp)
|
||||
|
||||
if(ggps.mIsUpdate)
|
||||
mDataAccess->updateGroupData(grp);
|
||||
else
|
||||
mDataAccess->addGroupData(grp);
|
||||
|
||||
delete grp ;
|
||||
groups_to_subscribe.push_back(grpId) ;
|
||||
}
|
||||
else
|
||||
@ -2744,7 +2842,7 @@ void RsGenExchange::publishGrps()
|
||||
// services should return SERVICE_CREATE_FAIL if the action timed out
|
||||
// at the moment this is only important for the idservice:
|
||||
// the idservice may ask the user for a password, and the user needs time
|
||||
ggps.mStartTS = now;
|
||||
ggps.mStartTS = now;
|
||||
create = CREATE_FAIL_TRY_LATER;
|
||||
}
|
||||
else if(ret == SERVICE_CREATE_FAIL)
|
||||
@ -2766,8 +2864,7 @@ void RsGenExchange::publishGrps()
|
||||
delete grp;
|
||||
delete grpItem;
|
||||
vit = mGrpsToPublish.erase(vit);
|
||||
toNotify.insert(std::make_pair(
|
||||
token, GrpNote(false, grpId)));
|
||||
toNotify.insert(std::make_pair(token, GrpNote(false, ggps.mIsUpdate,grpId)));
|
||||
|
||||
}
|
||||
else if(create == CREATE_FAIL_TRY_LATER)
|
||||
@ -2790,30 +2887,21 @@ void RsGenExchange::publishGrps()
|
||||
#endif
|
||||
|
||||
// add to published to allow acknowledgement
|
||||
toNotify.insert(std::make_pair(token,
|
||||
GrpNote(true,grpId)));
|
||||
toNotify.insert(std::make_pair(token, GrpNote(true,ggps.mIsUpdate,grpId)));
|
||||
}
|
||||
}
|
||||
|
||||
std::map<uint32_t, GrpNote>::iterator mit = toNotify.begin();
|
||||
// toNotify contains the token and the group update/creation info. We parse it to
|
||||
// - notify that group creation/update for a specific token was
|
||||
|
||||
std::list<RsGxsGroupId> grpChanged;
|
||||
for(; mit != toNotify.end(); ++mit)
|
||||
for(auto mit=toNotify.begin(); mit != toNotify.end(); ++mit)
|
||||
{
|
||||
GrpNote& note = mit->second;
|
||||
RsTokenService::GxsRequestStatus status =
|
||||
note.first ? RsTokenService::COMPLETE
|
||||
: RsTokenService::FAILED;
|
||||
RsTokenService::GxsRequestStatus status = note.mSuccess ? RsTokenService::COMPLETE : RsTokenService::FAILED;
|
||||
|
||||
mGrpNotify.insert(std::make_pair(mit->first, note.second));
|
||||
mDataAccess->updatePublicRequestStatus(mit->first, status);
|
||||
|
||||
if(note.first)
|
||||
grpChanged.push_back(note.second);
|
||||
}
|
||||
|
||||
for(auto& groupId:grpChanged)
|
||||
mNotifications.push_back(new RsGxsGroupChange(RsGxsNotify::TYPE_RECEIVED_NEW,groupId, true));
|
||||
mGrpNotify.insert(std::make_pair(mit->first, note.mGroupId)); // always notify
|
||||
mDataAccess->updatePublicRequestStatus(mit->first, status); // update the token request with the given status
|
||||
}
|
||||
}
|
||||
|
||||
// This is done off-mutex to avoid possible cross deadlocks with the net service.
|
||||
@ -2823,8 +2911,6 @@ void RsGenExchange::publishGrps()
|
||||
mNetService->subscribeStatusChanged((*it),true) ;
|
||||
}
|
||||
|
||||
|
||||
|
||||
uint32_t RsGenExchange::generatePublicToken()
|
||||
{
|
||||
return mDataAccess->generatePublicToken();
|
||||
@ -3082,7 +3168,7 @@ void RsGenExchange::processRecvdMessages()
|
||||
mNotifications.push_back(c);
|
||||
}
|
||||
|
||||
mDataStore->storeMessage(msgs_to_store); // we do that late because it destroys the items in msgs_to_store
|
||||
mDataStore->storeMessage(msgs_to_store); // All items will be destroyed later on, since msgs_to_store is a temporary map
|
||||
}
|
||||
}
|
||||
|
||||
@ -3224,9 +3310,9 @@ void RsGenExchange::processRecvdGroups()
|
||||
|
||||
mDataStore->storeGroup(grps_to_store);
|
||||
#ifdef GEN_EXCH_DEBUG
|
||||
std::cerr << " adding the following grp ids to notification: " << std::endl;
|
||||
for(std::list<RsGxsGroupId>::const_iterator it(grpIds.begin());it!=grpIds.end();++it)
|
||||
std::cerr << " " << *it << std::endl;
|
||||
std::cerr << " adding the following grp ids to notification: " << std::endl;
|
||||
for(std::list<RsGxsGroupId>::const_iterator it(grpIds.begin());it!=grpIds.end();++it)
|
||||
std::cerr << " " << *it << std::endl;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@ -3317,8 +3403,7 @@ void RsGenExchange::performUpdateValidation()
|
||||
}
|
||||
}
|
||||
|
||||
// Warning: updateGroup will destroy the objects in grps. Dont use it afterwards!
|
||||
mDataStore->updateGroup(grps);
|
||||
mDataStore->updateGroup(grps);
|
||||
|
||||
#ifdef GEN_EXCH_DEBUG
|
||||
std::cerr << " adding the following grp ids to notification: " << std::endl;
|
||||
|
@ -71,14 +71,15 @@ struct RsGxsChannelGroup : RsSerializable, RsGxsGenericGroupData
|
||||
|
||||
struct RsGxsChannelPost : RsSerializable, RsGxsGenericMsgData
|
||||
{
|
||||
RsGxsChannelPost() : mCount(0), mSize(0) {}
|
||||
RsGxsChannelPost() : mAttachmentCount(0), mCommentCount(0), mSize(0) {}
|
||||
|
||||
std::set<RsGxsMessageId> mOlderVersions;
|
||||
std::string mMsg; // UTF8 encoded.
|
||||
|
||||
std::list<RsGxsFile> mFiles;
|
||||
uint32_t mCount; // auto calced.
|
||||
uint64_t mSize; // auto calced.
|
||||
uint32_t mAttachmentCount; // auto calced.
|
||||
uint32_t mCommentCount; // auto calced. WARNING: not computed yet. In the future we need a background process to update this and store in the service string.
|
||||
uint64_t mSize; // auto calced.
|
||||
|
||||
RsGxsImage mThumbnail;
|
||||
|
||||
@ -92,8 +93,9 @@ struct RsGxsChannelPost : RsSerializable, RsGxsGenericMsgData
|
||||
|
||||
RS_SERIAL_PROCESS(mMsg);
|
||||
RS_SERIAL_PROCESS(mFiles);
|
||||
RS_SERIAL_PROCESS(mCount);
|
||||
RS_SERIAL_PROCESS(mSize);
|
||||
RS_SERIAL_PROCESS(mAttachmentCount);
|
||||
RS_SERIAL_PROCESS(mCommentCount);
|
||||
RS_SERIAL_PROCESS(mSize);
|
||||
RS_SERIAL_PROCESS(mThumbnail);
|
||||
}
|
||||
|
||||
@ -114,6 +116,8 @@ enum class RsChannelEventCode: uint8_t
|
||||
RECEIVED_DISTANT_SEARCH_RESULT = 0x08, // result for the given group id available for the given turtle request id
|
||||
STATISTICS_CHANGED = 0x09, // stats (nb of supplier friends, how many msgs they have etc) has changed
|
||||
SYNC_PARAMETERS_UPDATED = 0x0a, // sync and storage times have changed
|
||||
NEW_COMMENT = 0x0b, // new comment arrived/published. mChannelThreadId gives the ID of the commented message
|
||||
NEW_VOTE = 0x0c, // new vote arrived/published. mChannelThreadId gives the ID of the votes message comment
|
||||
};
|
||||
|
||||
struct RsGxsChannelEvent: RsEvent
|
||||
@ -123,6 +127,7 @@ struct RsGxsChannelEvent: RsEvent
|
||||
RsChannelEventCode mChannelEventCode;
|
||||
RsGxsGroupId mChannelGroupId;
|
||||
RsGxsMessageId mChannelMsgId;
|
||||
RsGxsMessageId mChannelThreadId;
|
||||
|
||||
///* @see RsEvent @see RsSerializable
|
||||
void serial_process( RsGenericSerializer::SerializeJob j,RsGenericSerializer::SerializeContext& ctx) override
|
||||
@ -323,8 +328,11 @@ public:
|
||||
std::vector<RsGxsChannelGroup>& channelsInfo ) = 0;
|
||||
|
||||
/**
|
||||
* @brief Get all channel messages and comments in a given channel
|
||||
* @jsonapi{development}
|
||||
* @brief Get all channel messages, comments and votes in a given channel
|
||||
* @note It's the client's responsibility to figure out which message (resp. comment)
|
||||
* a comment (resp. vote) refers to.
|
||||
*
|
||||
* @jsonapi{development}
|
||||
* @param[in] channelId id of the channel of which the content is requested
|
||||
* @param[out] posts storage for posts
|
||||
* @param[out] comments storage for the comments
|
||||
@ -337,11 +345,13 @@ public:
|
||||
std::vector<RsGxsVote>& votes ) = 0;
|
||||
|
||||
/**
|
||||
* @brief Get channel messages and comments corresponding to the given IDs.
|
||||
* If the set is empty, nothing is returned.
|
||||
* @note Since comments are internally themselves messages, it is possible
|
||||
* to get comments only by supplying their IDs.
|
||||
* @jsonapi{development}
|
||||
* @brief Get channel messages, comments and votes corresponding to the given IDs.
|
||||
* @note Since comments are internally themselves messages, this function actually
|
||||
* returns the data for messages, comments or votes that have the given ID.
|
||||
* It *does not* automatically retrieve the comments or votes for a given message
|
||||
* which Id you supplied.
|
||||
*
|
||||
* @jsonapi{development}
|
||||
* @param[in] channelId id of the channel of which the content is requested
|
||||
* @param[in] contentsIds ids of requested contents
|
||||
* @param[out] posts storage for posts
|
||||
@ -356,8 +366,9 @@ public:
|
||||
std::vector<RsGxsVote>& votes ) = 0;
|
||||
|
||||
/**
|
||||
* @brief Get channel comments corresponding to the given IDs.
|
||||
* @brief Get channel comments corresponding to the given message IDs.
|
||||
* If the set is empty, nothing is returned.
|
||||
*
|
||||
* @jsonapi{development}
|
||||
* @param[in] channelId id of the channel of which the content is requested
|
||||
* @param[in] contentIds ids of requested contents
|
||||
|
@ -116,6 +116,7 @@ enum class RsForumEventCode: uint8_t
|
||||
STATISTICS_CHANGED = 0x07, /// suppliers and how many messages they have changed
|
||||
MODERATOR_LIST_CHANGED = 0x08, /// forum moderation list has changed.
|
||||
SYNC_PARAMETERS_UPDATED = 0x0a, /// sync and storage times have changed
|
||||
PINNED_POSTS_CHANGED = 0x0b, /// some posts where pinned or un-pinned
|
||||
};
|
||||
|
||||
struct RsGxsForumEvent: RsEvent
|
||||
|
@ -117,6 +117,8 @@ enum class RsPostedEventCode: uint8_t
|
||||
STATISTICS_CHANGED = 0x07,
|
||||
MESSAGE_VOTES_UPDATED = 0x08,
|
||||
SYNC_PARAMETERS_UPDATED = 0x09,
|
||||
NEW_COMMENT = 0x0a,
|
||||
NEW_VOTE = 0x0b,
|
||||
};
|
||||
|
||||
|
||||
|
@ -137,7 +137,7 @@ bool RsGxsChannelPostItem::toChannelPost(RsGxsChannelPost &post, bool moveImage)
|
||||
post.mThumbnail.copy((uint8_t *) mThumbnail.binData.bin_data, mThumbnail.binData.bin_len);
|
||||
}
|
||||
|
||||
post.mCount = 0;
|
||||
post.mAttachmentCount = 0;
|
||||
post.mSize = 0;
|
||||
std::list<RsTlvFileItem>::iterator fit;
|
||||
for(fit = mAttachment.items.begin(); fit != mAttachment.items.end(); ++fit)
|
||||
@ -148,7 +148,7 @@ bool RsGxsChannelPostItem::toChannelPost(RsGxsChannelPost &post, bool moveImage)
|
||||
fi.mHash = fit->hash;
|
||||
|
||||
post.mFiles.push_back(fi);
|
||||
post.mCount++;
|
||||
post.mAttachmentCount++;
|
||||
post.mSize += fi.mSize;
|
||||
}
|
||||
return true;
|
||||
|
@ -259,7 +259,15 @@ void p3GxsChannels::notifyChanges(std::vector<RsGxsNotify *> &changes)
|
||||
|
||||
ev->mChannelMsgId = msgChange->mMsgId;
|
||||
ev->mChannelGroupId = msgChange->mGroupId;
|
||||
ev->mChannelEventCode = RsChannelEventCode::NEW_MESSAGE;
|
||||
|
||||
if(nullptr != dynamic_cast<RsGxsCommentItem*>(msgChange->mNewMsgItem))
|
||||
ev->mChannelEventCode = RsChannelEventCode::NEW_COMMENT;
|
||||
else
|
||||
if(nullptr != dynamic_cast<RsGxsVoteItem*>(msgChange->mNewMsgItem))
|
||||
ev->mChannelEventCode = RsChannelEventCode::NEW_VOTE;
|
||||
else
|
||||
ev->mChannelEventCode = RsChannelEventCode::NEW_MESSAGE;
|
||||
|
||||
rsEvents->postEvent(ev);
|
||||
}
|
||||
}
|
||||
@ -477,10 +485,9 @@ bool p3GxsChannels::groupShareKeys(
|
||||
* at the moment - fix it up later
|
||||
*/
|
||||
|
||||
bool p3GxsChannels::getPostData(
|
||||
const uint32_t &token, std::vector<RsGxsChannelPost> &msgs,
|
||||
std::vector<RsGxsComment> &cmts,
|
||||
std::vector<RsGxsVote> &vots)
|
||||
bool p3GxsChannels::getPostData( const uint32_t& token, std::vector<RsGxsChannelPost>& msgs,
|
||||
std::vector<RsGxsComment>& cmts,
|
||||
std::vector<RsGxsVote>& vots)
|
||||
{
|
||||
#ifdef GXSCHANNELS_DEBUG
|
||||
RsDbg() << __PRETTY_FUNCTION__ << std::endl;
|
||||
@ -675,7 +682,7 @@ bool p3GxsChannels::setChannelDownloadDirectory(
|
||||
}
|
||||
|
||||
/* extract from ServiceString */
|
||||
SSGxsChannelGroup ss;
|
||||
GxsChannelGroupInfo ss;
|
||||
ss.load(it->second.mServiceString);
|
||||
|
||||
if (directory == ss.mDownloadDirectory)
|
||||
@ -728,7 +735,7 @@ bool p3GxsChannels::getChannelDownloadDirectory(const RsGxsGroupId & groupId,std
|
||||
}
|
||||
|
||||
/* extract from ServiceString */
|
||||
SSGxsChannelGroup ss;
|
||||
GxsChannelGroupInfo ss;
|
||||
ss.load(it->second.mServiceString);
|
||||
directory = ss.mDownloadDirectory;
|
||||
|
||||
@ -1729,14 +1736,14 @@ bool p3GxsChannels::autoDownloadEnabled(const RsGxsGroupId &groupId,bool& enable
|
||||
}
|
||||
|
||||
/* extract from ServiceString */
|
||||
SSGxsChannelGroup ss;
|
||||
GxsChannelGroupInfo ss;
|
||||
ss.load(it->second.mServiceString);
|
||||
enabled = ss.mAutoDownload;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SSGxsChannelGroup::load(const std::string &input)
|
||||
bool GxsChannelGroupInfo::load(const std::string &input)
|
||||
{
|
||||
if(input.empty())
|
||||
{
|
||||
@ -1785,7 +1792,7 @@ bool SSGxsChannelGroup::load(const std::string &input)
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string SSGxsChannelGroup::save() const
|
||||
std::string GxsChannelGroupInfo::save() const
|
||||
{
|
||||
std::string output = "v2 ";
|
||||
|
||||
@ -1827,7 +1834,7 @@ bool p3GxsChannels::setAutoDownload(const RsGxsGroupId& groupId, bool enabled)
|
||||
}
|
||||
|
||||
/* extract from ServiceString */
|
||||
SSGxsChannelGroup ss;
|
||||
GxsChannelGroupInfo ss;
|
||||
ss.load(it->second.mServiceString);
|
||||
if (enabled == ss.mAutoDownload)
|
||||
{
|
||||
|
@ -35,11 +35,12 @@
|
||||
#include <string>
|
||||
|
||||
|
||||
// This class is only a helper to parse the channel group service string.
|
||||
|
||||
class SSGxsChannelGroup
|
||||
class GxsChannelGroupInfo
|
||||
{
|
||||
public:
|
||||
SSGxsChannelGroup(): mAutoDownload(false), mDownloadDirectory("") {}
|
||||
GxsChannelGroupInfo(): mAutoDownload(false), mDownloadDirectory("") {}
|
||||
bool load(const std::string &input);
|
||||
std::string save() const;
|
||||
|
||||
@ -55,31 +56,31 @@ class p3GxsChannels: public RsGenExchange, public RsGxsChannels,
|
||||
public:
|
||||
p3GxsChannels( RsGeneralDataService* gds, RsNetworkExchangeService* nes,
|
||||
RsGixs* gixs );
|
||||
virtual RsServiceInfo getServiceInfo();
|
||||
virtual RsServiceInfo getServiceInfo() override;
|
||||
|
||||
virtual void service_tick();
|
||||
virtual void service_tick() override;
|
||||
|
||||
protected:
|
||||
|
||||
|
||||
virtual RsSerialiser* setupSerialiser(); // @see p3Config::setupSerialiser()
|
||||
virtual bool saveList(bool &cleanup, std::list<RsItem *>&saveList); // @see p3Config::saveList(bool &cleanup, std::list<RsItem *>&)
|
||||
virtual bool loadList(std::list<RsItem *>& loadList); // @see p3Config::loadList(std::list<RsItem *>&)
|
||||
virtual RsSerialiser* setupSerialiser() override; // @see p3Config::setupSerialiser()
|
||||
virtual bool saveList(bool &cleanup, std::list<RsItem *>&saveList) override; // @see p3Config::saveList(bool &cleanup, std::list<RsItem *>&)
|
||||
virtual bool loadList(std::list<RsItem *>& loadList) override; // @see p3Config::loadList(std::list<RsItem *>&)
|
||||
|
||||
virtual TurtleRequestId turtleGroupRequest(const RsGxsGroupId& group_id);
|
||||
virtual TurtleRequestId turtleSearchRequest(const std::string& match_string);
|
||||
virtual bool retrieveDistantSearchResults(TurtleRequestId req, std::map<RsGxsGroupId, RsGxsGroupSearchResults> &results) ;
|
||||
virtual bool clearDistantSearchResults(TurtleRequestId req);
|
||||
virtual bool getDistantSearchResultGroupData(const RsGxsGroupId& group_id,RsGxsChannelGroup& distant_group);
|
||||
virtual DistantSearchGroupStatus getDistantSearchStatus(const RsGxsGroupId& group_id) ;
|
||||
virtual TurtleRequestId turtleGroupRequest(const RsGxsGroupId& group_id) override;
|
||||
virtual TurtleRequestId turtleSearchRequest(const std::string& match_string) override;
|
||||
virtual bool retrieveDistantSearchResults(TurtleRequestId req, std::map<RsGxsGroupId, RsGxsGroupSearchResults> &results) override;
|
||||
virtual bool clearDistantSearchResults(TurtleRequestId req) override;
|
||||
virtual bool getDistantSearchResultGroupData(const RsGxsGroupId& group_id,RsGxsChannelGroup& distant_group) override;
|
||||
virtual DistantSearchGroupStatus getDistantSearchStatus(const RsGxsGroupId& group_id) override;
|
||||
|
||||
// Overloaded to cache new groups.
|
||||
virtual RsGenExchange::ServiceCreate_Return service_CreateGroup(RsGxsGrpItem* grpItem, RsTlvSecurityKeySet& keySet);
|
||||
virtual RsGenExchange::ServiceCreate_Return service_CreateGroup(RsGxsGrpItem* grpItem, RsTlvSecurityKeySet& keySet) override;
|
||||
|
||||
virtual void notifyChanges(std::vector<RsGxsNotify*>& changes);
|
||||
virtual void notifyChanges(std::vector<RsGxsNotify*>& changes) override;
|
||||
|
||||
// Overloaded from RsTickEvent.
|
||||
virtual void handle_event(uint32_t event_type, const std::string &elabel);
|
||||
virtual void handle_event(uint32_t event_type, const std::string &elabel) override;
|
||||
|
||||
public:
|
||||
|
||||
@ -97,18 +98,18 @@ virtual bool getPostData(const uint32_t &token, std::vector<RsGxsChannelPost> &p
|
||||
|
||||
//virtual bool groupRestoreKeys(const std::string &groupId);
|
||||
virtual bool groupShareKeys(
|
||||
const RsGxsGroupId &groupId, const std::set<RsPeerId>& peers);
|
||||
const RsGxsGroupId &groupId, const std::set<RsPeerId>& peers) override;
|
||||
|
||||
virtual bool createGroup(uint32_t &token, RsGxsChannelGroup &group);
|
||||
virtual bool createPost(uint32_t &token, RsGxsChannelPost &post);
|
||||
virtual bool createGroup(uint32_t &token, RsGxsChannelGroup &group) override;
|
||||
virtual bool createPost(uint32_t &token, RsGxsChannelPost &post) override;
|
||||
|
||||
virtual bool updateGroup(uint32_t &token, RsGxsChannelGroup &group);
|
||||
virtual bool updateGroup(uint32_t &token, RsGxsChannelGroup &group) override;
|
||||
|
||||
// no tokens... should be cached.
|
||||
virtual bool setChannelAutoDownload(const RsGxsGroupId &groupId, bool enabled);
|
||||
virtual bool getChannelAutoDownload(const RsGxsGroupId &groupid, bool& enabled);
|
||||
virtual bool setChannelDownloadDirectory(const RsGxsGroupId &groupId, const std::string& directory);
|
||||
virtual bool getChannelDownloadDirectory(const RsGxsGroupId &groupId, std::string& directory);
|
||||
virtual bool setChannelAutoDownload(const RsGxsGroupId &groupId, bool enabled) override;
|
||||
virtual bool getChannelAutoDownload(const RsGxsGroupId &groupid, bool& enabled) override;
|
||||
virtual bool setChannelDownloadDirectory(const RsGxsGroupId &groupId, const std::string& directory) override;
|
||||
virtual bool getChannelDownloadDirectory(const RsGxsGroupId &groupId, std::string& directory) override;
|
||||
|
||||
#ifdef TO_REMOVE
|
||||
/// @see RsGxsChannels::turtleSearchRequest
|
||||
|
@ -683,8 +683,6 @@ void p3GxsCircles::notifyChanges(std::vector<RsGxsNotify *> &changes)
|
||||
RsGxsCircleGroupItem *old_circle_grp_item = dynamic_cast<RsGxsCircleGroupItem*>(groupChange->mOldGroupItem);
|
||||
RsGxsCircleGroupItem *new_circle_grp_item = dynamic_cast<RsGxsCircleGroupItem*>(groupChange->mNewGroupItem);
|
||||
|
||||
const RsGxsCircleId circle_id ( old_circle_grp_item->meta.mGroupId );
|
||||
|
||||
if(old_circle_grp_item == nullptr || new_circle_grp_item == nullptr)
|
||||
{
|
||||
RsErr() << __PRETTY_FUNCTION__ << " received GxsGroupUpdate item with mOldGroup and mNewGroup not of type RsGxsCircleGroupItem. This is inconsistent!" << std::endl;
|
||||
@ -692,7 +690,9 @@ void p3GxsCircles::notifyChanges(std::vector<RsGxsNotify *> &changes)
|
||||
continue;
|
||||
}
|
||||
|
||||
// First of all, we check if there is a difference between the old and new list of invited members
|
||||
const RsGxsCircleId circle_id ( old_circle_grp_item->meta.mGroupId );
|
||||
|
||||
// First of all, we check if there is a difference between the old and new list of invited members
|
||||
|
||||
for(auto& gxs_id: new_circle_grp_item->gxsIdSet.ids)
|
||||
if(old_circle_grp_item->gxsIdSet.ids.find(gxs_id) == old_circle_grp_item->gxsIdSet.ids.end())
|
||||
|
@ -307,7 +307,7 @@ void p3GxsForums::notifyChanges(std::vector<RsGxsNotify *> &changes)
|
||||
|
||||
if(old_forum_grp_item == nullptr || new_forum_grp_item == nullptr)
|
||||
{
|
||||
RsErr() << __PRETTY_FUNCTION__ << " received GxsGroupUpdate item with mOldGroup and mNewGroup not of type RsGxsForumGroupItem. This is inconsistent!" << std::endl;
|
||||
RsErr() << __PRETTY_FUNCTION__ << " received GxsGroupUpdate item with mOldGroup and mNewGroup not of type RsGxsForumGroupItem or NULL. This is inconsistent!" << std::endl;
|
||||
delete grpChange;
|
||||
continue;
|
||||
}
|
||||
@ -335,6 +335,29 @@ void p3GxsForums::notifyChanges(std::vector<RsGxsNotify *> &changes)
|
||||
|
||||
rsEvents->postEvent(ev);
|
||||
}
|
||||
|
||||
// check the list of pinned posts
|
||||
|
||||
std::list<RsGxsMessageId> added_pins, removed_pins;
|
||||
|
||||
for(auto& msg_id: new_forum_grp_item->mGroup.mPinnedPosts.ids)
|
||||
if(old_forum_grp_item->mGroup.mPinnedPosts.ids.find(msg_id) == old_forum_grp_item->mGroup.mPinnedPosts.ids.end())
|
||||
added_pins.push_back(msg_id);
|
||||
|
||||
for(auto& msg_id: old_forum_grp_item->mGroup.mPinnedPosts.ids)
|
||||
if(new_forum_grp_item->mGroup.mPinnedPosts.ids.find(msg_id) == new_forum_grp_item->mGroup.mPinnedPosts.ids.end())
|
||||
removed_pins.push_back(msg_id);
|
||||
|
||||
if(!added_pins.empty() || !removed_pins.empty())
|
||||
{
|
||||
auto ev = std::make_shared<RsGxsForumEvent>();
|
||||
|
||||
ev->mForumGroupId = new_forum_grp_item->meta.mGroupId;
|
||||
ev->mForumEventCode = RsForumEventCode::PINNED_POSTS_CHANGED;
|
||||
|
||||
rsEvents->postEvent(ev);
|
||||
}
|
||||
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -107,10 +107,18 @@ void p3PostBase::notifyChanges(std::vector<RsGxsNotify *> &changes)
|
||||
case RsGxsNotify::TYPE_PUBLISHED:
|
||||
{
|
||||
auto ev = std::make_shared<RsGxsPostedEvent>();
|
||||
ev->mPostedMsgId = msgChange->mMsgId;
|
||||
ev->mPostedMsgId = msgChange->mMsgId;
|
||||
ev->mPostedThreadId = msgChange->mNewMsgItem->meta.mThreadId;
|
||||
ev->mPostedGroupId = msgChange->mGroupId;
|
||||
ev->mPostedEventCode = RsPostedEventCode::NEW_MESSAGE;
|
||||
ev->mPostedGroupId = msgChange->mGroupId;
|
||||
|
||||
if(nullptr != dynamic_cast<RsGxsCommentItem*>(msgChange->mNewMsgItem))
|
||||
ev->mPostedEventCode = RsPostedEventCode::NEW_COMMENT;
|
||||
else
|
||||
if(nullptr != dynamic_cast<RsGxsVoteItem*>(msgChange->mNewMsgItem))
|
||||
ev->mPostedEventCode = RsPostedEventCode::NEW_VOTE;
|
||||
else
|
||||
ev->mPostedEventCode = RsPostedEventCode::NEW_MESSAGE;
|
||||
|
||||
rsEvents->postEvent(ev);
|
||||
#ifdef POSTBASE_DEBUG
|
||||
std::cerr << "p3PostBase::notifyChanges() Found Message Change Notification: NEW/PUBLISHED ID=" << msgChange->mMsgId << " in group " << msgChange->mGroupId << ", thread ID = " << msgChange->mNewMsgItem->meta.mThreadId << std::endl;
|
||||
|
@ -146,7 +146,7 @@ void PostedPostDelegate::paint(QPainter * painter, const QStyleOptionViewItem &
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
QSize PostedPostDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const
|
||||
QSize PostedPostDelegate::sizeHint(const QStyleOptionViewItem&, const QModelIndex& index) const
|
||||
{
|
||||
// This is the only place where we actually set the size of cells
|
||||
|
||||
@ -474,7 +474,8 @@ void PostedListWidgetWithModel::handleEvent_main_thread(std::shared_ptr<const Rs
|
||||
|
||||
switch(e->mPostedEventCode)
|
||||
{
|
||||
case RsPostedEventCode::NEW_MESSAGE: // [[fallthrough]];
|
||||
case RsPostedEventCode::NEW_COMMENT: // [[fallthrough]];
|
||||
case RsPostedEventCode::NEW_VOTE: // [[fallthrough]];
|
||||
{
|
||||
// special treatment here because the message might be a comment, so we need to refresh the comment tab if openned
|
||||
|
||||
@ -486,6 +487,7 @@ void PostedListWidgetWithModel::handleEvent_main_thread(std::shared_ptr<const Rs
|
||||
t->refresh();
|
||||
}
|
||||
}
|
||||
case RsPostedEventCode::NEW_MESSAGE: // [[fallthrough]];
|
||||
case RsPostedEventCode::NEW_POSTED_GROUP: // [[fallthrough]];
|
||||
case RsPostedEventCode::UPDATED_POSTED_GROUP: // [[fallthrough]];
|
||||
case RsPostedEventCode::UPDATED_MESSAGE:
|
||||
@ -877,8 +879,10 @@ void PostedListWidgetWithModel::insertBoardDetails(const RsPostedGroup& group)
|
||||
sync_string = tr("Unknown");
|
||||
}
|
||||
|
||||
if(group.mMeta.mLastPost > 0 && group.mMeta.mLastPost + rsPosted->getSyncPeriod(group.mMeta.mGroupId) < time(NULL) && IS_GROUP_SUBSCRIBED(group.mMeta.mSubscribeFlags))
|
||||
sync_string += " (Warning: will not allow latest posts to sync)";
|
||||
auto sync_period = rsPosted->getSyncPeriod(group.mMeta.mGroupId) ;
|
||||
|
||||
if(sync_period > 0 && group.mMeta.mLastPost > 0 && group.mMeta.mLastPost + sync_period < time(NULL) && IS_GROUP_SUBSCRIBED(group.mMeta.mSubscribeFlags))
|
||||
sync_string += " (Warning: will not allow posts to sync)";
|
||||
|
||||
ui->syncPeriodLabel->setText(sync_string);
|
||||
|
||||
|
@ -589,6 +589,23 @@ p, li { white-space: pre-wrap; }
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>LineEditClear</class>
|
||||
<extends>QLineEdit</extends>
|
||||
<header location="global">gui/common/LineEditClear.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>RSTabWidget</class>
|
||||
<extends>QTabWidget</extends>
|
||||
<header>gui/common/RSTabWidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>RSTreeView</class>
|
||||
<extends>QTreeView</extends>
|
||||
<header>gui/common/RSTreeView.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>GxsIdLabel</class>
|
||||
<extends>QLabel</extends>
|
||||
@ -599,27 +616,11 @@ p, li { white-space: pre-wrap; }
|
||||
<extends>QToolButton</extends>
|
||||
<header>gui/common/SubscribeToolButton.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>RSTreeView</class>
|
||||
<extends>QTreeView</extends>
|
||||
<header>gui/common/RSTreeView.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>LineEditClear</class>
|
||||
<extends>QLineEdit</extends>
|
||||
<header>gui/common/LineEditClear.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>GxsIdChooser</class>
|
||||
<extends>QComboBox</extends>
|
||||
<header>gui/gxs/GxsIdChooser.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>RSTabWidget</class>
|
||||
<extends>QTabWidget</extends>
|
||||
<header>gui/common/RSTabWidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources>
|
||||
<include location="../icons.qrc"/>
|
||||
|
@ -578,10 +578,7 @@ void NewFriendList::peerTreeWidgetCustomPopupMenu()
|
||||
mModel->getGroupData(index,group_info);
|
||||
|
||||
bool standard = group_info.flag & RS_GROUP_FLAG_STANDARD;
|
||||
#ifdef RS_DIRECT_CHAT
|
||||
contextMenu.addAction(FilesDefs::getIconFromQtResourcePath(IMAGE_MSG), tr("Send message to whole group"), this, SLOT(msgGroup()));
|
||||
contextMenu.addSeparator();
|
||||
#endif // RS_DIRECT_CHAT
|
||||
|
||||
contextMenu.addAction(FilesDefs::getIconFromQtResourcePath(IMAGE_EDIT), tr("Edit Group"), this, SLOT(editGroup()));
|
||||
|
||||
QAction *action = contextMenu.addAction(FilesDefs::getIconFromQtResourcePath(IMAGE_REMOVE), tr("Remove Group"), this, SLOT(removeGroup()));
|
||||
|
@ -573,9 +573,9 @@ void GxsChannelPostItem::fill()
|
||||
|
||||
ui->datetimelabel->setText(DateTime::formatLongDateTime(mPost.mMeta.mPublishTs));
|
||||
|
||||
if ( (mPost.mCount != 0) || (mPost.mSize != 0) ) {
|
||||
if ( (mPost.mAttachmentCount != 0) || (mPost.mSize != 0) ) {
|
||||
ui->filelabel->setVisible(true);
|
||||
ui->filelabel->setText(QString("(%1 %2) %3").arg(mPost.mCount).arg( (mPost.mCount > 1)?tr("Files"):tr("File")).arg(misc::friendlyUnit(mPost.mSize)));
|
||||
ui->filelabel->setText(QString("(%1 %2) %3").arg(mPost.mAttachmentCount).arg( (mPost.mAttachmentCount > 1)?tr("Files"):tr("File")).arg(misc::friendlyUnit(mPost.mSize)));
|
||||
} else {
|
||||
ui->filelabel->setVisible(false);
|
||||
}
|
||||
|
@ -86,6 +86,13 @@ GxsCommentDialog::~GxsCommentDialog()
|
||||
delete(ui);
|
||||
}
|
||||
|
||||
void GxsCommentDialog::commentClear()
|
||||
{
|
||||
ui->treeWidget->clear();
|
||||
mGrpId.clear();
|
||||
mMostRecentMsgId.clear();
|
||||
mMsgVersions.clear();
|
||||
}
|
||||
void GxsCommentDialog::commentLoad(const RsGxsGroupId &grpId, const std::set<RsGxsMessageId>& msg_versions,const RsGxsMessageId& most_recent_msgId,bool use_cache)
|
||||
{
|
||||
std::cerr << "GxsCommentDialog::commentLoad(" << grpId << ", most recent msg version: " << most_recent_msgId << ")";
|
||||
|
@ -39,6 +39,7 @@ public:
|
||||
void setTokenService(RsTokenService *token_service, RsGxsCommentService *comment_service);
|
||||
void setCommentHeader(QWidget *header);
|
||||
void commentLoad(const RsGxsGroupId &grpId, const std::set<RsGxsMessageId> &msg_versions, const RsGxsMessageId &most_recent_msgId, bool use_cache=false);
|
||||
void commentClear();
|
||||
|
||||
RsGxsGroupId groupId() { return mGrpId; }
|
||||
RsGxsMessageId messageId() { return mMostRecentMsgId; }
|
||||
|
@ -28,6 +28,8 @@
|
||||
|
||||
#include <QAbstractTextDocumentLayout>
|
||||
#include <QApplication>
|
||||
#include <QTextEdit>
|
||||
#include <QHeaderView>
|
||||
#include <QClipboard>
|
||||
#include <QDateTime>
|
||||
#include <QMenu>
|
||||
@ -68,6 +70,8 @@
|
||||
std::map<RsGxsMessageId, std::vector<RsGxsComment> > GxsCommentTreeWidget::mCommentsCache;
|
||||
QMutex GxsCommentTreeWidget::mCacheMutex;
|
||||
|
||||
//#define USE_NEW_DELEGATE 1
|
||||
|
||||
// This class allows to draw the item using an appropriate size
|
||||
|
||||
class MultiLinesCommentDelegate: public QStyledItemDelegate
|
||||
@ -84,7 +88,7 @@ public:
|
||||
{
|
||||
Q_ASSERT(index.isValid());
|
||||
|
||||
QStyleOptionViewItemV4 opt = option;
|
||||
QStyleOptionViewItem opt = option;
|
||||
initStyleOption(&opt, index);
|
||||
// disable default icon
|
||||
opt.icon = QIcon();
|
||||
@ -107,7 +111,6 @@ public:
|
||||
QSizeF s = td.documentLayout()->documentSize();
|
||||
|
||||
int m = QFontMetricsF(QFont()).height();
|
||||
|
||||
QSize full_area(std::min(r.width(),(int)s.width())+m,std::min(r.height(),(int)s.height())+m);
|
||||
|
||||
QPixmap px(full_area.width(),full_area.height());
|
||||
@ -115,6 +118,7 @@ public:
|
||||
QPainter p(&px) ;
|
||||
p.setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
|
||||
QPainterPath path ;
|
||||
path.addRoundedRect(QRectF(m/4.0,m/4.0,s.width()+m/2.0,s.height()+m/2.0),m,m) ;
|
||||
QPen pen(Qt::gray,m/7.0f);
|
||||
@ -127,6 +131,7 @@ public:
|
||||
p.translate(QPointF(m/2.0,m/2.0));
|
||||
td.documentLayout()->draw( &p, ctx );
|
||||
|
||||
|
||||
painter->drawPixmap(r.topLeft(),px);
|
||||
|
||||
const_cast<QAbstractItemModel*>(index.model())->setData(index,px.size(),POST_CELL_SIZE_ROLE);
|
||||
@ -136,11 +141,114 @@ private:
|
||||
QFontMetricsF qf;
|
||||
};
|
||||
|
||||
GxsCommentTreeWidget::GxsCommentTreeWidget(QWidget *parent)
|
||||
:QTreeWidget(parent), mTokenQueue(NULL), mRsTokenService(NULL), mCommentService(NULL)
|
||||
#ifdef USE_NEW_DELEGATE
|
||||
class NoEditDelegate: public QStyledItemDelegate
|
||||
{
|
||||
// QTreeWidget* widget = this;
|
||||
public:
|
||||
NoEditDelegate(QObject* parent=0): QStyledItemDelegate(parent) {}
|
||||
virtual QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
class GxsCommentDelegate: public QStyledItemDelegate
|
||||
{
|
||||
public:
|
||||
GxsCommentDelegate(QFontMetricsF f) : qf(f){}
|
||||
|
||||
QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex &index) const override
|
||||
{
|
||||
return index.data(POST_CELL_SIZE_ROLE).toSize() ;
|
||||
}
|
||||
|
||||
void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex& index) const override
|
||||
{
|
||||
if(index.column() == PCITEM_COLUMN_COMMENT)
|
||||
editor->setGeometry(option.rect);
|
||||
}
|
||||
|
||||
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override
|
||||
{
|
||||
if(index.column() == PCITEM_COLUMN_COMMENT)
|
||||
{
|
||||
QTextEdit *b = new QTextEdit(parent);
|
||||
|
||||
b->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
b->setFixedSize(option.rect.size());
|
||||
b->setAcceptRichText(true);
|
||||
b->setTextInteractionFlags(Qt::TextSelectableByMouse|Qt::LinksAccessibleByMouse);
|
||||
b->document()->setHtml("<html>"+index.data(Qt::DisplayRole).toString()+"</html>");
|
||||
b->adjustSize();
|
||||
|
||||
return b;
|
||||
}
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
|
||||
{
|
||||
if((option.state & QStyle::State_Selected)) // Avoids double display. The selected widget is never exactly the size of the rendered one,
|
||||
return; // so when selected, we only draw the selected one.
|
||||
|
||||
Q_ASSERT(index.isValid());
|
||||
|
||||
QStyleOptionViewItem opt = option;
|
||||
initStyleOption(&opt, index);
|
||||
// disable default icon
|
||||
opt.icon = QIcon();
|
||||
opt.text = QString();
|
||||
|
||||
const QRect r = option.rect.adjusted(0,0,-option.decorationSize.width(),0);
|
||||
|
||||
QTextDocument td ;
|
||||
td.setHtml("<html>"+index.data(Qt::DisplayRole).toString()+"</html>");
|
||||
td.setTextWidth(r.width());
|
||||
QSizeF s = td.documentLayout()->documentSize();
|
||||
|
||||
int m = QFontMetricsF(QFont()).height() ;
|
||||
//int m = 2;
|
||||
|
||||
QSize full_area(std::min(r.width(),(int)s.width())+m,std::min(r.height(),(int)s.height())+m);
|
||||
|
||||
QPixmap px(full_area.width(),full_area.height());
|
||||
px.fill(QColor(0,0,0,0));//Transparent background as item background is already paint.
|
||||
QPainter p(&px) ;
|
||||
p.setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
QTextEdit b;
|
||||
b.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
b.setFixedSize(full_area);
|
||||
b.setAcceptRichText(true);
|
||||
b.setTextInteractionFlags(Qt::TextSelectableByMouse|Qt::LinksAccessibleByMouse);
|
||||
b.document()->setHtml("<html>"+index.data(Qt::DisplayRole).toString()+"</html>");
|
||||
b.adjustSize();
|
||||
b.render(&p,QPoint(),QRegion(),QWidget::DrawChildren );// draw the widgets, not the background
|
||||
|
||||
painter->drawPixmap(opt.rect.topLeft(),px);
|
||||
|
||||
const_cast<QAbstractItemModel*>(index.model())->setData(index,px.size(),POST_CELL_SIZE_ROLE);
|
||||
}
|
||||
|
||||
private:
|
||||
QFontMetricsF qf;
|
||||
};
|
||||
#endif
|
||||
|
||||
void GxsCommentTreeWidget::mouseMoveEvent(QMouseEvent *e)
|
||||
{
|
||||
QModelIndex idx = indexAt(e->pos());
|
||||
|
||||
if(idx != selectionModel()->currentIndex())
|
||||
selectionModel()->setCurrentIndex(idx,QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
|
||||
|
||||
QTreeView::mouseMoveEvent(e);
|
||||
}
|
||||
|
||||
GxsCommentTreeWidget::GxsCommentTreeWidget(QWidget *parent)
|
||||
:QTreeWidget(parent), mTokenQueue(NULL), mRsTokenService(NULL), mCommentService(NULL)
|
||||
{
|
||||
setVerticalScrollMode(ScrollPerPixel);
|
||||
setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
RSElidedItemDelegate *itemDelegate = new RSElidedItemDelegate(this);
|
||||
@ -148,29 +256,45 @@ GxsCommentTreeWidget::GxsCommentTreeWidget(QWidget *parent)
|
||||
setItemDelegate(itemDelegate);
|
||||
setWordWrap(true);
|
||||
|
||||
setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
|
||||
#ifdef USE_NEW_DELEGATE
|
||||
setMouseTracking(true); // for auto selection
|
||||
setItemDelegateForColumn(PCITEM_COLUMN_COMMENT,new GxsCommentDelegate(QFontMetricsF(font()))) ;
|
||||
|
||||
// Apparently the following below is needed, since there is no way to set item flags for a single column
|
||||
// so after setting flags Qt will believe that all columns are editable.
|
||||
|
||||
setItemDelegateForColumn(PCITEM_COLUMN_AUTHOR, new NoEditDelegate(this));
|
||||
setItemDelegateForColumn(PCITEM_COLUMN_DATE, new NoEditDelegate(this));
|
||||
setItemDelegateForColumn(PCITEM_COLUMN_SCORE, new NoEditDelegate(this));
|
||||
setItemDelegateForColumn(PCITEM_COLUMN_UPVOTES, new NoEditDelegate(this));
|
||||
setItemDelegateForColumn(PCITEM_COLUMN_DOWNVOTES,new NoEditDelegate(this));
|
||||
setItemDelegateForColumn(PCITEM_COLUMN_OWNVOTE, new NoEditDelegate(this));
|
||||
|
||||
QObject::connect(header(),SIGNAL(geometriesChanged()),this,SLOT(updateContent()));
|
||||
QObject::connect(header(),SIGNAL(sectionResized(int,int,int)),this,SLOT(updateContent()));
|
||||
|
||||
setEditTriggers(QAbstractItemView::CurrentChanged | QAbstractItemView::SelectedClicked);
|
||||
#else
|
||||
setItemDelegateForColumn(PCITEM_COLUMN_COMMENT,new MultiLinesCommentDelegate(QFontMetricsF(font()))) ;
|
||||
|
||||
#endif
|
||||
|
||||
commentsRole = new RSTreeWidgetItemCompareRole;
|
||||
commentsRole->setRole(PCITEM_COLUMN_DATE, ROLE_SORT);
|
||||
|
||||
mUseCache = false;
|
||||
|
||||
// QFont font = QFont("ARIAL", 10);
|
||||
// font.setBold(true);
|
||||
|
||||
// QString name("test");
|
||||
// QTreeWidgetItem *item = new QTreeWidgetItem();
|
||||
// item->setText(0, name);
|
||||
// item->setFont(0, font);
|
||||
// item->setSizeHint(0, QSize(18, 18));
|
||||
// item->setForeground(0, QBrush(QColor(79, 79, 79)));
|
||||
|
||||
// addTopLevelItem(item);
|
||||
// item->setExpanded(true);
|
||||
|
||||
return;
|
||||
//header()->setSectionResizeMode(PCITEM_COLUMN_COMMENT,QHeaderView::ResizeToContents);
|
||||
return;
|
||||
}
|
||||
|
||||
void GxsCommentTreeWidget::updateContent()
|
||||
{
|
||||
model()->dataChanged(QModelIndex(),QModelIndex());
|
||||
|
||||
std::cerr << "Updating content" << std::endl;
|
||||
}
|
||||
GxsCommentTreeWidget::~GxsCommentTreeWidget()
|
||||
{
|
||||
if (mTokenQueue) {
|
||||
@ -193,15 +317,18 @@ void GxsCommentTreeWidget::setCurrentCommentMsgId(QTreeWidgetItem *current, QTre
|
||||
}
|
||||
}
|
||||
|
||||
void GxsCommentTreeWidget::customPopUpMenu(const QPoint& /*point*/)
|
||||
void GxsCommentTreeWidget::customPopUpMenu(const QPoint& point)
|
||||
{
|
||||
QTreeWidgetItem *item = itemAt(point);
|
||||
|
||||
QMenu contextMnu( this );
|
||||
QAction* action = contextMnu.addAction(FilesDefs::getIconFromQtResourcePath(IMAGE_REPLY), tr("Reply to Comment"), this, SLOT(replyToComment()));
|
||||
action->setDisabled(mCurrentCommentMsgId.isNull());
|
||||
action = contextMnu.addAction(FilesDefs::getIconFromQtResourcePath(IMAGE_MESSAGE), tr("Submit Comment"), this, SLOT(makeComment()));
|
||||
action->setDisabled(mMsgVersions.empty());
|
||||
action = contextMnu.addAction(FilesDefs::getIconFromQtResourcePath(IMAGE_COPY), tr("Copy Comment"), this, SLOT(copyComment()));
|
||||
action->setDisabled(mCurrentCommentMsgId.isNull());
|
||||
action->setData( item->data(PCITEM_COLUMN_COMMENT,Qt::DisplayRole) );
|
||||
action->setDisabled(mCurrentCommentMsgId.isNull());
|
||||
|
||||
contextMnu.addSeparator();
|
||||
|
||||
@ -342,8 +469,10 @@ void GxsCommentTreeWidget::replyToComment()
|
||||
|
||||
void GxsCommentTreeWidget::copyComment()
|
||||
{
|
||||
QString txt = dynamic_cast<QAction*>(sender())->data().toString();
|
||||
|
||||
QMimeData *mimeData = new QMimeData();
|
||||
mimeData->setHtml("<html>"+mCurrentCommentText+"</html>");
|
||||
mimeData->setHtml("<html>"+txt+"</html>");
|
||||
QClipboard *clipboard = QApplication::clipboard();
|
||||
clipboard->setMimeData(mimeData, QClipboard::Clipboard);
|
||||
}
|
||||
@ -540,23 +669,20 @@ void GxsCommentTreeWidget::addItem(RsGxsMessageId itemId, RsGxsMessageId parentI
|
||||
int treeCount(QTreeWidget *tree, QTreeWidgetItem *parent = 0)
|
||||
{
|
||||
int count = 0;
|
||||
if (parent == 0) {
|
||||
if (parent == 0)
|
||||
{
|
||||
int topCount = tree->topLevelItemCount();
|
||||
for (int i = 0; i < topCount; i++) {
|
||||
QTreeWidgetItem *item = tree->topLevelItem(i);
|
||||
if (item->isExpanded()) {
|
||||
count += treeCount(tree, item);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < topCount; i++)
|
||||
count += treeCount(tree, tree->topLevelItem(i));
|
||||
|
||||
count += topCount;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
int childCount = parent->childCount();
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
QTreeWidgetItem *item = parent->child(i);
|
||||
if (item->isExpanded()) {
|
||||
count += treeCount(tree, item);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < childCount; i++)
|
||||
count += treeCount(tree, parent->child(i));
|
||||
|
||||
count += childCount;
|
||||
}
|
||||
return count;
|
||||
@ -642,14 +768,14 @@ void GxsCommentTreeWidget::insertComments(const std::vector<RsGxsComment>& comme
|
||||
}
|
||||
|
||||
text = QString::fromUtf8(comment.mComment.c_str());
|
||||
item->setText(PCITEM_COLUMN_COMMENT, text);
|
||||
item->setToolTip(PCITEM_COLUMN_COMMENT, text);
|
||||
item->setText(PCITEM_COLUMN_COMMENT, text);
|
||||
item->setToolTip(PCITEM_COLUMN_COMMENT, text);
|
||||
|
||||
RsGxsId authorId = comment.mMeta.mAuthorId;
|
||||
RsGxsId authorId = comment.mMeta.mAuthorId;
|
||||
item->setId(authorId, PCITEM_COLUMN_AUTHOR, false);
|
||||
item->setData(PCITEM_COLUMN_COMMENT,POST_COLOR_ROLE,QVariant(authorId.toByteArray()[1]));
|
||||
|
||||
text = QString::number(comment.mScore);
|
||||
text = QString::number(comment.mScore);
|
||||
item->setText(PCITEM_COLUMN_SCORE, text);
|
||||
|
||||
text = QString::number(comment.mUpVotes);
|
||||
@ -670,6 +796,11 @@ void GxsCommentTreeWidget::insertComments(const std::vector<RsGxsComment>& comme
|
||||
text = QString::fromUtf8(comment.mMeta.mAuthorId.toStdString().c_str());
|
||||
item->setText(PCITEM_COLUMN_AUTHORID, text);
|
||||
|
||||
#ifdef USE_NEW_DELEGATE
|
||||
// Allows to call createEditor() in the delegate. Without this, createEditor() is never called in
|
||||
// the styled item delegate.
|
||||
item->setFlags(Qt::ItemIsEditable | item->flags());
|
||||
#endif
|
||||
|
||||
addItem(comment.mMeta.mMsgId, comment.mMeta.mParentId, item);
|
||||
}
|
||||
|
@ -47,7 +47,12 @@ public:
|
||||
void setVoteId(const RsGxsId &voterId);
|
||||
|
||||
void setUseCache(bool b) { mUseCache = b ;}
|
||||
|
||||
protected slots:
|
||||
void updateContent();
|
||||
|
||||
protected:
|
||||
void mouseMoveEvent(QMouseEvent *e) override;
|
||||
|
||||
/* to be overloaded */
|
||||
virtual void service_requestComments(const RsGxsGroupId &group_id, const std::set<RsGxsMessageId> &msgIds);
|
||||
|
@ -63,8 +63,8 @@ void GxsCreateCommentDialog::createComment()
|
||||
{
|
||||
RsGxsComment comment;
|
||||
|
||||
QString text = ui->commentTextEdit->toHtml();
|
||||
RsHtml::optimizeHtml(text);
|
||||
QString text = ui->commentTextEdit->toPlainText();
|
||||
// RsHtml::optimizeHtml(text);
|
||||
std::string msg = text.toUtf8().constData();
|
||||
|
||||
comment.mComment = msg;
|
||||
|
@ -160,8 +160,11 @@ p, li { white-space: pre-wrap; }
|
||||
<property name="styleSheet">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="acceptRichText">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>Type your comment</string>
|
||||
<string>Type in your comment</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -254,9 +257,6 @@ p, li { white-space: pre-wrap; }
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources>
|
||||
<include location="../icons.qrc"/>
|
||||
<include location="../images.qrc"/>
|
||||
</resources>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
@ -37,7 +37,7 @@ const uint32_t ChannelCreateEnabledFlags = (
|
||||
// GXS_GROUP_FLAGS_PUBLISHSIGN |
|
||||
// GXS_GROUP_FLAGS_SHAREKEYS | // disabled because the UI doesn't handle it, so no need to show the disabled button. The user can do it in a second step from the channel menu.
|
||||
// GXS_GROUP_FLAGS_PERSONALSIGN |
|
||||
GXS_GROUP_FLAGS_COMMENTS |
|
||||
// GXS_GROUP_FLAGS_COMMENTS | // disabled because the UI doesn't handle it yet. Better to hide it then.
|
||||
0);
|
||||
|
||||
const uint32_t ChannelCreateDefaultsFlags = ( GXS_GROUP_DEFAULTS_DISTRIB_PUBLIC |
|
||||
|
@ -72,6 +72,25 @@ void RsGxsChannelPostsModel::setMode(TreeMode mode)
|
||||
triggerViewUpdate();
|
||||
}
|
||||
|
||||
void updateCommentCounts( std::vector<RsGxsChannelPost>& posts, std::vector<RsGxsComment>& comments)
|
||||
{
|
||||
// Store posts IDs in a std::map to avoid a quadratic cost
|
||||
|
||||
std::map<RsGxsMessageId,uint32_t> post_indices;
|
||||
|
||||
for(uint32_t i=0;i<posts.size();++i)
|
||||
{
|
||||
post_indices[posts[i].mMeta.mMsgId] = i;
|
||||
posts[i].mCommentCount = 0; // should be 0 already, but we secure that value.
|
||||
}
|
||||
|
||||
// now look into comments and increase the count
|
||||
|
||||
for(uint32_t i=0;i<comments.size();++i)
|
||||
++posts[post_indices[comments[i].mMeta.mThreadId]].mCommentCount;
|
||||
}
|
||||
|
||||
|
||||
void RsGxsChannelPostsModel::handleEvent_main_thread(std::shared_ptr<const RsEvent> event)
|
||||
{
|
||||
const RsGxsChannelEvent *e = dynamic_cast<const RsGxsChannelEvent*>(event.get());
|
||||
@ -88,24 +107,44 @@ void RsGxsChannelPostsModel::handleEvent_main_thread(std::shared_ptr<const RsEve
|
||||
//
|
||||
// We need to update the data!
|
||||
|
||||
if(e->mChannelGroupId == mChannelGroup.mMeta.mGroupId)
|
||||
RsThread::async([this, e]()
|
||||
// make a copy of e, so as to avoid destruction of the shared pointer during async thread execution, since [e] doesn't actually tell
|
||||
// the shared_ptr that it is copied! So no counter is updated.
|
||||
RsGxsChannelEvent E(*e);
|
||||
|
||||
if(E.mChannelGroupId == mChannelGroup.mMeta.mGroupId)
|
||||
RsThread::async([this, E]()
|
||||
{
|
||||
// 1 - get message data from p3GxsChannels
|
||||
// 1 - get message data from p3GxsChannels. No need for pointers here, because we send only a single post to postToObject()
|
||||
|
||||
std::vector<RsGxsChannelPost> posts;
|
||||
std::vector<RsGxsComment> comments;
|
||||
std::vector<RsGxsVote> votes;
|
||||
|
||||
if(!rsGxsChannels->getChannelContent(mChannelGroup.mMeta.mGroupId,std::set<RsGxsMessageId>{ e->mChannelMsgId }, posts,comments,votes))
|
||||
std::cerr << "display of e 1: " << E << std::endl;
|
||||
|
||||
if(!rsGxsChannels->getChannelContent(E.mChannelGroupId,std::set<RsGxsMessageId>{ E.mChannelMsgId }, posts,comments,votes))
|
||||
{
|
||||
std::cerr << __PRETTY_FUNCTION__ << " failed to retrieve channel message data for channel/msg " << e->mChannelGroupId << "/" << e->mChannelMsgId << std::endl;
|
||||
std::cerr << __PRETTY_FUNCTION__ << " failed to retrieve channel message data for channel/msg " << E.mChannelGroupId << "/" << E.mChannelMsgId << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
// 2 - update the model in the UI thread.
|
||||
// Need to call this in order to get the actuall comment count. The previous call only retrieves the message, since we supplied the message ID.
|
||||
// another way to go would be to save the comment ids of the existing message and re-insert them before calling getChannelContent.
|
||||
|
||||
RsQThreadUtils::postToObject( [posts,comments,votes,this]()
|
||||
std::cerr << "display of e 2: " << E << std::endl;
|
||||
std::cerr << "Before call : IS_MSG_READ=" << IS_MSG_NEW(posts[0].mMeta.mMsgFlags) << " for message id " << E.mChannelMsgId << std::endl;
|
||||
if(!rsGxsChannels->getChannelComments(E.mChannelGroupId,std::set<RsGxsMessageId>{ E.mChannelMsgId },comments))
|
||||
{
|
||||
std::cerr << __PRETTY_FUNCTION__ << " failed to retrieve message comment data for channel/msg " << E.mChannelGroupId << "/" << E.mChannelMsgId << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
updateCommentCounts(posts,comments);
|
||||
|
||||
std::cerr << "After call : IS_MSG_READ=" << IS_MSG_NEW(posts[0].mMeta.mMsgFlags) << std::endl;
|
||||
// 2 - update the model in the UI thread.
|
||||
|
||||
RsQThreadUtils::postToObject( [posts,this]()
|
||||
{
|
||||
for(uint32_t i=0;i<posts.size();++i)
|
||||
{
|
||||
@ -113,7 +152,7 @@ void RsGxsChannelPostsModel::handleEvent_main_thread(std::shared_ptr<const RsEve
|
||||
|
||||
for(uint32_t j=0;j<mPosts.size();++j)
|
||||
if(mPosts[j].mMeta.mMsgId == posts[i].mMeta.mMsgId)
|
||||
{
|
||||
{
|
||||
mPosts[j] = posts[i];
|
||||
|
||||
triggerViewUpdate();
|
||||
@ -121,10 +160,10 @@ void RsGxsChannelPostsModel::handleEvent_main_thread(std::shared_ptr<const RsEve
|
||||
}
|
||||
},this);
|
||||
});
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -195,9 +234,7 @@ void RsGxsChannelPostsModel::setFilter(const QStringList& strings,bool only_unre
|
||||
|
||||
count = mFilteredPosts.size();
|
||||
|
||||
std::cerr << "After filtering: " << count << " posts remain." << std::endl;
|
||||
|
||||
beginInsertRows(QModelIndex(),0,rowCount()-1);
|
||||
beginInsertRows(QModelIndex(),0,rowCount()-1);
|
||||
endInsertRows();
|
||||
|
||||
postMods();
|
||||
@ -326,7 +363,7 @@ bool RsGxsChannelPostsModel::setNumColumns(int n)
|
||||
RsErr() << __PRETTY_FUNCTION__ << " Attempt to set a number of column of 0. This is wrong." << std::endl;
|
||||
return false;
|
||||
}
|
||||
if(mColumns == n)
|
||||
if((int)mColumns == n)
|
||||
return false;
|
||||
|
||||
preMods();
|
||||
@ -556,6 +593,15 @@ void RsGxsChannelPostsModel::update_posts(const RsGxsGroupId& group_id)
|
||||
std::cerr << __PRETTY_FUNCTION__ << " failed to retrieve channel messages for channel " << group_id << std::endl;
|
||||
return;
|
||||
}
|
||||
std::cerr << "Got channel all content for channel " << group_id << std::endl;
|
||||
std::cerr << " posts : " << posts->size() << std::endl;
|
||||
std::cerr << " comments: " << comments->size() << std::endl;
|
||||
std::cerr << " votes : " << votes->size() << std::endl;
|
||||
|
||||
// This shouldn't be needed normally. We need it until a background process computes the number of comments per
|
||||
// post and stores it in the service string. Since we request all data, this process isn't costing much anyway.
|
||||
|
||||
updateCommentCounts(*posts,*comments);
|
||||
|
||||
// 2 - update the model in the UI thread.
|
||||
|
||||
|
@ -76,6 +76,7 @@ QColor SelectedColor = QRgb(0xff308dc7);
|
||||
#define COLUMN_SIZE_FONT_FACTOR_H 10
|
||||
|
||||
#define STAR_OVERLAY_IMAGE ":icons/star_overlay_128.png"
|
||||
#define COMMENT_OVERLAY_IMAGE ":images/white-bubble-64.png"
|
||||
#define IMAGE_COPYLINK ":icons/png/copy.png"
|
||||
#define IMAGE_GRID_VIEW ":icons/png/menu.png"
|
||||
#define IMAGE_DOWNLOAD ":icons/png/download.png"
|
||||
@ -179,8 +180,19 @@ void ChannelPostDelegate::paint(QPainter * painter, const QStyleOptionViewItem &
|
||||
QPainter p(&pixmap);
|
||||
QFontMetricsF fm(option.font);
|
||||
|
||||
p.drawPixmap(mZoom*QPoint(0.1*fm.height(),-3.6*fm.height()),FilesDefs::getPixmapFromQtResourcePath(STAR_OVERLAY_IMAGE).scaled(mZoom*7*fm.height(),mZoom*7*fm.height(),Qt::KeepAspectRatio,Qt::SmoothTransformation));
|
||||
p.drawPixmap(mZoom*QPoint(0.1*fm.height(),-3.4*fm.height()),FilesDefs::getPixmapFromQtResourcePath(STAR_OVERLAY_IMAGE).scaled(mZoom*6*fm.height(),mZoom*6*fm.height(),Qt::KeepAspectRatio,Qt::SmoothTransformation));
|
||||
}
|
||||
|
||||
if(post.mCommentCount)
|
||||
{
|
||||
QPainter p(&pixmap);
|
||||
QFontMetricsF fm(option.font);
|
||||
|
||||
p.drawPixmap(QPoint(pixmap.width(),0.0)+mZoom*QPoint(-2.9*fm.height(),0.4*fm.height()),
|
||||
FilesDefs::getPixmapFromQtResourcePath(COMMENT_OVERLAY_IMAGE).scaled(mZoom*3*fm.height(),mZoom*3*fm.height(),
|
||||
Qt::KeepAspectRatio,Qt::SmoothTransformation));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
painter->drawPixmap(option.rect.topLeft(),
|
||||
@ -222,8 +234,8 @@ void ChannelPostDelegate::paint(QPainter * painter, const QStyleOptionViewItem &
|
||||
|
||||
QString info_text = QDateTime::fromMSecsSinceEpoch(qint64(1000)*post.mMeta.mPublishTs).toString(Qt::DefaultLocaleShortDate);
|
||||
|
||||
if(post.mCount > 0)
|
||||
info_text += ", " + QString::number(post.mCount)+ " " +((post.mCount>1)?tr("files"):tr("file")) + " (" + misc::friendlyUnit(qulonglong(post.mSize)) + ")" ;
|
||||
if(post.mAttachmentCount > 0)
|
||||
info_text += ", " + QString::number(post.mAttachmentCount)+ " " +((post.mAttachmentCount>1)?tr("files"):tr("file")) + " (" + misc::friendlyUnit(qulonglong(post.mSize)) + ")" ;
|
||||
|
||||
painter->drawText(QPoint(p.x()+0.5*font_height,y),info_text);
|
||||
y += font_height;
|
||||
@ -738,7 +750,9 @@ void GxsChannelPostsWidgetWithModel::handleEvent_main_thread(std::shared_ptr<con
|
||||
switch(e->mChannelEventCode)
|
||||
{
|
||||
case RsChannelEventCode::NEW_CHANNEL: // [[fallthrough]];
|
||||
case RsChannelEventCode::UPDATED_CHANNEL: // [[fallthrough]];
|
||||
case RsChannelEventCode::NEW_COMMENT: // [[fallthrough]];
|
||||
case RsChannelEventCode::NEW_VOTE: // [[fallthrough]];
|
||||
case RsChannelEventCode::UPDATED_CHANNEL: // [[fallthrough]];
|
||||
case RsChannelEventCode::NEW_MESSAGE: // [[fallthrough]];
|
||||
case RsChannelEventCode::UPDATED_MESSAGE:
|
||||
case RsChannelEventCode::SYNC_PARAMETERS_UPDATED:
|
||||
@ -756,7 +770,10 @@ void GxsChannelPostsWidgetWithModel::showPostDetails()
|
||||
{
|
||||
QModelIndex index = ui->postsTree->selectionModel()->currentIndex();
|
||||
RsGxsChannelPost post = index.data(Qt::UserRole).value<RsGxsChannelPost>() ;
|
||||
|
||||
#ifdef DEBUG_CHANNEL_POSTS_WIDGET
|
||||
std::cerr << "showPostDetails: current index is " << index.row() << "," << index.column() << std::endl;
|
||||
#endif
|
||||
|
||||
QTextDocument doc;
|
||||
doc.setHtml(post.mMsg.c_str());
|
||||
|
||||
@ -768,6 +785,7 @@ void GxsChannelPostsWidgetWithModel::showPostDetails()
|
||||
ui->postTime_LB->hide();
|
||||
mChannelPostFilesModel->clear();
|
||||
ui->details_TW->setEnabled(false);
|
||||
mSelectedPost.clear();
|
||||
|
||||
return;
|
||||
}
|
||||
@ -777,7 +795,9 @@ void GxsChannelPostsWidgetWithModel::showPostDetails()
|
||||
ui->postName_LB->show();
|
||||
ui->postTime_LB->show();
|
||||
|
||||
std::cerr << "showPostDetails: setting mSelectedPost to current post Id " << post.mMeta.mMsgId << ". Previous value: " << mSelectedPost << std::endl;
|
||||
#ifdef DEBUG_CHANNEL_POSTS_WIDGET
|
||||
std::cerr << "showPostDetails: setting mSelectedPost to current post Id " << post.mMeta.mMsgId << ". Previous value: " << mSelectedPost << std::endl;
|
||||
#endif
|
||||
mSelectedPost = post.mMeta.mMsgId;
|
||||
|
||||
std::list<ChannelPostFileInfo> files;
|
||||
@ -791,7 +811,9 @@ void GxsChannelPostsWidgetWithModel::showPostDetails()
|
||||
|
||||
ui->commentsDialog->commentLoad(post.mMeta.mGroupId, all_msgs_versions, post.mMeta.mMsgId,true);
|
||||
|
||||
#ifdef DEBUG_CHANNEL_POSTS_WIDGET
|
||||
std::cerr << "Showing details about selected index : "<< index.row() << "," << index.column() << std::endl;
|
||||
#endif
|
||||
|
||||
ui->postDetails_TE->setText(RsHtml().formatText(NULL, QString::fromUtf8(post.mMsg.c_str()), RSHTML_FORMATTEXT_EMBED_SMILEYS | RSHTML_FORMATTEXT_EMBED_LINKS));
|
||||
|
||||
@ -864,6 +886,16 @@ void GxsChannelPostsWidgetWithModel::updateGroupData()
|
||||
|
||||
RsQThreadUtils::postToObject( [this,group]()
|
||||
{
|
||||
if(mGroup.mMeta.mGroupId != group.mMeta.mGroupId) // this prevents any attempt to display the wrong index. Navigate() if needed will use mSelectedPost
|
||||
{
|
||||
#ifdef DEBUG_CHANNEL_POSTS_WIDGET
|
||||
std::cerr << "Old group: " << mGroup.mMeta.mGroupId << ", new group: " << group.mMeta.mGroupId << ". Celaring selection" << std::endl;
|
||||
#endif
|
||||
whileBlocking(ui->postsTree->selectionModel())->clear();
|
||||
whileBlocking(ui->commentsDialog)->commentClear();
|
||||
updateCommentsCount(0);
|
||||
}
|
||||
|
||||
mGroup = group;
|
||||
mChannelPostsModel->updateChannel(groupId());
|
||||
whileBlocking(ui->filterLineEdit)->clear();
|
||||
@ -1132,8 +1164,10 @@ void GxsChannelPostsWidgetWithModel::insertChannelDetails(const RsGxsChannelGrou
|
||||
sync_string = tr("Unknown");
|
||||
}
|
||||
|
||||
if(group.mMeta.mLastPost > 0 && group.mMeta.mLastPost + rsGxsChannels->getSyncPeriod(group.mMeta.mGroupId) < time(NULL) && IS_GROUP_SUBSCRIBED(group.mMeta.mSubscribeFlags))
|
||||
sync_string += " (Warning: will not allow latest posts to sync)";
|
||||
auto sync_period = rsGxsChannels->getSyncPeriod(group.mMeta.mGroupId) ;
|
||||
|
||||
if(sync_period > 0 && group.mMeta.mLastPost > 0 && group.mMeta.mLastPost + rsGxsChannels->getSyncPeriod(group.mMeta.mGroupId) < time(NULL) && IS_GROUP_SUBSCRIBED(group.mMeta.mSubscribeFlags))
|
||||
sync_string += " (Warning: will not allow posts to sync)";
|
||||
|
||||
ui->infoSyncTimeLabel->setText(sync_string);
|
||||
|
||||
|
@ -410,7 +410,7 @@ QVariant RsGxsForumModel::data(const QModelIndex &index, int role) const
|
||||
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));
|
||||
font.setBold( (fmpe.mPostFlags & ForumModelPostEntry::FLAG_POST_HAS_UNREAD_CHILDREN) || IS_MSG_UNREAD(fmpe.mMsgStatus));
|
||||
return QVariant(font);
|
||||
}
|
||||
|
||||
@ -445,15 +445,15 @@ QVariant RsGxsForumModel::textColorRole(const ForumModelPostEntry& fmpe,int /*co
|
||||
if( (fmpe.mPostFlags & ForumModelPostEntry::FLAG_POST_IS_MISSING))
|
||||
return QVariant(mTextColorMissing);
|
||||
|
||||
if(fmpe.mPostFlags & ForumModelPostEntry::FLAG_POST_IS_PINNED)
|
||||
return QVariant(mTextColorPinned);
|
||||
|
||||
if(IS_MSG_UNREAD(fmpe.mMsgStatus))
|
||||
return QVariant(mTextColorUnread);
|
||||
else
|
||||
if(fmpe.mPostFlags & ForumModelPostEntry::FLAG_POST_IS_PINNED)
|
||||
return QVariant(mTextColorPinned);
|
||||
else
|
||||
return QVariant(mTextColorRead);
|
||||
return QVariant(mTextColorRead);
|
||||
|
||||
return QVariant();
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QVariant RsGxsForumModel::statusRole(const ForumModelPostEntry& fmpe,int column) const
|
||||
@ -595,8 +595,8 @@ QVariant RsGxsForumModel::pinnedRole(const ForumModelPostEntry& fmpe,int /*colum
|
||||
|
||||
QVariant RsGxsForumModel::backgroundRole(const ForumModelPostEntry& fmpe,int /*column*/) const
|
||||
{
|
||||
if(fmpe.mPostFlags & ForumModelPostEntry::FLAG_POST_IS_PINNED)
|
||||
return QVariant(QBrush(mBackgroundColorPinned));
|
||||
// if(fmpe.mPostFlags & ForumModelPostEntry::FLAG_POST_IS_PINNED)
|
||||
// return QVariant(QBrush(mBackgroundColorPinned));
|
||||
|
||||
if(mFilteringEnabled && (fmpe.mPostFlags & ForumModelPostEntry::FLAG_POST_PASSES_FILTER))
|
||||
return QVariant(QBrush(mBackgroundColorFiltered));
|
||||
@ -656,12 +656,13 @@ QVariant RsGxsForumModel::displayRole(const ForumModelPostEntry& fmpe,int col) c
|
||||
{
|
||||
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
|
||||
// else if(fmpe.mPostFlags & ForumModelPostEntry::FLAG_POST_IS_PINNED)
|
||||
// return QVariant( QString("<img src=\":/icons/pinned_64.png\" height=%1/>").arg(QFontMetricsF(QFont()).height())
|
||||
// + QString::fromUtf8(fmpe.mTitle.c_str()));
|
||||
else
|
||||
return QVariant(QString::fromUtf8(fmpe.mTitle.c_str()));
|
||||
|
||||
case COLUMN_THREAD_READ:return QVariant();
|
||||
case COLUMN_THREAD_READ:return QVariant();
|
||||
case COLUMN_THREAD_DATE:{
|
||||
if(fmpe.mPostFlags & ForumModelPostEntry::FLAG_POST_IS_MISSING)
|
||||
return QVariant(QString());
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user