From 5fc31d2c32331552956a9d7745c539d8ec78d5a1 Mon Sep 17 00:00:00 2001 From: defnax Date: Fri, 22 Jan 2021 21:31:51 +0100 Subject: [PATCH] Added new widgets for the Channels & Boards comments notify for Activity Stream --- retroshare-gui/src/gui/NewsFeed.cpp | 8 + .../src/gui/Posted/PostedDialog.cpp | 1 + .../src/gui/feeds/BoardsCommentsItem.cpp | 566 +++++++++++++++ .../src/gui/feeds/BoardsCommentsItem.h | 125 ++++ .../src/gui/feeds/BoardsCommentsItem.ui | 588 ++++++++++++++++ .../src/gui/feeds/ChannelsCommentsItem.cpp | 650 ++++++++++++++++++ .../src/gui/feeds/ChannelsCommentsItem.h | 125 ++++ .../src/gui/feeds/ChannelsCommentsItem.ui | 588 ++++++++++++++++ .../src/gui/gxschannels/GxsChannelDialog.cpp | 1 + .../src/gui/qss/stylesheet/Standard.qss | 20 + retroshare-gui/src/qss/qdarkstyle-v2.qss | 19 + retroshare-gui/src/qss/qdarkstyle.qss | 19 + retroshare-gui/src/retroshare-gui.pro | 6 + 13 files changed, 2716 insertions(+) create mode 100644 retroshare-gui/src/gui/feeds/BoardsCommentsItem.cpp create mode 100644 retroshare-gui/src/gui/feeds/BoardsCommentsItem.h create mode 100644 retroshare-gui/src/gui/feeds/BoardsCommentsItem.ui create mode 100644 retroshare-gui/src/gui/feeds/ChannelsCommentsItem.cpp create mode 100644 retroshare-gui/src/gui/feeds/ChannelsCommentsItem.h create mode 100644 retroshare-gui/src/gui/feeds/ChannelsCommentsItem.ui diff --git a/retroshare-gui/src/gui/NewsFeed.cpp b/retroshare-gui/src/gui/NewsFeed.cpp index b42f342f4..136acd7c8 100644 --- a/retroshare-gui/src/gui/NewsFeed.cpp +++ b/retroshare-gui/src/gui/NewsFeed.cpp @@ -33,8 +33,10 @@ #include #include "util/qtthreadsutils.h" +#include "feeds/BoardsCommentsItem.h" #include "feeds/ChatMsgItem.h" #include "feeds/GxsCircleItem.h" +#include "feeds/ChannelsCommentsItem.h" #include "feeds/GxsChannelGroupItem.h" #include "feeds/GxsChannelPostItem.h" #include "feeds/GxsForumGroupItem.h" @@ -240,6 +242,9 @@ void NewsFeed::handlePostedEvent(std::shared_ptr event) case RsPostedEventCode::NEW_MESSAGE: addFeedItem( new PostedItem(this, NEWSFEED_POSTEDMSGLIST, pe->mPostedGroupId, pe->mPostedMsgId, false, true)); break; + case RsPostedEventCode::NEW_COMMENT: + addFeedItem( new BoardsCommentsItem(this, NEWSFEED_POSTEDMSGLIST, pe->mPostedGroupId, pe->mPostedMsgId, false, true)); + break; default: break; } } @@ -285,6 +290,9 @@ void NewsFeed::handleChannelEvent(std::shared_ptr event) case RsChannelEventCode::NEW_MESSAGE: addFeedItem(new GxsChannelPostItem(this, NEWSFEED_CHANNELNEWLIST, pe->mChannelGroupId, pe->mChannelMsgId, false, true)); break; + case RsChannelEventCode::NEW_COMMENT: + addFeedItem(new ChannelsCommentsItem(this, NEWSFEED_CHANNELNEWLIST, pe->mChannelGroupId, pe->mChannelMsgId, false, true)); + break; case RsChannelEventCode::RECEIVED_PUBLISH_KEY: addFeedItem(new GxsChannelGroupItem(this, NEWSFEED_CHANNELPUBKEYLIST, pe->mChannelGroupId, false, true)); break; diff --git a/retroshare-gui/src/gui/Posted/PostedDialog.cpp b/retroshare-gui/src/gui/Posted/PostedDialog.cpp index 5000ce3f1..1364cf2d7 100644 --- a/retroshare-gui/src/gui/Posted/PostedDialog.cpp +++ b/retroshare-gui/src/gui/Posted/PostedDialog.cpp @@ -62,6 +62,7 @@ void PostedDialog::handleEvent_main_thread(std::shared_ptr event) { case RsPostedEventCode::NEW_MESSAGE: case RsPostedEventCode::UPDATED_MESSAGE: // [[fallthrough]]; + case RsPostedEventCode::NEW_COMMENT: // [[fallthrough]]; case RsPostedEventCode::READ_STATUS_CHANGED: // [[fallthrough]]; updateGroupStatisticsReal(e->mPostedGroupId); // update the list immediately break; diff --git a/retroshare-gui/src/gui/feeds/BoardsCommentsItem.cpp b/retroshare-gui/src/gui/feeds/BoardsCommentsItem.cpp new file mode 100644 index 000000000..be9be47fb --- /dev/null +++ b/retroshare-gui/src/gui/feeds/BoardsCommentsItem.cpp @@ -0,0 +1,566 @@ +/******************************************************************************* + * retroshare-gui/src/gui/Posted/BoardsCommentsItem.cpp * + * * + * Copyright (C) 2020 by 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 +#include + +#include "rshare.h" +#include "BoardsCommentsItem.h" +#include "gui/feeds/FeedHolder.h" +#include "gui/RetroShareLink.h" +#include "gui/gxs/GxsIdDetails.h" +#include "util/DateTime.h" +#include "util/misc.h" +#include "util/stringutil.h" +#include "gui/common/FilesDefs.h" +#include "util/qtthreadsutils.h" +#include "util/HandleRichText.h" +#include "gui/MainWindow.h" +#include "gui/Identity/IdDialog.h" +#include "gui/Posted/PostedDialog.h" + +#include "ui_BoardsCommentsItem.h" + +#include +#include + +#include +#include + +#define LINK_IMAGE ":/images/thumb-link.png" + +/** Constructor */ + +//======================================================================================== +// BaseBoardsCommentsItem // +//======================================================================================== + +BaseBoardsCommentsItem::BaseBoardsCommentsItem( FeedHolder *feedHolder, uint32_t feedId + , const RsGroupMetaData &group_meta, const RsGxsMessageId& post_id + , bool isHome, bool autoUpdate) + : GxsFeedItem(feedHolder, feedId, group_meta.mGroupId, post_id, isHome, rsPosted, autoUpdate) + , mInFill(false), mGroupMeta(group_meta) + , mLoaded(false), mIsLoadingGroup(false), mIsLoadingMessage(false), mIsLoadingComment(false) +{ + mPost.mMeta.mMsgId = post_id; + mPost.mMeta.mGroupId = mGroupMeta.mGroupId; +} + +BaseBoardsCommentsItem::BaseBoardsCommentsItem( FeedHolder *feedHolder, uint32_t feedId + , const RsGxsGroupId &groupId, const RsGxsMessageId& post_id + , bool isHome, bool autoUpdate) + : GxsFeedItem(feedHolder, feedId, groupId, post_id, isHome, rsPosted, autoUpdate) + , mInFill(false) + , mLoaded(false), mIsLoadingGroup(false), mIsLoadingMessage(false), mIsLoadingComment(false) +{ + mPost.mMeta.mMsgId = post_id; +} + +BaseBoardsCommentsItem::~BaseBoardsCommentsItem() +{ + auto timeout = std::chrono::steady_clock::now() + std::chrono::milliseconds(200); + while( (mIsLoadingGroup || mIsLoadingMessage || mIsLoadingComment) + && std::chrono::steady_clock::now() < timeout) + { + RsDbg() << __PRETTY_FUNCTION__ << " is Waiting " + << (mIsLoadingGroup ? "Group " : "") + << (mIsLoadingMessage ? "Message " : "") + << (mIsLoadingComment ? "Comment " : "") + << "loading finished." << std::endl; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } +} + +void BaseBoardsCommentsItem::paintEvent(QPaintEvent *e) +{ + /* This method employs a trick to trigger a deferred loading. The post and group is requested only + * when actually displayed on the screen. */ + + if(!mLoaded) + { + mLoaded = true ; + + requestMessage(); + requestComment(); + } + + GxsFeedItem::paintEvent(e) ; +} + +bool BaseBoardsCommentsItem::setPost(const RsPostedPost &post, bool doFill) +{ + if (groupId() != post.mMeta.mGroupId || messageId() != post.mMeta.mMsgId) { + std::cerr << "BaseBoardsCommentsItem::setPost() - Wrong id, cannot set post"; + std::cerr << std::endl; + return false; + } + + mPost = post; + + if (doFill) + fill(); + + return true; +} + +void BaseBoardsCommentsItem::loadGroup() +{ + mIsLoadingGroup = true; + RsThread::async([this]() + { + // 1 - get group data + +#ifdef DEBUG_FORUMS + std::cerr << "Retrieving post data for post " << mThreadId << std::endl; +#endif + + std::vector groups; + const std::list groupIds = { groupId() }; + + if(!rsPosted->getBoardsInfo(groupIds,groups)) + { + RsErr() << "GxsPostedGroupItem::loadGroup() ERROR getting data" << std::endl; + mIsLoadingGroup = false; + return; + } + + if (groups.size() != 1) + { + std::cerr << "GxsPostedGroupItem::loadGroup() Wrong number of Items" << std::endl; + mIsLoadingGroup = false; + return; + } + RsPostedGroup group(groups[0]); + + 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 */ + + mGroupMeta = group.mMeta; + mIsLoadingGroup = false; + + }, this ); + }); +} + +void BaseBoardsCommentsItem::loadMessage() +{ + mIsLoadingMessage = true; + RsThread::async([this]() + { + // 1 - get group data + + std::vector posts; + std::vector comments; + std::vector votes; + + if(! rsPosted->getBoardContent( groupId(), std::set( { messageId() } ),posts,comments,votes)) + { + RsErr() << "BaseBoardsCommentsItem::loadMessage() ERROR getting data" << std::endl; + mIsLoadingMessage = false; + return; + } + + if (posts.size() == 1) + { + std::cerr << (void*)this << ": Obtained post, with msgId = " << posts[0].mMeta.mMsgId << std::endl; + const RsPostedPost& post(posts[0]); + + RsQThreadUtils::postToObject( [post,this]() { setPost(post,true); mIsLoadingMessage = false;}, this ); + } + else if(comments.size() == 1) + { + const RsGxsComment& cmt = comments[0]; + std::cerr << (void*)this << ": Obtained comment, setting messageId to threadID = " << cmt.mMeta.mThreadId << std::endl; + + RsQThreadUtils::postToObject( [cmt,this]() + { + setComment(cmt); + + //Change this item to be uploaded with thread element. + setMessageId(cmt.mMeta.mThreadId); + requestMessage(); + + mIsLoadingMessage = false; + }, this ); + + } + else + { + std::cerr << "GxsChannelPostItem::loadMessage() Wrong number of Items. Remove It." << std::endl; + + RsQThreadUtils::postToObject( [this]() { removeItem(); mIsLoadingMessage = false;}, this ); + } + }); +} + + +void BaseBoardsCommentsItem::loadComment() +{ +#ifdef DEBUG_ITEM + std::cerr << "GxsChannelPostItem::loadComment()"; + std::cerr << std::endl; +#endif + mIsLoadingComment = true; + RsThread::async([this]() + { + // 1 - get group data + + std::set msgIds; + + for(auto MsgId: messageVersions()) + msgIds.insert(MsgId); + + std::vector posts; + std::vector comments; + std::vector votes; + + if(! rsPosted->getBoardContent( groupId(),msgIds,posts,comments,votes)) + { + RsErr() << "BaseBoardsCommentsItem::loadGroup() ERROR getting data" << std::endl; + mIsLoadingComment = false; + return; + } + + int comNb = comments.size(); + + RsQThreadUtils::postToObject( [comNb,this]() + { + setCommentsSize(comNb); + mIsLoadingComment = false; + }, this ); + }); +} + +QString BaseBoardsCommentsItem::groupName() +{ + return QString::fromUtf8(mGroupMeta.mGroupName.c_str()); +} + +QString BaseBoardsCommentsItem::messageName() +{ + return QString::fromUtf8(mPost.mMeta.mMsgName.c_str()); +} + +void BaseBoardsCommentsItem::loadComments() +{ + std::cerr << "BaseBoardsCommentsItem::loadComments()"; + std::cerr << std::endl; + + if (mFeedHolder) + { + /* window will destroy itself! */ + PostedDialog *postedDialog = dynamic_cast(MainWindow::getPage(MainWindow::Posted)); + + if (!postedDialog) + return ; + + MainWindow::showWindow(MainWindow::Posted); + postedDialog->navigate(mPost.mMeta.mGroupId, mPost.mMeta.mMsgId) ; + } +} +void BaseBoardsCommentsItem::readToggled(bool checked) +{ + if (mInFill) { + return; + } + + RsGxsGrpMsgIdPair msgPair = std::make_pair(groupId(), messageId()); + + uint32_t token; + rsPosted->setMessageReadStatus(token, msgPair, !checked); + + setReadStatus(false, checked); +} + +void BaseBoardsCommentsItem::readAndClearItem() +{ +#ifdef DEBUG_ITEM + std::cerr << "BaseBoardsCommentsItem::readAndClearItem()"; + std::cerr << std::endl; +#endif + + readToggled(false); + removeItem(); +} +void BaseBoardsCommentsItem::copyMessageLink() +{ + if (groupId().isNull() || messageId().isNull()) { + return; + } + + RetroShareLink link = RetroShareLink::createGxsMessageLink(RetroShareLink::TYPE_POSTED, groupId(), messageId(), messageName()); + + if (link.valid()) { + QList urls; + urls.push_back(link); + RSLinkClipboard::copyLinks(urls); + } +} + +void BaseBoardsCommentsItem::showAuthorInPeople() +{ + if(mPost.mMeta.mAuthorId.isNull()) + { + std::cerr << "(EE) 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(mPost.mMeta.mAuthorId)); +} + +//======================================================================================== +// BoardsCommentsItem // +//======================================================================================== + +BoardsCommentsItem::BoardsCommentsItem(FeedHolder *feedHolder, uint32_t feedId, const RsGroupMetaData &group_meta, const RsGxsMessageId& post_id, bool isHome, bool autoUpdate) : + BaseBoardsCommentsItem(feedHolder, feedId, group_meta, post_id, isHome, autoUpdate) +{ + setup(); +} + +BoardsCommentsItem::BoardsCommentsItem(FeedHolder *feedHolder, uint32_t feedId, const RsGxsGroupId &groupId, const RsGxsMessageId& post_id, bool isHome, bool autoUpdate) : + BaseBoardsCommentsItem(feedHolder, feedId, groupId, post_id, isHome, autoUpdate) +{ + setup(); + loadGroup(); +} + +void BoardsCommentsItem::setup() +{ + /* Invoke the Qt Designer generated object setup routine */ + ui = new Ui::BoardsCommentsItem; + ui->setupUi(this); + + setAttribute(Qt::WA_DeleteOnClose, true); + + mInFill = false; + + /* clear ui */ + ui->titleLabel->setText(tr("Loading")); + ui->datetimeLabel->clear(); + ui->replyFrame->hide(); + + ui->commentButton->setIcon(FilesDefs::getIconFromQtResourcePath(":/icons/png/comment.png")); + ui->copyLinkButton->setIcon(FilesDefs::getIconFromQtResourcePath(":/icons/png/copy.png")); + ui->expandButton->setIcon(FilesDefs::getIconFromQtResourcePath(":/icons/png/down-arrow.png")); + ui->readAndClearButton->setIcon(FilesDefs::getIconFromQtResourcePath(":/icons/png/correct.png")); + ui->clearButton->setIcon(FilesDefs::getIconFromQtResourcePath(":/icons/png/exit2.png")); + + /* general ones */ + connect(ui->clearButton, SIGNAL(clicked()), this, SLOT(removeItem())); + + /* specific */ + connect(ui->commentButton, SIGNAL( clicked()), this, SLOT(loadComments())); + connect(ui->voteUpButton, SIGNAL(clicked()), this, SLOT(makeUpVote())); + connect(ui->voteDownButton, SIGNAL(clicked()), this, SLOT( makeDownVote())); + connect(ui->expandButton, SIGNAL(clicked()), this, SLOT( toggle())); + connect(ui->readButton, SIGNAL(toggled(bool)), this, SLOT(readToggled(bool))); + connect(ui->readAndClearButton, SIGNAL(clicked()), this, SLOT(readAndClearItem())); + connect(ui->copyLinkButton, SIGNAL(clicked()), this, SLOT(copyMessageLink())); + + // hide voting buttons, backend is not implemented yet + ui->voteUpButton->hide(); + ui->voteDownButton->hide(); + ui->scoreLabel->hide(); + + // hide expand button, replies is not implemented yet + ui->expandButton->hide(); + ui->unsubscribeButton->hide(); + + ui->clearButton->hide(); + ui->readAndClearButton->hide(); +} + +void BoardsCommentsItem::makeDownVote() +{ + RsGxsGrpMsgIdPair msgId; + msgId.first = mPost.mMeta.mGroupId; + msgId.second = mPost.mMeta.mMsgId; + + ui->voteUpButton->setEnabled(false); + ui->voteDownButton->setEnabled(false); + + emit vote(msgId, false); +} + +void BoardsCommentsItem::makeUpVote() +{ + RsGxsGrpMsgIdPair msgId; + msgId.first = mPost.mMeta.mGroupId; + msgId.second = mPost.mMeta.mMsgId; + + ui->voteUpButton->setEnabled(false); + ui->voteDownButton->setEnabled(false); + + emit vote(msgId, true); +} + +void BoardsCommentsItem::setComment(const RsGxsComment& cmt) +{ + ui->commLabel->setText(RsHtml().formatText(NULL, QString::fromUtf8(cmt.mComment.c_str()), RSHTML_FORMATTEXT_EMBED_LINKS)); + + ui->nameLabel->setId(cmt.mMeta.mAuthorId); + ui->datetimeLabel->setText(DateTime::formatLongDateTime(cmt.mMeta.mPublishTs)); + + RsIdentityDetails idDetails ; + rsIdentity->getIdDetails(cmt.mMeta.mAuthorId,idDetails); + QPixmap pixmap; + + if(idDetails.mAvatar.mSize == 0 || !GxsIdDetails::loadPixmapFromData(idDetails.mAvatar.mData, idDetails.mAvatar.mSize, pixmap,GxsIdDetails::SMALL)) + pixmap = GxsIdDetails::makeDefaultIcon(cmt.mMeta.mAuthorId,GxsIdDetails::SMALL); + ui->avatarLabel->setPixmap(pixmap); + +} +void BoardsCommentsItem::setCommentsSize(int comNb) +{ + QString sComButText = tr("Comment"); + if (comNb == 1) + sComButText = sComButText.append("(1)"); + else if(comNb > 1) + sComButText = tr("Comments ").append("(%1)").arg(comNb); + + ui->commentButton->setText(sComButText); +} + +void BoardsCommentsItem::fill() +{ + + ui->logoLabel->setPixmap( FilesDefs::getPixmapFromQtResourcePath(":/icons/png/comment.png")); + + RetroShareLink link = RetroShareLink::createGxsGroupLink(RetroShareLink::TYPE_POSTED, mGroupMeta.mGroupId, groupName()); + ui->titleLabel->setText(link.toHtml()); + + RetroShareLink msgLink = RetroShareLink::createGxsMessageLink(RetroShareLink::TYPE_POSTED, mPost.mMeta.mGroupId, mPost.mMeta.mMsgId, messageName()); + ui->subjectLabel->setText(msgLink.toHtml()); + + mInFill = true; + + //QString score = QString::number(mPost.mTopScore); + //ui->scoreLabel->setText(score); + + // differences between Feed or Top of Comment. + if (mFeedHolder) + { + // feed. + //frame_comment->show(); + ui->commentButton->show(); + + if (mPost.mComments) + { + QString commentText = QString::number(mPost.mComments); + commentText += " "; + commentText += tr("Comments"); + ui->commentButton->setText(commentText); + } + else + { + ui->commentButton->setText(tr("Comment")); + } + + //setReadStatus(IS_MSG_NEW(mPost.mMeta.mMsgStatus), IS_MSG_UNREAD(mPost.mMeta.mMsgStatus) || IS_MSG_NEW(mPost.mMeta.mMsgStatus)); + } + else + { + // no feed. + //frame_comment->hide(); + ui->commentButton->hide(); + + ui->readButton->hide(); + } + + if (mIsHome) + { + ui->clearButton->hide(); + ui->readAndClearButton->hide(); + } + else + { + ui->clearButton->show(); + ui->readAndClearButton->show(); + } + + // hide read button not yet functional + ui->readButton->hide(); + + // disable voting buttons - if they have already voted. + if (mPost.mMeta.mMsgStatus & GXS_SERV::GXS_MSG_STATUS_VOTE_MASK) + { + ui->voteUpButton->setEnabled(false); + ui->voteDownButton->setEnabled(false); + } + + mInFill = false; + + emit sizeChanged(this); +} + +void BoardsCommentsItem::setReadStatus(bool isNew, bool isUnread) +{ + if (isUnread) + { + ui->readButton->setChecked(true); + ui->readButton->setIcon(FilesDefs::getIconFromQtResourcePath(":/images/message-state-unread.png")); + } + else + { + ui->readButton->setChecked(false); + ui->readButton->setIcon(FilesDefs::getIconFromQtResourcePath(":/images/message-state-read.png")); + } + + ui->mainFrame->setProperty("new", isNew); + ui->mainFrame->style()->unpolish(ui->mainFrame); + ui->mainFrame->style()->polish( ui->mainFrame); +} + +void BoardsCommentsItem::toggle() +{ + expand(ui->replyFrame->isHidden()); +} + +void BoardsCommentsItem::doExpand(bool open) +{ + if (open) + { + ui->replyFrame->show(); + ui->expandButton->setIcon(FilesDefs::getIconFromQtResourcePath(QString(":/icons/png/up-arrow.png"))); + ui->expandButton->setToolTip(tr("Hide")); + } + else + { + ui->replyFrame->hide(); + ui->expandButton->setIcon(FilesDefs::getIconFromQtResourcePath(QString(":/icons/png/down-arrow.png"))); + ui->expandButton->setToolTip(tr("Expand")); + } + + emit sizeChanged(this); + +} diff --git a/retroshare-gui/src/gui/feeds/BoardsCommentsItem.h b/retroshare-gui/src/gui/feeds/BoardsCommentsItem.h new file mode 100644 index 000000000..d5bff44b7 --- /dev/null +++ b/retroshare-gui/src/gui/feeds/BoardsCommentsItem.h @@ -0,0 +1,125 @@ +/******************************************************************************* + * retroshare-gui/src/gui/Posted/BoardsCommentsItem.h * + * * + * Copyright (C) 2020 by 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 MRK_BOARDSCOMMENTS_ITEM_H +#define MRK_BOARDSCOMMENTS_ITEM_H + +#include + +#include +#include "gui/gxs/GxsFeedItem.h" + +namespace Ui { +class BoardsCommentsItem; +} + +class FeedHolder; +struct RsPostedPost; + +class BaseBoardsCommentsItem : public GxsFeedItem +{ + Q_OBJECT + +public: + BaseBoardsCommentsItem(FeedHolder *parent, uint32_t feedId, const RsGxsGroupId& groupId, const RsGxsMessageId& messageId, bool isHome, bool autoUpdate); + BaseBoardsCommentsItem(FeedHolder *parent, uint32_t feedId, const RsGroupMetaData& group_meta, const RsGxsMessageId& post_id, bool isHome, bool autoUpdate); + virtual ~BaseBoardsCommentsItem(); + + bool setPost(const RsPostedPost& post, bool doFill = true); + + const RsPostedPost& getPost() const { return mPost ; } + RsPostedPost& getPost() { return mPost ; } + + uint64_t uniqueIdentifier() const override { return hash_64bits("BoardsCommentsItem " + messageId().toStdString()); } + +private slots: + void loadComments(); + void readToggled(bool checked); + void readAndClearItem(); + void copyMessageLink(); + void showAuthorInPeople(); + +signals: + void vote(const RsGxsGrpMsgIdPair& msgId, bool up); + +protected: + /* FeedItem */ + virtual void paintEvent(QPaintEvent *) override; + + /* GxsGroupFeedItem */ + virtual QString groupName() override; + virtual void loadGroup() override; + virtual RetroShareLink::enumType getLinkType() override { return RetroShareLink::TYPE_UNKNOWN; } + + /* GxsFeedItem */ + virtual QString messageName() override; + + virtual void loadMessage() override; + virtual void loadComment() override; + + bool mInFill; + RsGroupMetaData mGroupMeta; + RsPostedPost mPost; + + virtual void setup()=0; + virtual void fill()=0; + virtual void doExpand(bool open) override =0; + virtual void setComment(const RsGxsComment&)=0; + virtual void setReadStatus(bool isNew, bool isUnread)=0; + virtual void setCommentsSize(int comNb)=0; + virtual void makeUpVote()=0; + virtual void makeDownVote()=0; + +private: + bool mLoaded; + bool mIsLoadingGroup; + bool mIsLoadingMessage; + bool mIsLoadingComment; +}; + +class BoardsCommentsItem: public BaseBoardsCommentsItem +{ + Q_OBJECT + +public: + BoardsCommentsItem(FeedHolder *parent, uint32_t feedId, const RsGxsGroupId& groupId, const RsGxsMessageId& messageId, bool isHome, bool autoUpdate); + BoardsCommentsItem(FeedHolder *parent, uint32_t feedId, const RsGroupMetaData& group_meta, const RsGxsMessageId& post_id, bool isHome, bool autoUpdate); + +protected: + void setup() override; + void fill() override; + void setComment(const RsGxsComment&) override; + void setReadStatus(bool isNew, bool isUnread) override; + void setCommentsSize(int comNb) override; + +private slots: + void doExpand(bool open); + void toggle(); + void makeUpVote(); + void makeDownVote(); + +private: + /** Qt Designer generated object */ + Ui::BoardsCommentsItem *ui; +}; + +//Q_DECLARE_METATYPE(RsPostedPost) + +#endif diff --git a/retroshare-gui/src/gui/feeds/BoardsCommentsItem.ui b/retroshare-gui/src/gui/feeds/BoardsCommentsItem.ui new file mode 100644 index 000000000..c13e9685c --- /dev/null +++ b/retroshare-gui/src/gui/feeds/BoardsCommentsItem.ui @@ -0,0 +1,588 @@ + + + BoardsCommentsItem + + + + 0 + 0 + 755 + 201 + + + + + 1 + + + 1 + + + 1 + + + 1 + + + + + + 0 + 0 + + + + true + + + QFrame::Box + + + QFrame::Sunken + + + + + + + + + 0 + 0 + + + + + 70 + 70 + + + + + 70 + 70 + + + + + + + true + + + Qt::AlignCenter + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + 9 + + + 9 + + + + + + 0 + 0 + + + + + 75 + true + + + + New Comment + + + true + + + + + + + + 75 + true + + + + POST TITLE + + + true + + + + + + + + 0 + 0 + + + + + 75 + true + true + + + + Channel Subject + + + false + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 75 + true + + + + DateTime + + + + + + + + + + 0 + 0 + + + + + + + + QLayout::SetDefaultConstraint + + + 0 + + + 0 + + + 0 + + + 0 + + + 6 + + + 0 + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 288 + 17 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 0 + 0 + + + + + 75 + true + + + + Name + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + 32 + 32 + + + + + 32 + 32 + + + + Avatar + + + + + + + + 0 + 0 + + + + + MS Sans Serif + 10 + + + + Comm value + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + + + + 8 + + + + + + 24 + 16777215 + + + + Qt::NoFocus + + + Toggle Message Read Status + + + true + + + false + + + false + + + + + + + + 75 + true + + + + 0 + + + + + + + I like this + + + + + + + + + + I dislike this + + + + + + + + + + + MS Sans Serif + 10 + 75 + true + + + + Comments + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + Qt::NoFocus + + + Unsubscribe From Channel + + + Unsubscribe + + + + + + + + 0 + 0 + + + + Qt::NoFocus + + + Copy RetroShare Link + + + + + + + + + + + 0 + 0 + + + + Qt::NoFocus + + + Expand + + + + + + + + 0 + 0 + + + + Qt::NoFocus + + + Set as read and remove item + + + + + + + + 0 + 0 + + + + Qt::NoFocus + + + Remove Item + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QFrame::Box + + + QFrame::Sunken + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + + 0 + 0 + + + + true + + + 6 + + + -1 + + + true + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + + + + + + + + + + GxsIdLabel + QLabel +
gui/gxs/GxsIdLabel.h
+
+
+ + + + +
diff --git a/retroshare-gui/src/gui/feeds/ChannelsCommentsItem.cpp b/retroshare-gui/src/gui/feeds/ChannelsCommentsItem.cpp new file mode 100644 index 000000000..6b80de690 --- /dev/null +++ b/retroshare-gui/src/gui/feeds/ChannelsCommentsItem.cpp @@ -0,0 +1,650 @@ +/******************************************************************************* + * gui/feeds/ChannelsCommentsItem.cpp * + * * + * Copyright (c) 2020, 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 + +#include "gui/gxs/GxsIdDetails.h" +#include "gui/common/FilesDefs.h" +#include "rshare.h" +#include "ChannelsCommentsItem.h" +#include "ui_ChannelsCommentsItem.h" + +#include "FeedHolder.h" +#include "SubFileItem.h" +#include "util/misc.h" +#include "util/qtthreadsutils.h" +#include "gui/RetroShareLink.h" +#include "util/HandleRichText.h" +#include "util/DateTime.h" +#include "util/stringutil.h" +#include "gui/gxschannels/CreateGxsChannelMsg.h" +#include "gui/gxschannels/GxsChannelDialog.h" +#include "gui/MainWindow.h" + +#include + +#include +#include + +/**** + * #define DEBUG_ITEM 1 + ****/ + +ChannelsCommentsItem::ChannelsCommentsItem(FeedHolder *feedHolder, uint32_t feedId, const RsGroupMetaData& group_meta, const RsGxsMessageId &messageId, bool isHome, bool autoUpdate,const std::set& older_versions) : + GxsFeedItem(feedHolder, feedId, group_meta.mGroupId, messageId, isHome, rsGxsChannels, autoUpdate), + mGroupMeta(group_meta) +{ + mPost.mMeta.mMsgId = messageId; // useful for uniqueIdentifer() before the post is loaded + mPost.mMeta.mGroupId = mGroupMeta.mGroupId; + + QVector v; + //bool self = false; + + for(std::set::const_iterator it(older_versions.begin());it!=older_versions.end();++it) + v.push_back(*it) ; + + if(older_versions.find(messageId) == older_versions.end()) + v.push_back(messageId); + + setMessageVersions(v) ; + setup(); + + // no call to loadGroup() here because we have it already. +} + +ChannelsCommentsItem::ChannelsCommentsItem(FeedHolder *feedHolder, uint32_t feedId, const RsGxsGroupId& groupId, const RsGxsMessageId &messageId, bool isHome, bool autoUpdate,const std::set& older_versions) : + GxsFeedItem(feedHolder, feedId, groupId, messageId, isHome, rsGxsChannels, autoUpdate) // this one should be in GxsFeedItem +{ + mPost.mMeta.mMsgId = messageId; // useful for uniqueIdentifer() before the post is loaded + + QVector v; + //bool self = false; + + for(std::set::const_iterator it(older_versions.begin());it!=older_versions.end();++it) + v.push_back(*it) ; + + if(older_versions.find(messageId) == older_versions.end()) + v.push_back(messageId); + + setMessageVersions(v) ; + setup(); + + loadGroup(); +} + +void ChannelsCommentsItem::paintEvent(QPaintEvent *e) +{ + /* This method employs a trick to trigger a deferred loading. The post and group is requested only + * when actually displayed on the screen. */ + + if(!mLoaded) + { + mLoaded = true ; + + std::set older_versions; // not so nice. We need to use std::set everywhere + for(auto& m:messageVersions()) + older_versions.insert(m); + + fill(); + requestMessage(); + requestComment(); + } + + GxsFeedItem::paintEvent(e) ; +} + +ChannelsCommentsItem::~ChannelsCommentsItem() +{ + delete(ui); +} + +bool ChannelsCommentsItem::isUnread() const +{ + return IS_MSG_UNREAD(mPost.mMeta.mMsgStatus) ; +} + +void ChannelsCommentsItem::setup() +{ + /* Invoke the Qt Designer generated object setup routine */ + + ui = new Ui::ChannelsCommentsItem; + ui->setupUi(this); + + // Manually set icons to allow to use clever resource sharing that is missing in Qt for Icons loaded from Qt resource file. + // This is particularly important here because a channel may contain many posts, so duplicating the QImages here is deadly for the + // memory. + + ui->logoLabel->setPixmap(FilesDefs::getPixmapFromQtResourcePath(":/icons/png/comment.png")); + ui->readButton->setIcon(FilesDefs::getIconFromQtResourcePath(":/images/message-state-unread.png")); + ui->voteUpButton->setIcon(FilesDefs::getIconFromQtResourcePath(":/images/vote_up.png")); + ui->voteDownButton->setIcon(FilesDefs::getIconFromQtResourcePath(":/images/vote_down.png")); + ui->commentButton->setIcon(FilesDefs::getIconFromQtResourcePath(":/icons/png/comment.png")); + ui->copyLinkButton->setIcon(FilesDefs::getIconFromQtResourcePath(":/icons/png/copy.png")); + ui->expandButton->setIcon(FilesDefs::getIconFromQtResourcePath(":/icons/png/down-arrow.png")); + ui->readAndClearButton->setIcon(FilesDefs::getIconFromQtResourcePath(":/icons/png/correct.png")); + ui->clearButton->setIcon(FilesDefs::getIconFromQtResourcePath(":/icons/png/exit2.png")); + + setAttribute(Qt::WA_DeleteOnClose, true); + + mInFill = false; + mCloseOnRead = false; + mLoaded = false; + + /* clear ui */ + ui->titleLabel->setText(tr("Loading...")); + ui->datetimelabel->clear(); + + /* general ones */ + connect(ui->expandButton, SIGNAL(clicked()), this, SLOT(toggle())); + connect(ui->clearButton, SIGNAL(clicked()), this, SLOT(removeItem())); + + /* specific */ + connect(ui->readAndClearButton, SIGNAL(clicked()), this, SLOT(readAndClearItem())); + connect(ui->unsubscribeButton, SIGNAL(clicked()), this, SLOT(unsubscribeChannel())); + + // HACK FOR NOW. + ui->commentButton->hide();// hidden until properly enabled. + connect(ui->commentButton, SIGNAL(clicked()), this, SLOT(loadComments())); + connect(ui->copyLinkButton, SIGNAL(clicked()), this, SLOT(copyMessageLink())); + + connect(ui->readButton, SIGNAL(toggled(bool)), this, SLOT(readToggled(bool))); + + // hide voting buttons, backend is not implemented yet + ui->voteUpButton->hide(); + ui->voteDownButton->hide(); + //connect(ui-> voteUpButton, SIGNAL(clicked()), this, SLOT(makeUpVote())); + //connect(ui->voteDownButton, SIGNAL(clicked()), this, SLOT(makeDownVote())); + + ui->scoreLabel->hide(); + + // hide expand button, replies is not implemented yet + ui->expandButton->hide(); + ui->unsubscribeButton->hide(); + + ui->titleLabel->setMinimumWidth(100); + + ui->mainFrame->setProperty("new", false); + ui->mainFrame->style()->unpolish(ui->mainFrame); + ui->mainFrame->style()->polish( ui->mainFrame); + + ui->expandFrame->hide(); +} + +bool ChannelsCommentsItem::setPost(const RsGxsChannelPost &post, bool doFill) +{ + if (groupId() != post.mMeta.mGroupId || messageId() != post.mMeta.mMsgId) { + std::cerr << "ChannelsCommentsItem::setPost() - Wrong id, cannot set post"; + std::cerr << std::endl; + return false; + } + + mPost = post; + + if (doFill) { + fill(); + } + + return true; +} + +QString ChannelsCommentsItem::getTitleLabel() +{ + return QString::fromUtf8(mPost.mMeta.mMsgName.c_str()); +} + +QString ChannelsCommentsItem::getMsgLabel() +{ + //return RsHtml().formatText(NULL, QString::fromUtf8(mPost.mMsg.c_str()), RSHTML_FORMATTEXT_EMBED_SMILEYS | RSHTML_FORMATTEXT_EMBED_LINKS); + // Disabled, because emoticon replacement kills performance. + return QString::fromUtf8(mPost.mMsg.c_str()); +} + +QString ChannelsCommentsItem::groupName() +{ + return QString::fromUtf8(mGroupMeta.mGroupName.c_str()); +} + +void ChannelsCommentsItem::loadComments() +{ + /* window will destroy itself! */ + GxsChannelDialog *channelDialog = dynamic_cast(MainWindow::getPage(MainWindow::Channels)); + + if (!channelDialog) + return ; + + MainWindow::showWindow(MainWindow::Channels); + channelDialog->navigate(mPost.mMeta.mGroupId, mPost.mMeta.mMsgId); +} + +void ChannelsCommentsItem::loadGroup() +{ +#ifdef DEBUG_ITEM + std::cerr << "GxsChannelGroupItem::loadGroup()"; + std::cerr << std::endl; +#endif + + RsThread::async([this]() + { + // 1 - get group data + + std::vector groups; + const std::list groupIds = { groupId() }; + + if(!rsGxsChannels->getChannelsInfo(groupIds,groups)) // would be better to call channel Summaries for a single group + { + RsErr() << "GxsGxsChannelGroupItem::loadGroup() ERROR getting data" << std::endl; + return; + } + + if (groups.size() != 1) + { + std::cerr << "GxsGxsChannelGroupItem::loadGroup() Wrong number of Items"; + std::cerr << std::endl; + return; + } + RsGxsChannelGroup group(groups[0]); + + 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 */ + + mGroupMeta = group.mMeta; + + }, this ); + }); +} +void ChannelsCommentsItem::loadMessage() +{ +#ifdef DEBUG_ITEM + std::cerr << "ChannelsCommentsItem::loadMessage()"; + std::cerr << std::endl; +#endif + RsThread::async([this]() + { + // 1 - get group data + + std::vector posts; + std::vector comments; + std::vector votes; + + if(! rsGxsChannels->getChannelContent( groupId(), std::set( { messageId() } ),posts,comments,votes)) + { + RsErr() << "GxsGxsChannelGroupItem::loadGroup() ERROR getting data" << std::endl; + return; + } + + if (posts.size() == 1) + { +#ifdef DEBUG_ITEM + std::cerr << (void*)this << ": Obtained post, with msgId = " << posts[0].mMeta.mMsgId << std::endl; +#endif + const RsGxsChannelPost& post(posts[0]); + + RsQThreadUtils::postToObject( [post,this]() { setPost(post); }, this ); + } + else if(comments.size() == 1) + { + const RsGxsComment& cmt = comments[0]; +#ifdef DEBUG_ITEM + std::cerr << (void*)this << ": Obtained comment, setting messageId to threadID = " << cmt.mMeta.mThreadId << std::endl; +#endif + + RsQThreadUtils::postToObject( [cmt,this]() + { + uint32_t autorized_lines = (int)floor((ui->logoLabel->height() - ui->titleLabel->height() - ui->buttonHLayout->sizeHint().height())/QFontMetricsF(ui->subjectLabel->font()).height()); + + ui->commLabel->setText(RsHtml().formatText(NULL, RsStringUtil::CopyLines(QString::fromUtf8(cmt.mComment.c_str()), autorized_lines), RSHTML_FORMATTEXT_EMBED_LINKS)); + + ui->nameLabel->setId(cmt.mMeta.mAuthorId); + ui->datetimelabel->setText(DateTime::formatLongDateTime(cmt.mMeta.mPublishTs)); + + RsIdentityDetails idDetails ; + rsIdentity->getIdDetails(cmt.mMeta.mAuthorId,idDetails); + QPixmap pixmap ; + + if(idDetails.mAvatar.mSize == 0 || !GxsIdDetails::loadPixmapFromData(idDetails.mAvatar.mData, idDetails.mAvatar.mSize, pixmap,GxsIdDetails::SMALL)) + pixmap = GxsIdDetails::makeDefaultIcon(cmt.mMeta.mAuthorId,GxsIdDetails::SMALL); + ui->avatarLabel->setPixmap(pixmap); + + //Change this item to be uploaded with thread element. + setMessageId(cmt.mMeta.mThreadId); + requestMessage(); + + }, this ); + + } + else + { +#ifdef DEBUG_ITEM + std::cerr << "ChannelsCommentsItem::loadMessage() Wrong number of Items. Remove It."; + std::cerr << std::endl; +#endif + + RsQThreadUtils::postToObject( [this]() { removeItem(); }, this ); + } + }); +} + +void ChannelsCommentsItem::loadComment() +{ +#ifdef DEBUG_ITEM + std::cerr << "ChannelsCommentsItem::loadComment()"; + std::cerr << std::endl; +#endif + + RsThread::async([this]() + { + // 1 - get group data + + std::set msgIds; + + for(auto MsgId: messageVersions()) + msgIds.insert(MsgId); + + std::vector posts; + std::vector comments; + + if(! rsGxsChannels->getChannelComments( groupId(),msgIds,comments)) + { + RsErr() << "GxsGxsChannelGroupItem::loadGroup() ERROR getting data" << std::endl; + return; + } + + int comNb = comments.size(); + + RsQThreadUtils::postToObject( [comNb,this]() + { + QString sComButText = tr("Comment"); + if (comNb == 1) + sComButText = sComButText.append("(1)"); + else if(comNb > 1) + sComButText = tr("Comments ").append("(%1)").arg(comNb); + + ui->commentButton->setText(sComButText); + + }, this ); + }); +} + +void ChannelsCommentsItem::fill() +{ + /* fill in */ + +// if (isLoading()) { + // /* Wait for all requests */ + //return; +// } + +#ifdef DEBUG_ITEM + std::cerr << "ChannelsCommentsItem::fill()"; + std::cerr << std::endl; +#endif + + mInFill = true; + + QString title; + //float f = QFontMetricsF(font()).height()/14.0 ; + + if (!mIsHome) + { + if (mCloseOnRead && !IS_MSG_NEW(mPost.mMeta.mMsgStatus)) { + removeItem(); + } + + RetroShareLink link = RetroShareLink::createGxsGroupLink(RetroShareLink::TYPE_CHANNEL, mPost.mMeta.mGroupId, groupName()); + title += link.toHtml(); + ui->titleLabel->setText(title); + + RetroShareLink msgLink = RetroShareLink::createGxsMessageLink(RetroShareLink::TYPE_CHANNEL, mPost.mMeta.mGroupId, mPost.mMeta.mMsgId, messageName()); + ui->subjectLabel->setText(msgLink.toHtml()); + + if (IS_GROUP_SUBSCRIBED(mGroupMeta.mSubscribeFlags) || IS_GROUP_ADMIN(mGroupMeta.mSubscribeFlags)) + { + ui->unsubscribeButton->setEnabled(true); + } + else + { + ui->unsubscribeButton->setEnabled(false); + } + ui->readButton->hide(); + + if (IS_MSG_NEW(mPost.mMeta.mMsgStatus)) { + mCloseOnRead = true; + } + } + else + { + /* subject */ + ui->titleLabel->setText(QString::fromUtf8(mPost.mMeta.mMsgName.c_str())); + + uint32_t autorized_lines = (int)floor((ui->logoLabel->height() - ui->titleLabel->height() - ui->buttonHLayout->sizeHint().height())/QFontMetricsF(ui->subjectLabel->font()).height()); + + // fill first 4 lines of message. (csoler) Disabled the replacement of smileys and links, because the cost is too crazy + //ui->subjectLabel->setText(RsHtml().formatText(NULL, RsStringUtil::CopyLines(QString::fromUtf8(mPost.mMsg.c_str()), autorized_lines), RSHTML_FORMATTEXT_EMBED_SMILEYS | RSHTML_FORMATTEXT_EMBED_LINKS)); + + ui->subjectLabel->setText(RsStringUtil::CopyLines(QString::fromUtf8(mPost.mMsg.c_str()), 2)) ; + + //QString score = QString::number(post.mTopScore); + // scoreLabel->setText(score); + + /* disable buttons: deletion facility not enabled with cache services yet */ + ui->clearButton->setEnabled(false); + ui->unsubscribeButton->setEnabled(false); + ui->clearButton->hide(); + ui->readAndClearButton->hide(); + ui->unsubscribeButton->hide(); + ui->copyLinkButton->show(); + + if (IS_GROUP_SUBSCRIBED(mGroupMeta.mSubscribeFlags) || IS_GROUP_ADMIN(mGroupMeta.mSubscribeFlags)) + { + ui->readButton->setVisible(true); + + setReadStatus(IS_MSG_NEW(mPost.mMeta.mMsgStatus), IS_MSG_UNREAD(mPost.mMeta.mMsgStatus) || IS_MSG_NEW(mPost.mMeta.mMsgStatus)); + } + else + { + ui->readButton->setVisible(false); + } + + mCloseOnRead = false; + } + + // differences between Feed or Top of Comment. + if (mFeedHolder) + { + ui->commentButton->show(); + + // Not yet functional + /*if (mPost.mCommentCount) + { + QString commentText = QString::number(mPost.mCommentCount); + commentText += " "; + commentText += tr("Comments"); + ui->commentButton->setText(commentText); + } + else + { + ui->commentButton->setText(tr("Comment")); + }*/ + + } + else + { + ui->commentButton->hide(); + } + + // disable voting buttons - if they have already voted. + /*if (post.mMeta.mMsgStatus & GXS_SERV::GXS_MSG_STATUS_VOTE_MASK) + { + voteUpButton->setEnabled(false); + voteDownButton->setEnabled(false); + }*/ + + if (wasExpanded() || ui->expandFrame->isVisible()) { + fillExpandFrame(); + } + + mInFill = false; +} + +void ChannelsCommentsItem::fillExpandFrame() +{ + //ui->msgLabel->setText(RsHtml().formatText(NULL, QString::fromUtf8(mPost.mMsg.c_str()), RSHTML_FORMATTEXT_EMBED_SMILEYS | RSHTML_FORMATTEXT_EMBED_LINKS)); + +} + +QString ChannelsCommentsItem::messageName() +{ + return QString::fromUtf8(mPost.mMeta.mMsgName.c_str()); +} + +void ChannelsCommentsItem::setReadStatus(bool isNew, bool isUnread) +{ + if (isNew) + mPost.mMeta.mMsgStatus |= GXS_SERV::GXS_MSG_STATUS_GUI_NEW; + else + mPost.mMeta.mMsgStatus &= ~GXS_SERV::GXS_MSG_STATUS_GUI_NEW; + + if (isUnread) + { + mPost.mMeta.mMsgStatus |= GXS_SERV::GXS_MSG_STATUS_GUI_UNREAD; + whileBlocking(ui->readButton)->setChecked(true); + ui->readButton->setIcon(FilesDefs::getIconFromQtResourcePath(":/images/message-state-unread.png")); + } + else + { + mPost.mMeta.mMsgStatus &= ~GXS_SERV::GXS_MSG_STATUS_GUI_UNREAD; + whileBlocking(ui->readButton)->setChecked(false); + ui->readButton->setIcon(FilesDefs::getIconFromQtResourcePath(":/images/message-state-read.png")); + } + + ui->mainFrame->setProperty("new", isNew); + ui->mainFrame->style()->unpolish(ui->mainFrame); + ui->mainFrame->style()->polish( ui->mainFrame); +} + +void ChannelsCommentsItem::doExpand(bool open) +{ + if (mFeedHolder) + { + mFeedHolder->lockLayout(this, true); + } + + if (open) + { + ui->expandFrame->show(); + ui->expandButton->setIcon(FilesDefs::getIconFromQtResourcePath(QString(":/icons/png/up-arrow.png"))); + ui->expandButton->setToolTip(tr("Hide")); + + readToggled(false); + } + else + { + ui->expandFrame->hide(); + ui->expandButton->setIcon(FilesDefs::getIconFromQtResourcePath(QString(":/icons/png/down-arrow.png"))); + ui->expandButton->setToolTip(tr("Expand")); + } + + emit sizeChanged(this); + + if (mFeedHolder) + { + mFeedHolder->lockLayout(this, false); + } +} + +void ChannelsCommentsItem::expandFill(bool first) +{ + GxsFeedItem::expandFill(first); + + if (first) { + fillExpandFrame(); + } +} + +void ChannelsCommentsItem::toggle() +{ + expand(ui->expandFrame->isHidden()); +} + +/*********** SPECIFIC FUNCTIONS ***********************/ + +void ChannelsCommentsItem::readAndClearItem() +{ +#ifdef DEBUG_ITEM + std::cerr << "ChannelsCommentsItem::readAndClearItem()"; + std::cerr << std::endl; +#endif + readToggled(false); + removeItem(); +} + +void ChannelsCommentsItem::unsubscribeChannel() +{ +#ifdef DEBUG_ITEM + std::cerr << "ChannelsCommentsItem::unsubscribeChannel()"; + std::cerr << std::endl; +#endif + + unsubscribe(); +} + +void ChannelsCommentsItem::readToggled(bool /*checked*/) +{ + if (mInFill) { + return; + } + + mCloseOnRead = false; + + RsGxsGrpMsgIdPair msgPair = std::make_pair(groupId(), messageId()); + + rsGxsChannels->markRead(msgPair, isUnread()); + + //setReadStatus(false, checked); // Updated by events +} + +void ChannelsCommentsItem::makeDownVote() +{ + RsGxsGrpMsgIdPair msgId; + msgId.first = mPost.mMeta.mGroupId; + msgId.second = mPost.mMeta.mMsgId; + + ui->voteUpButton->setEnabled(false); + ui->voteDownButton->setEnabled(false); + + emit vote(msgId, false); +} + +void ChannelsCommentsItem::makeUpVote() +{ + RsGxsGrpMsgIdPair msgId; + msgId.first = mPost.mMeta.mGroupId; + msgId.second = mPost.mMeta.mMsgId; + + ui->voteUpButton->setEnabled(false); + ui->voteDownButton->setEnabled(false); + + emit vote(msgId, true); +} diff --git a/retroshare-gui/src/gui/feeds/ChannelsCommentsItem.h b/retroshare-gui/src/gui/feeds/ChannelsCommentsItem.h new file mode 100644 index 000000000..53537d61e --- /dev/null +++ b/retroshare-gui/src/gui/feeds/ChannelsCommentsItem.h @@ -0,0 +1,125 @@ +/******************************************************************************* + * gui/feeds/ChannelsCommentsItem.h * + * * + * Copyright (c) 2020, 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 _CHANNELS_COMMENTS_ITEM_H +#define _CHANNELS_COMMENTS_ITEM_H + +#include + +#include +#include "gui/gxs/GxsFeedItem.h" + +namespace Ui { +class ChannelsCommentsItem; +} + +class FeedHolder; +class SubFileItem; + +class ChannelsCommentsItem : public GxsFeedItem +{ + Q_OBJECT + +public: + // This one is used in NewFeed for incoming channel posts. Only the group and msg ids are known at this point. + // It can be used for all apparences of channel posts. But in rder to merge comments from the previous versions of the post, the list of + // previous posts should be supplied. It's optional. If not supplied only the comments of the new version will be displayed. + + ChannelsCommentsItem(FeedHolder *feedHolder, uint32_t feedId, const RsGxsGroupId& groupId, const RsGxsMessageId &messageId, bool isHome, bool autoUpdate, const std::set& older_versions = std::set()); + + // This one is used in channel thread widget. We don't want the group data to reload at every post, so we load it in the hosting + // GxsChannelsPostsWidget and pass it to created items. + + ChannelsCommentsItem(FeedHolder *feedHolder, uint32_t feedId, const RsGroupMetaData& group, const RsGxsMessageId &messageId, bool isHome, bool autoUpdate, const std::set& older_versions = std::set()); + + virtual ~ChannelsCommentsItem(); + + uint64_t uniqueIdentifier() const override { return hash_64bits("ChannelsCommentsItem " + messageId().toStdString()) ; } + + bool setGroup(const RsGxsChannelGroup& group, bool doFill = true); + bool setPost(const RsGxsChannelPost& post, bool doFill = true); + + QString getTitleLabel(); + QString getMsgLabel(); + + bool isLoaded() const {return mLoaded;}; + bool isUnread() const ; + void setReadStatus(bool isNew, bool isUnread); + + const std::set& olderVersions() const { return mPost.mOlderVersions; } + + static uint64_t computeIdentifier(const RsGxsMessageId& msgid) { return hash64("ChannelsCommentsItem " + msgid.toStdString()) ; } +protected: + //void init(const RsGxsMessageId& messageId,const std::set& older_versions); + + /* FeedItem */ + virtual void doExpand(bool open); + virtual void expandFill(bool first); + + // This does nothing except triggering the loading of the post data and comments. This function is mainly used to detect + // when the post is actually made visible. + + virtual void paintEvent(QPaintEvent *) override; + + /* GxsGroupFeedItem */ + virtual QString groupName(); + virtual void loadGroup() override; + virtual RetroShareLink::enumType getLinkType() { return RetroShareLink::TYPE_CHANNEL; } + + /* GxsFeedItem */ + virtual QString messageName(); + virtual void loadMessage(); + virtual void loadComment(); + +private slots: + /* default stuff */ + void toggle() override; + void readAndClearItem(); + + void loadComments(); + void readToggled(bool checked); + void unsubscribeChannel(); + + void makeUpVote(); + void makeDownVote(); + +signals: + void vote(const RsGxsGrpMsgIdPair& msgId, bool up); + +private: + void setup(); + void fill(); + void fillExpandFrame(); + +private: + bool mInFill; + bool mCloseOnRead; + bool mLoaded; + + RsGroupMetaData mGroupMeta; + RsGxsChannelPost mPost; + + /** Qt Designer generated object */ + Ui::ChannelsCommentsItem *ui; +}; + +//Q_DECLARE_METATYPE(RsGxsChannelPost) + +#endif diff --git a/retroshare-gui/src/gui/feeds/ChannelsCommentsItem.ui b/retroshare-gui/src/gui/feeds/ChannelsCommentsItem.ui new file mode 100644 index 000000000..83759a9ea --- /dev/null +++ b/retroshare-gui/src/gui/feeds/ChannelsCommentsItem.ui @@ -0,0 +1,588 @@ + + + ChannelsCommentsItem + + + + 0 + 0 + 755 + 201 + + + + + 1 + + + 1 + + + 1 + + + 1 + + + + + + 0 + 0 + + + + true + + + QFrame::Box + + + QFrame::Sunken + + + + + + + + + 0 + 0 + + + + + 70 + 70 + + + + + 70 + 70 + + + + + + + true + + + Qt::AlignCenter + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + 9 + + + 9 + + + + + + 0 + 0 + + + + + 75 + true + + + + New Comment + + + true + + + + + + + + 75 + true + + + + POST TITLE + + + true + + + + + + + + 0 + 0 + + + + + 75 + true + true + + + + Channel Subject + + + false + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 75 + true + + + + DateTime + + + + + + + + + + 0 + 0 + + + + + + + + QLayout::SetDefaultConstraint + + + 0 + + + 0 + + + 0 + + + 0 + + + 6 + + + 0 + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 288 + 17 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 0 + 0 + + + + + 75 + true + + + + Name + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + 32 + 32 + + + + + 32 + 32 + + + + Avatar + + + + + + + + 0 + 0 + + + + + MS Sans Serif + 10 + + + + Comm value + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + + + + 8 + + + + + + 24 + 16777215 + + + + Qt::NoFocus + + + Toggle Message Read Status + + + true + + + false + + + false + + + + + + + + 75 + true + + + + 0 + + + + + + + I like this + + + + + + + + + + I dislike this + + + + + + + + + + + MS Sans Serif + 10 + 75 + true + + + + Comments + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + Qt::NoFocus + + + Unsubscribe From Channel + + + Unsubscribe + + + + + + + + 0 + 0 + + + + Qt::NoFocus + + + Copy RetroShare Link + + + + + + + + + + + 0 + 0 + + + + Qt::NoFocus + + + Expand + + + + + + + + 0 + 0 + + + + Qt::NoFocus + + + Set as read and remove item + + + + + + + + 0 + 0 + + + + Qt::NoFocus + + + Remove Item + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QFrame::Box + + + QFrame::Sunken + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + + 0 + 0 + + + + true + + + 6 + + + -1 + + + true + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + + + + + + + + + + GxsIdLabel + QLabel +
gui/gxs/GxsIdLabel.h
+
+
+ + + + +
diff --git a/retroshare-gui/src/gui/gxschannels/GxsChannelDialog.cpp b/retroshare-gui/src/gui/gxschannels/GxsChannelDialog.cpp index b878cb0c4..5d08ea707 100644 --- a/retroshare-gui/src/gui/gxschannels/GxsChannelDialog.cpp +++ b/retroshare-gui/src/gui/gxschannels/GxsChannelDialog.cpp @@ -69,6 +69,7 @@ void GxsChannelDialog::handleEvent_main_thread(std::shared_ptr ev case RsChannelEventCode::NEW_MESSAGE: // [[fallthrough]]; case RsChannelEventCode::UPDATED_MESSAGE: // [[fallthrough]]; + case RsChannelEventCode::NEW_COMMENT: // [[fallthrough]]; case RsChannelEventCode::READ_STATUS_CHANGED: // [[fallthrough]]; updateGroupStatisticsReal(e->mChannelGroupId);// update the list immediately break; diff --git a/retroshare-gui/src/gui/qss/stylesheet/Standard.qss b/retroshare-gui/src/gui/qss/stylesheet/Standard.qss index 1e4cf9633..72464c99b 100644 --- a/retroshare-gui/src/gui/qss/stylesheet/Standard.qss +++ b/retroshare-gui/src/gui/qss/stylesheet/Standard.qss @@ -1171,3 +1171,23 @@ PhotoItem QWidget:hover { PhotoSlideShow QLabel#albumLabel { font-size: 12pt; } + +ChannelsCommentsItem QFrame#mainFrame, BoardsCommentsItem QFrame#mainFrame { + background-color: white; +} + +ChannelsCommentsItem QLabel#newCommentLabel, BoardsCommentsItem QLabel#newCommentLabel { + font: 14px; + font: bold; +} + +ChannelsCommentsItem QLabel#subjectLabel, ChannelsCommentsItem QLabel#titleLabel , QLabel#nameLabel { + font: 14px; + font: bold; +} + +BoardsCommentsItem QLabel#subjectLabel, QLabel#titleLabel , QLabel#nameLabel { + font: 14px; + font: bold; +} + diff --git a/retroshare-gui/src/qss/qdarkstyle-v2.qss b/retroshare-gui/src/qss/qdarkstyle-v2.qss index daa19b493..8a40da319 100644 --- a/retroshare-gui/src/qss/qdarkstyle-v2.qss +++ b/retroshare-gui/src/qss/qdarkstyle-v2.qss @@ -2285,3 +2285,22 @@ ChatLobbyWidget QTreeWidget#lobbyTreeWidget{ NewFriendList QTreeView#peerTreeWidget { font-size: 12pt; } + +ChannelsCommentsItem QFrame#mainFrame, BoardsCommentsItem QFrame#mainFrame { + background: #19232D; +} + +ChannelsCommentsItem QLabel#newCommentLabel, BoardsCommentsItem QLabel#newCommentLabel { + font: 14px; + font: bold; +} + +ChannelsCommentsItem QLabel#subjectLabel, ChannelsCommentsItem QLabel#titleLabel , QLabel#nameLabel { + font: 14px; + font: bold; +} + +BoardsCommentsItem QLabel#subjectLabel, QLabel#titleLabel , QLabel#nameLabel { + font: 14px; + font: bold; +} diff --git a/retroshare-gui/src/qss/qdarkstyle.qss b/retroshare-gui/src/qss/qdarkstyle.qss index f7cd0215e..b1cf7c023 100644 --- a/retroshare-gui/src/qss/qdarkstyle.qss +++ b/retroshare-gui/src/qss/qdarkstyle.qss @@ -1519,3 +1519,22 @@ AlbumItem QFrame#albumFrame { border: 2px solid #3A3939; border-radius: 10px } + +ChannelsCommentsItem QFrame#mainFrame, BoardsCommentsItem QFrame#mainFrame { + background: #302F2F; +} + +ChannelsCommentsItem QLabel#newCommentLabel, BoardsCommentsItem QLabel#newCommentLabel { + font: 14px; + font: bold; +} + +ChannelsCommentsItem QLabel#subjectLabel, ChannelsCommentsItem QLabel#titleLabel , QLabel#nameLabel { + font: 14px; + font: bold; +} + +BoardsCommentsItem QLabel#subjectLabel, QLabel#titleLabel , QLabel#nameLabel { + font: 14px; + font: bold; +} diff --git a/retroshare-gui/src/retroshare-gui.pro b/retroshare-gui/src/retroshare-gui.pro index 2bb9e16b9..73564a574 100644 --- a/retroshare-gui/src/retroshare-gui.pro +++ b/retroshare-gui/src/retroshare-gui.pro @@ -623,9 +623,11 @@ HEADERS += rshare.h \ gui/elastic/arrow.h \ gui/elastic/elnode.h \ gui/NewsFeed.h \ + gui/feeds/BoardsCommentsItem.h \ gui/feeds/FeedItem.h \ gui/feeds/FeedHolder.h \ gui/feeds/GxsCircleItem.h \ + gui/feeds/ChannelsCommentsItem.h \ gui/feeds/PeerItem.h \ gui/feeds/MsgItem.h \ gui/feeds/ChatMsgItem.h \ @@ -733,7 +735,9 @@ FORMS += gui/StartDialog.ui \ gui/advsearch/AdvancedSearchDialog.ui \ gui/advsearch/expressionwidget.ui \ gui/NewsFeed.ui \ + gui/feeds/BoardsCommentsItem.ui \ gui/feeds/GxsCircleItem.ui \ + gui/feeds/ChannelsCommentsItem.ui \ gui/feeds/PeerItem.ui \ gui/feeds/MsgItem.ui \ gui/feeds/ChatMsgItem.ui \ @@ -980,9 +984,11 @@ SOURCES += main.cpp \ gui/elastic/arrow.cpp \ gui/elastic/elnode.cpp \ gui/NewsFeed.cpp \ + gui/feeds/BoardsCommentsItem.cpp \ gui/feeds/FeedItem.cpp \ gui/feeds/FeedHolder.cpp \ gui/feeds/GxsCircleItem.cpp \ + gui/feeds/ChannelsCommentsItem.cpp \ gui/feeds/PeerItem.cpp \ gui/feeds/MsgItem.cpp \ gui/feeds/ChatMsgItem.cpp \