This commit is contained in:
defnax 2026-08-31 21:46:59 +00:00 committed by GitHub
commit e9acb2d550
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 1361 additions and 56 deletions

View File

@ -1115,6 +1115,8 @@ if(RS_GXSGUI)
src/gui/gxs/GxsCommentTreeWidget.h
src/gui/gxs/GxsCommentContainer.h
src/gui/gxs/GxsCommentDialog.h
src/gui/gxs/CommentItemWidget.h
src/gui/gxs/FlatViewCommentWidget.h
src/gui/gxs/GxsCreateCommentDialog.h
src/gui/gxs/GxsGroupFrameDialog.h
src/gui/gxs/GxsMessageFrameWidget.h
@ -1134,6 +1136,8 @@ if(RS_GXSGUI)
src/gui/gxs/GxsGroupDialog.ui
src/gui/gxs/GxsCommentContainer.ui
src/gui/gxs/GxsCommentDialog.ui
src/gui/gxs/CommentItemWidget.ui
src/gui/gxs/FlatViewCommentWidget.ui
src/gui/gxs/GxsCreateCommentDialog.ui
src/gui/gxs/GxsGroupFrameDialog.ui
src/gui/gxs/GxsGroupShareKey.ui
@ -1152,6 +1156,8 @@ if(RS_GXSGUI)
src/gui/gxs/GxsCommentTreeWidget.cpp
src/gui/gxs/GxsCommentContainer.cpp
src/gui/gxs/GxsCommentDialog.cpp
src/gui/gxs/CommentItemWidget.cpp
src/gui/gxs/FlatViewCommentWidget.cpp
src/gui/gxs/GxsCreateCommentDialog.cpp
src/gui/gxs/GxsGroupFrameDialog.cpp
src/gui/gxs/GxsMessageFrameWidget.cpp

View File

@ -0,0 +1,215 @@
/*******************************************************************************
* retroshare-gui/src/gui/gxs/CommentItemWidget.cpp *
* *
* Copyright 2026 by RetroShare Team <retroshare.project@gmail.com> *
* *
* 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 <https://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
#include "CommentItemWidget.h"
#include "ui_CommentItemWidget.h"
#include "GxsIdDetails.h"
#include "util/DateTime.h"
#include "util/qtthreadsutils.h"
#include "util/HandleRichText.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QPushButton>
#include <QPixmap>
#include <QIcon>
#include <QDebug>
#include <QMouseEvent>
CommentItemWidget::CommentItemWidget(QWidget *parent)
: QWidget(parent), ui(new Ui::CommentItemWidget), mViewRepliesButton(nullptr),
mLevel(0), mUpvoteActive(false), mDownvoteActive(false), mSelected(false), mRepliesExpanded(false), mReplyCount(0)
{
ui->setupUi(this);
setupStyle();
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum);
mViewRepliesButton = new QPushButton(this);
mViewRepliesButton->setFlat(true);
mViewRepliesButton->hide();
connect(mViewRepliesButton, &QPushButton::clicked, this, &CommentItemWidget::on_viewRepliesButton_clicked);
// Insert the view-replies button into the content layout, below the actions row
QVBoxLayout *contentLay = qobject_cast<QVBoxLayout*>(ui->contentLayout);
if (contentLay) {
QHBoxLayout *repliesButtonLay = new QHBoxLayout();
repliesButtonLay->setContentsMargins(0, 0, 0, 0);
repliesButtonLay->addWidget(mViewRepliesButton);
repliesButtonLay->addStretch();
contentLay->addLayout(repliesButtonLay);
}
}
CommentItemWidget::~CommentItemWidget()
{
delete ui;
}
void CommentItemWidget::setMsgId(const RsGxsMessageId &id)
{
mMsgId = id;
}
void CommentItemWidget::setAuthorId(const RsGxsId &id)
{
mAuthorId = id;
}
void CommentItemWidget::setupStyle()
{
// Style the upvote/downvote buttons
ui->upvoteButton->setIcon(QIcon(":/icons/png/thumbs-up.png"));
ui->downvoteButton->setIcon(QIcon(":/icons/png/thumbs-down.png"));
}
void CommentItemWidget::setAuthorName(const QString &name)
{
mAuthorName = name;
ui->authorLabel->setText(name);
}
void CommentItemWidget::setAuthorAvatar(const QPixmap &avatar)
{
if (!avatar.isNull())
ui->avatarLabel->setPixmap(avatar);
}
void CommentItemWidget::setCommentText(const QString &text)
{
mCommentText = text;
ui->commentTextLabel->setText(RsHtml().formatText(NULL, text, RSHTML_FORMATTEXT_EMBED_SMILEYS | RSHTML_FORMATTEXT_EMBED_LINKS));
}
void CommentItemWidget::setDateTime(const QString &datetime)
{
ui->dateTimeLabel->setText(datetime);
}
void CommentItemWidget::setScore(int score)
{
ui->scoreLabel->setText(QString::number(score));
}
void CommentItemWidget::setUpvote(bool upvoted)
{
mUpvoteActive = upvoted;
ui->upvoteButton->setChecked(upvoted);
if (upvoted) {
ui->downvoteButton->setChecked(false);
ui->downvoteButton->setStyleSheet("");
} else {
ui->upvoteButton->setStyleSheet("");
}
}
void CommentItemWidget::setDownvote(bool downvoted)
{
mDownvoteActive = downvoted;
ui->downvoteButton->setChecked(downvoted);
if (downvoted) {
ui->upvoteButton->setChecked(false);
ui->upvoteButton->setStyleSheet("");
} else {
ui->downvoteButton->setStyleSheet("");
}
}
void CommentItemWidget::setReplyCount(int count)
{
if (count > 0) {
ui->replyButton->setText(QString("Reply (%1)").arg(count));
} else {
ui->replyButton->setText("Reply");
}
}
void CommentItemWidget::setLevel(int level)
{
mLevel = level;
// Apply indentation for nested replies
int leftMargin = 6 + (level * 24);
layout()->setContentsMargins(leftMargin, 4, 6, 4);
}
void CommentItemWidget::on_upvoteButton_clicked()
{
setUpvote(!mUpvoteActive);
emit upvoteClicked(mMsgId);
}
void CommentItemWidget::on_downvoteButton_clicked()
{
setDownvote(!mDownvoteActive);
emit downvoteClicked(mMsgId);
}
void CommentItemWidget::on_replyButton_clicked()
{
emit replyClicked(mMsgId);
}
void CommentItemWidget::on_authorLabel_linkActivated(const QString &link)
{
Q_UNUSED(link);
emit authorClicked(mAuthorId);
}
void CommentItemWidget::setViewRepliesCount(int count)
{
mReplyCount = count;
if (count <= 0) {
mViewRepliesButton->hide();
return;
}
mRepliesExpanded = false;
mViewRepliesButton->setIcon(QIcon(":/icons/png/down-arrow.png"));
mViewRepliesButton->setText(tr("View %1 repl%2").arg(count).arg(count == 1 ? "y" : "ies"));
mViewRepliesButton->show();
}
void CommentItemWidget::on_viewRepliesButton_clicked()
{
mRepliesExpanded = !mRepliesExpanded;
if (mRepliesExpanded){
mViewRepliesButton->setIcon(QIcon(":/icons/png/up-arrow.png"));
mViewRepliesButton->setText(tr("Hide %1 repl%2").arg(mReplyCount).arg(mReplyCount == 1 ? "y" : "ies"));
}else{
mViewRepliesButton->setIcon(QIcon(":/icons/png/down-arrow.png"));
mViewRepliesButton->setText(tr("View %1 repl%2").arg(mReplyCount).arg(mReplyCount == 1 ? "y" : "ies"));
}
emit viewRepliesToggled(mMsgId, mRepliesExpanded);
}
void CommentItemWidget::setSelected(bool selected)
{
mSelected = selected;
if (selected)
setStyleSheet("QWidget#CommentItemWidget { background-color: #e8f0fe; border-left: 3px solid #3ea6ff; }");
else
setStyleSheet("QWidget#CommentItemWidget { background-color: transparent; }");
}
void CommentItemWidget::mousePressEvent(QMouseEvent *event)
{
QWidget::mousePressEvent(event);
emit commentSelected(mMsgId);
}

View File

@ -0,0 +1,103 @@
/*******************************************************************************
* retroshare-gui/src/gui/gxs/CommentItemWidget.h *
* *
* Copyright 2026 by RetroShare Team <retroshare.project@gmail.com> *
* *
* 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 <https://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
#ifndef COMMENT_ITEM_WIDGET_H
#define COMMENT_ITEM_WIDGET_H
#include <QWidget>
#include <QPushButton>
#include <QLabel>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QGridLayout>
#include <QFrame>
#include <QMouseEvent>
#include <retroshare/rsgxscommon.h>
namespace Ui {
class CommentItemWidget;
}
class CommentItemWidget : public QWidget
{
Q_OBJECT
public:
explicit CommentItemWidget(QWidget *parent = nullptr);
~CommentItemWidget();
void setMsgId(const RsGxsMessageId &id);
RsGxsMessageId getMsgId() const { return mMsgId; }
void setAuthorId(const RsGxsId &id);
void setAuthorName(const QString &name);
void setAuthorAvatar(const QPixmap &avatar);
void setCommentText(const QString &text);
void setDateTime(const QString &datetime);
void setScore(int score);
void setUpvote(bool upvoted);
void setDownvote(bool downvoted);
void setReplyCount(int count);
void setLevel(int level);
void setViewRepliesCount(int count);
void setSelected(bool selected);
int getLevel() const { return mLevel; }
QString getCommentText() const { return mCommentText; }
QString getAuthorName() const { return mAuthorName; }
RsGxsId getAuthorId() const { return mAuthorId; }
signals:
void upvoteClicked(const RsGxsMessageId &msgId);
void downvoteClicked(const RsGxsMessageId &msgId);
void replyClicked(const RsGxsMessageId &msgId);
void authorClicked(const RsGxsId &authorId);
void viewRepliesToggled(const RsGxsMessageId &msgId, bool show);
void commentSelected(const RsGxsMessageId &msgId);
protected:
void mousePressEvent(QMouseEvent *event) override;
private slots:
void on_upvoteButton_clicked();
void on_downvoteButton_clicked();
void on_replyButton_clicked();
void on_authorLabel_linkActivated(const QString &link);
void on_viewRepliesButton_clicked();
private:
void setupStyle();
Ui::CommentItemWidget *ui;
QPushButton *mViewRepliesButton;
int mLevel;
bool mUpvoteActive;
bool mDownvoteActive;
bool mSelected;
bool mRepliesExpanded;
RsGxsMessageId mMsgId;
RsGxsId mAuthorId;
QString mCommentText;
QString mAuthorName;
int mReplyCount;
};
#endif // COMMENT_ITEM_WIDGET_H

View File

@ -0,0 +1,243 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CommentItemWidget</class>
<widget class="QWidget" name="CommentItemWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>500</width>
<height>98</height>
</rect>
</property>
<layout class="QVBoxLayout" name="CommentItemLayout">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>4</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>4</number>
</property>
<item>
<widget class="QFrame" name="mainFrame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="mainLayout">
<property name="spacing">
<number>8</number>
</property>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="avatarLabel">
<property name="minimumSize">
<size>
<width>40</width>
<height>40</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>40</width>
<height>40</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="contentLayout">
<property name="spacing">
<number>4</number>
</property>
<item>
<layout class="QHBoxLayout" name="headerLayout">
<property name="spacing">
<number>8</number>
</property>
<item>
<widget class="QLabel" name="authorLabel">
<property name="text">
<string>Author Name</string>
</property>
<property name="openExternalLinks">
<bool>false</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="dateTimeLabel">
<property name="text">
<string>2 hours ago</string>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item>
<spacer name="headerSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="commentTextLabel">
<property name="text">
<string>Comment text goes here</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="actionsLayout">
<property name="spacing">
<number>12</number>
</property>
<item>
<widget class="QPushButton" name="upvoteButton">
<property name="minimumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
<property name="toolTip">
<string>Like</string>
</property>
<property name="text">
<string/>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="scoreLabel">
<property name="text">
<string>0</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="downvoteButton">
<property name="minimumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
<property name="toolTip">
<string>Dislike</string>
</property>
<property name="text">
<string/>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="replyButton">
<property name="minimumSize">
<size>
<width>60</width>
<height>28</height>
</size>
</property>
<property name="text">
<string>Reply</string>
</property>
</widget>
</item>
<item>
<spacer name="actionsSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,354 @@
/*******************************************************************************
* retroshare-gui/src/gui/gxs/FlatViewCommentWidget.cpp *
* *
* Copyright 2026 by RetroShare Team <retroshare.project@gmail.com> *
* *
* 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 <https://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
#include "FlatViewCommentWidget.h"
#include "CommentItemWidget.h"
#include "GxsIdDetails.h"
#include "util/DateTime.h"
#include "util/qtthreadsutils.h"
#include <QVBoxLayout>
#include <QMessageBox>
#include <QDebug>
#include <algorithm>
#include "ui_FlatViewCommentWidget.h"
static void fillCommentItemWidgetCallback(GxsIdDetailsType type, const RsIdentityDetails &details, QObject *object, const QVariant &/*data*/)
{
CommentItemWidget *item = dynamic_cast<CommentItemWidget*>(object);
if (!item)
return;
switch (type) {
case GXS_ID_DETAILS_TYPE_DONE:
// getName() uses details.mNickname — the actual display name.
item->setAuthorName(GxsIdDetails::getNameForType(type, details));
{
QPixmap avatar;
if (details.mAvatar.mSize > 0 && GxsIdDetails::loadPixmapFromData(details.mAvatar.mData, details.mAvatar.mSize, avatar))
item->setAuthorAvatar(avatar);
else
item->setAuthorAvatar(GxsIdDetails::makeDefaultIcon(details.mId));
}
break;
case GXS_ID_DETAILS_TYPE_LOADING:
// Set generated default avatar so the slot is never blank while loading.
// Leave the name empty — no raw ID prefix shown.
if (!details.mId.isNull())
item->setAuthorAvatar(GxsIdDetails::makeDefaultIcon(details.mId));
break;
case GXS_ID_DETAILS_TYPE_FAILED:
// Identity not available; show truncated ID as fallback, keep default icon.
if (!details.mId.isNull()) {
item->setAuthorName(QString::fromStdString(details.mId.toStdString().substr(0, 10)) + QStringLiteral(""));
item->setAuthorAvatar(GxsIdDetails::makeDefaultIcon(details.mId));
}
break;
case GXS_ID_DETAILS_TYPE_BANNED:
item->setAuthorName(QObject::tr("[Banned]"));
if (!details.mId.isNull())
item->setAuthorAvatar(GxsIdDetails::makeDefaultIcon(details.mId));
break;
case GXS_ID_DETAILS_TYPE_EMPTY:
default:
break;
}
}
FlatViewCommentWidget::FlatViewCommentWidget(QWidget *parent)
: QWidget(parent), ui(new Ui::FlatViewCommentWidget), mCommentService(nullptr),
mSelectedWidget(nullptr)
{
ui->setupUi(this);
// Reuse the layout already defined in the .ui file — creating a second one
// would silently replace it and lose the trailing spacer.
mCommentsLayout = qobject_cast<QVBoxLayout*>(ui->commentsScrollArea->widget()->layout());
mCommentsLayout->setSpacing(8);
}
FlatViewCommentWidget::~FlatViewCommentWidget()
{
delete ui;
}
void FlatViewCommentWidget::setCommentService(RsGxsCommentService *service)
{
mCommentService = service;
}
void FlatViewCommentWidget::setVoterId(const RsGxsId &id)
{
mVoterId = id;
}
void FlatViewCommentWidget::updateReplyCountButtons()
{
for (auto it = mRepliesMap.begin(); it != mRepliesMap.end(); ++it) {
CommentItemWidget *parent = mCommentWidgets.value(it.key(), nullptr);
if (parent)
parent->setViewRepliesCount(it.value().size());
}
}
void FlatViewCommentWidget::clearComments()
{
// Zero before deleting to avoid a dangling pointer if Qt emits signals during destruction
mSelectedWidget = nullptr;
// Remove all comment widgets except the trailing stretch spacer
while (mCommentsLayout->count() > 1) {
QLayoutItem *item = mCommentsLayout->takeAt(0);
if (item && item->widget()) {
delete item->widget();
}
delete item;
}
mCommentWidgets.clear();
mRepliesMap.clear();
mScoreMap.clear();
mTimestampMap.clear();
}
void FlatViewCommentWidget::addComment(const RsGxsComment &comment, const RsGxsMessageId &parentId)
{
CommentItemWidget *itemWidget = new CommentItemWidget();
itemWidget->setMsgId(comment.mMeta.mMsgId);
itemWidget->setAuthorId(comment.mMeta.mAuthorId);
itemWidget->setCommentText(QString::fromStdString(comment.mComment));
itemWidget->setDateTime(DateTime::formatDateTime(comment.mMeta.mPublishTs));
itemWidget->setScore(static_cast<int>(comment.mUpVotes) - static_cast<int>(comment.mDownVotes));
if (comment.mOwnVote == GXS_VOTE_UP) {
itemWidget->setUpvote(true);
} else if (comment.mOwnVote == GXS_VOTE_DOWN) {
itemWidget->setDownvote(true);
}
mScoreMap[comment.mMeta.mMsgId] = comment.mScore;
mTimestampMap[comment.mMeta.mMsgId] = comment.mMeta.mPublishTs;
int level = parentId.isNull() ? 0 : 1;
itemWidget->setLevel(level);
// replies start hidden; the parent's "View X replies" button reveals them
if (level > 0)
itemWidget->hide();
mCommentWidgets[comment.mMeta.mMsgId] = itemWidget;
// Vote buttons wired to doVote() which calls the comment service directly,
// mirroring GxsCommentTreeWidget::vote()
connect(itemWidget, &CommentItemWidget::upvoteClicked, this, [this](const RsGxsMessageId &msgId) {
doVote(msgId, true);
});
connect(itemWidget, &CommentItemWidget::downvoteClicked, this, [this](const RsGxsMessageId &msgId) {
doVote(msgId, false);
});
// Reply button propagates up to GxsCommentDialog via signal
connect(itemWidget, &CommentItemWidget::replyClicked, this, &FlatViewCommentWidget::commentReply);
// Collapse/expand toggle for reply threads
connect(itemWidget, &CommentItemWidget::viewRepliesToggled,
this, &FlatViewCommentWidget::onViewRepliesToggled);
// Click-to-select within the list
connect(itemWidget, &CommentItemWidget::commentSelected,
this, &FlatViewCommentWidget::onCommentSelected);
// Async identity resolution fills name and avatar
GxsIdDetails::process(comment.mMeta.mAuthorId, fillCommentItemWidgetCallback, itemWidget);
if (parentId.isNull()) {
mCommentsLayout->insertWidget(mCommentsLayout->count() - 1, itemWidget);
} else {
mRepliesMap[parentId].append(comment.mMeta.mMsgId);
CommentItemWidget *parentWidget = mCommentWidgets.value(parentId, nullptr);
if (parentWidget) {
int insertPos = mCommentsLayout->indexOf(parentWidget) + 1;
// Skip past any replies already placed after this parent
while (insertPos < mCommentsLayout->count() - 1) {
CommentItemWidget *ciw = qobject_cast<CommentItemWidget*>(mCommentsLayout->itemAt(insertPos)->widget());
if (ciw && ciw->getLevel() > 0)
++insertPos;
else
break;
}
mCommentsLayout->insertWidget(insertPos, itemWidget);
} else {
mCommentsLayout->insertWidget(mCommentsLayout->count() - 1, itemWidget);
}
}
}
void FlatViewCommentWidget::loadCommentsForPost(const RsGxsGroupId &groupId, const std::set<RsGxsMessageId> &msgVersions, const RsGxsMessageId &mostRecentMsgId)
{
mCurrentGroupId = groupId;
mCurrentPostId = mostRecentMsgId;
mLatestMsgId = mostRecentMsgId;
mMsgVersions = msgVersions;
clearComments();
if (!mCommentService) {
qDebug() << "FlatViewCommentWidget: No comment service set";
return;
}
RsThread::async([this, groupId, msgVersions]()
{
std::vector<RsGxsComment> comments;
if (!mCommentService->getRelatedComments(groupId, msgVersions, comments)) {
std::cerr << "FlatViewCommentWidget: failed to get comments" << std::endl;
return;
}
RsQThreadUtils::postToObject([this, comments, msgVersions]()
{
clearComments();
// First pass: top-level comments — parent is one of the post's own message IDs
for (const auto &comment : comments) {
if (msgVersions.count(comment.mMeta.mParentId)) {
addComment(comment, RsGxsMessageId());
}
}
// Second pass: replies — parent is another comment, not the post itself
for (const auto &comment : comments) {
if (!msgVersions.count(comment.mMeta.mParentId)) {
addComment(comment, comment.mMeta.mParentId);
}
}
// Stamp each top-level comment with its reply count
updateReplyCountButtons();
}, this);
});
}
void FlatViewCommentWidget::sortComments(int sortMethod)
{
// Collect top-level CommentItemWidget pointers
QList<CommentItemWidget *> topLevel;
for (auto it = mCommentWidgets.begin(); it != mCommentWidgets.end(); ++it) {
if (it.value()->getLevel() == 0)
topLevel.append(it.value());
}
if (topLevel.isEmpty())
return;
std::sort(topLevel.begin(), topLevel.end(), [&](CommentItemWidget *a, CommentItemWidget *b) {
const RsGxsMessageId aId = a->getMsgId();
const RsGxsMessageId bId = b->getMsgId();
if (sortMethod == 1) // New: timestamp descending
return mTimestampMap.value(aId, 0) > mTimestampMap.value(bId, 0);
if (sortMethod == 2) // Top: score ascending
return mScoreMap.value(aId, 0.0) < mScoreMap.value(bId, 0.0);
// Hot (0, default): score descending
return mScoreMap.value(aId, 0.0) > mScoreMap.value(bId, 0.0);
});
// Detach all comment widgets from the layout without deleting them;
// delete the QLayoutItem* wrapper (required by Qt), not the widget itself.
while (mCommentsLayout->count() > 1)
delete mCommentsLayout->takeAt(0);
// Re-insert: each top-level widget followed immediately by its replies
for (CommentItemWidget *parent : topLevel) {
mCommentsLayout->insertWidget(mCommentsLayout->count() - 1, parent);
for (const RsGxsMessageId &replyId : mRepliesMap.value(parent->getMsgId())) {
CommentItemWidget *reply = mCommentWidgets.value(replyId, nullptr);
if (reply)
mCommentsLayout->insertWidget(mCommentsLayout->count() - 1, reply);
}
}
}
void FlatViewCommentWidget::doVote(const RsGxsMessageId &commentMsgId, bool up)
{
if (!mCommentService) {
qDebug() << "FlatViewCommentWidget::doVote: no comment service";
return;
}
if (mVoterId.isNull()) {
QMessageBox::warning(this, tr("Cannot vote"), tr("Please select an identity to vote with."));
return;
}
RsGxsGroupId groupId = mCurrentGroupId;
RsGxsMessageId threadId = mLatestMsgId;
RsGxsId voterId = mVoterId;
std::set<RsGxsMessageId> versions = mMsgVersions;
RsThread::async([this, groupId, threadId, commentMsgId, voterId, up, versions]()
{
std::string error_string;
RsGxsMessageId vote_id;
RsGxsVoteType tvote = up ? RsGxsVoteType::UP : RsGxsVoteType::DOWN;
bool res = mCommentService->voteForComment(groupId, threadId, commentMsgId, voterId, tvote, vote_id, error_string);
RsQThreadUtils::postToObject([this, res, error_string, groupId, versions, threadId]()
{
if (res)
loadCommentsForPost(groupId, versions, threadId);
else
QMessageBox::critical(nullptr, tr("Cannot vote"),
tr("Error while voting: ") + QString::fromStdString(error_string));
}, this);
});
}
void FlatViewCommentWidget::onViewRepliesToggled(const RsGxsMessageId &msgId, bool show)
{
for (const RsGxsMessageId &replyId : mRepliesMap.value(msgId)) {
CommentItemWidget *reply = mCommentWidgets.value(replyId, nullptr);
if (reply)
reply->setVisible(show);
}
}
void FlatViewCommentWidget::onCommentSelected(const RsGxsMessageId &msgId)
{
if (mSelectedWidget) {
mSelectedWidget->setSelected(false);
mSelectedWidget = nullptr;
}
CommentItemWidget *w = mCommentWidgets.value(msgId, nullptr);
if (w) {
w->setSelected(true);
mSelectedWidget = w;
}
}
CommentItemWidget *FlatViewCommentWidget::getCommentWidget(const RsGxsMessageId &msgId) const
{
return mCommentWidgets.value(msgId, nullptr);
}

View File

@ -0,0 +1,90 @@
/*******************************************************************************
* retroshare-gui/src/gui/gxs/FlatViewCommentWidget.h *
* *
* Copyright 206 by RetroShare Team <retroshare.project@gmail.com> *
* *
* 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 <https://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
#ifndef FLAT_VIEW_COMMENT_WIDGET_H
#define FLAT_VIEW_COMMENT_WIDGET_H
#include <QWidget>
#include <QVBoxLayout>
#include <QScrollArea>
#include <QMap>
#include <set>
#include <retroshare/rsgxscommon.h>
namespace Ui {
class FlatViewCommentWidget;
}
class CommentItemWidget;
class FlatViewCommentWidget : public QWidget
{
Q_OBJECT
public:
explicit FlatViewCommentWidget(QWidget *parent = nullptr);
~FlatViewCommentWidget();
// Add a comment at top level or as a reply to a parent
void addComment(const RsGxsComment &comment, const RsGxsMessageId &parentId = RsGxsMessageId());
void clearComments();
// Set the comment service for loading comments
void setCommentService(RsGxsCommentService *service);
void setVoterId(const RsGxsId &id);
void updateReplyCountButtons();
CommentItemWidget *getCommentWidget(const RsGxsMessageId &msgId) const;
signals:
void commentUpvote(const RsGxsGrpMsgIdPair &msgId, bool up);
void commentDownvote(const RsGxsGrpMsgIdPair &msgId, bool down);
void commentReply(const RsGxsMessageId &parentId);
public slots:
void loadCommentsForPost(const RsGxsGroupId &groupId, const std::set<RsGxsMessageId> &msgVersions, const RsGxsMessageId &mostRecentMsgId);
void sortComments(int sortMethod);
private slots:
void onViewRepliesToggled(const RsGxsMessageId &msgId, bool show);
void onCommentSelected(const RsGxsMessageId &msgId);
private:
void doVote(const RsGxsMessageId &commentMsgId, bool up);
Ui::FlatViewCommentWidget *ui;
QVBoxLayout *mCommentsLayout;
QMap<RsGxsMessageId, CommentItemWidget *> mCommentWidgets;
QMap<RsGxsMessageId, QList<RsGxsMessageId> > mRepliesMap;
QMap<RsGxsMessageId, double> mScoreMap;
QMap<RsGxsMessageId, rstime_t> mTimestampMap;
RsGxsCommentService *mCommentService;
RsGxsGroupId mCurrentGroupId;
RsGxsMessageId mCurrentPostId;
RsGxsMessageId mLatestMsgId;
std::set<RsGxsMessageId> mMsgVersions;
RsGxsId mVoterId;
CommentItemWidget *mSelectedWidget;
};
#endif // FLAT_VIEW_COMMENT_WIDGET_H

View File

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FlatViewCommentWidget</class>
<widget class="QWidget" name="FlatViewCommentWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>600</width>
<height>400</height>
</rect>
</property>
<layout class="QVBoxLayout" name="FlatViewCommentLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QScrollArea" name="commentsScrollArea">
<property name="widgetResizable">
<bool>true</bool>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAsNeeded</enum>
</property>
<widget class="QWidget" name="commentsScrollAreaWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>598</width>
<height>398</height>
</rect>
</property>
<layout class="QVBoxLayout" name="commentsContainerLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<spacer name="commentsSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>CommentItemWidget</class>
<extends>QWidget</extends>
<header>gui/gxs/CommentItemWidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@ -20,6 +20,9 @@
#include "gui/gxs/GxsCommentDialog.h"
#include "gui/gxs/GxsCommentTreeWidget.h"
#include "gui/gxs/FlatViewCommentWidget.h"
#include "gui/gxs/CommentItemWidget.h"
#include "gui/gxs/GxsCreateCommentDialog.h"
#include "ui_GxsCommentDialog.h"
#include <iostream>
@ -28,12 +31,14 @@
#include <QTimer>
#include <QMessageBox>
#include <QDateTime>
#include <QToolButton>
#include <QVBoxLayout>
//#define DEBUG_COMMENT_DIALOG 1
/** Constructor */
GxsCommentDialog::GxsCommentDialog(QWidget *parent, const RsGxsId &default_author, RsGxsCommentService *comment_service)
: QWidget(parent), ui(new Ui::GxsCommentDialog)
: QWidget(parent), ui(new Ui::GxsCommentDialog), mUseFlatView(false), mFlatViewWidget(nullptr), mCommentService(nullptr)
{
/* Invoke the Qt Designer generated QObject setup routine */
ui->setupUi(this);
@ -60,22 +65,27 @@ void GxsCommentDialog::init(const RsGxsId& default_author)
connect(ui->commentButton, SIGNAL(clicked()), ui->treeWidget, SLOT(makeComment()));
connect(ui->sortBox, SIGNAL(currentIndexChanged(int)), this, SLOT(sortComments(int)));
connect(ui->viewModeButton, &QToolButton::toggled, this, &GxsCommentDialog::onFlatViewToggled);
// default sort method "HOT".
ui->treeWidget->sortByColumn(4, Qt::DescendingOrder);
int S = QFontMetricsF(font()).height() ;
ui->sortBox->setIconSize(QSize(S*1.5,S*1.5));
ui->commentButton->setIconSize(QSize(S*1.5,S*1.5));
ui->viewModeButton->setIconSize(QSize(S*1.5,S*1.5));
}
void GxsCommentDialog::setGxsService(RsGxsCommentService *comment_service)
{
mCommentService = comment_service;
ui->treeWidget->setup(comment_service);
}
GxsCommentDialog::GxsCommentDialog(QWidget *parent,const RsGxsId &default_author)
: QWidget(parent), ui(new Ui::GxsCommentDialog)
: QWidget(parent), ui(new Ui::GxsCommentDialog), mUseFlatView(false), mFlatViewWidget(nullptr), mCommentService(nullptr)
{
/* Invoke the Qt Designer generated QObject setup routine */
ui->setupUi(this);
@ -91,6 +101,9 @@ GxsCommentDialog::~GxsCommentDialog()
void GxsCommentDialog::commentClear()
{
ui->treeWidget->clear();
if (mFlatViewWidget) {
mFlatViewWidget->clearComments();
}
mGrpId.clear();
mMostRecentMsgId.clear();
mMsgVersions.clear();
@ -109,6 +122,13 @@ void GxsCommentDialog::commentLoad(const RsGxsGroupId &grpId, const std::set<RsG
ui->treeWidget->setUseCache(use_cache);
ui->treeWidget->requestComments(mGrpId,msg_versions,most_recent_msgId);
if (mUseFlatView) {
setupFlatViewWidget();
if (mFlatViewWidget) {
mFlatViewWidget->loadCommentsForPost(mGrpId, mMsgVersions, mMostRecentMsgId);
}
}
}
void GxsCommentDialog::notifyCommentsLoaded(int n)
@ -131,6 +151,7 @@ void GxsCommentDialog::idChooserReady()
void GxsCommentDialog::voterSelectionChanged( int index )
{
Q_UNUSED(index)
#ifdef DEBUG_COMMENT_DIALOG
std::cerr << "GxsCommentDialog::voterSelectionChanged(" << index << ")";
std::cerr << std::endl;
@ -145,6 +166,8 @@ void GxsCommentDialog::voterSelectionChanged( int index )
std::cerr << std::endl;
#endif
ui->treeWidget->setVoteId(voterId);
if (mFlatViewWidget)
mFlatViewWidget->setVoterId(voterId);
break;
case GxsIdChooser::NoId:
@ -198,19 +221,131 @@ void GxsCommentDialog::setCommentHeader(QWidget *header)
void GxsCommentDialog::sortComments(int i)
{
switch(i)
{
default:
case 0:
ui->treeWidget->sortByColumn(4, Qt::DescendingOrder);
if (mUseFlatView && mFlatViewWidget) {
mFlatViewWidget->sortComments(0);
} else {
ui->treeWidget->sortByColumn(4, Qt::DescendingOrder);
}
break;
case 1:
ui->treeWidget->sortByColumn(2, Qt::DescendingOrder);
if (mUseFlatView && mFlatViewWidget) {
mFlatViewWidget->sortComments(1);
} else {
ui->treeWidget->sortByColumn(2, Qt::DescendingOrder);
}
break;
case 2:
ui->treeWidget->sortByColumn(3, Qt::DescendingOrder);
if (mUseFlatView && mFlatViewWidget) {
mFlatViewWidget->sortComments(2);
} else {
ui->treeWidget->sortByColumn(3, Qt::DescendingOrder);
}
break;
}
}
void GxsCommentDialog::setupFlatViewWidget()
{
if (mFlatViewWidget)
return;
mFlatViewWidget = new FlatViewCommentWidget(ui->flatViewPage);
mFlatViewWidget->setCommentService(mCommentService);
connect(mFlatViewWidget, &FlatViewCommentWidget::commentReply,
this, &GxsCommentDialog::onFlatViewCommentReply);
// Propagate the already-chosen voter ID if one was selected before the widget was created
{
RsGxsId voterId;
GxsIdChooser::ChosenId_Ret ret = ui->idChooser->getChosenId(voterId);
if (ret == GxsIdChooser::KnowId || ret == GxsIdChooser::UnKnowId)
mFlatViewWidget->setVoterId(voterId);
}
QVBoxLayout *pageLayout = new QVBoxLayout(ui->flatViewPage);
pageLayout->setContentsMargins(0, 0, 0, 0);
pageLayout->addWidget(mFlatViewWidget);
}
void GxsCommentDialog::loadFlatViewComments(const std::vector<RsGxsComment> &comments)
{
if (!mFlatViewWidget)
setupFlatViewWidget();
mUseFlatView = true;
ui->viewModeButton->setChecked(true);
ui->commentStackedWidget->setCurrentWidget(ui->flatViewPage);
mFlatViewWidget->clearComments();
// Collect all comment msgIds to distinguish top-level (parent not a comment) from replies
std::set<RsGxsMessageId> commentIds;
for (const auto &c : comments)
commentIds.insert(c.mMeta.mMsgId);
// First pass: top-level comments
for (const auto &comment : comments) {
if (!commentIds.count(comment.mMeta.mParentId))
mFlatViewWidget->addComment(comment, RsGxsMessageId());
}
// Second pass: replies
for (const auto &comment : comments) {
if (commentIds.count(comment.mMeta.mParentId))
mFlatViewWidget->addComment(comment, comment.mMeta.mParentId);
}
mFlatViewWidget->updateReplyCountButtons();
}
void GxsCommentDialog::loadFlatView()
{
mUseFlatView = true;
ui->viewModeButton->setChecked(true);
setupFlatViewWidget();
ui->commentStackedWidget->setCurrentWidget(ui->flatViewPage);
if (mFlatViewWidget)
mFlatViewWidget->loadCommentsForPost(mGrpId, mMsgVersions, mMostRecentMsgId);
}
void GxsCommentDialog::onFlatViewToggled(bool checked)
{
if (checked) {
mUseFlatView = true;
setupFlatViewWidget();
ui->commentStackedWidget->setCurrentWidget(ui->flatViewPage);
if (mFlatViewWidget)
mFlatViewWidget->loadCommentsForPost(mGrpId, mMsgVersions, mMostRecentMsgId);
} else {
mUseFlatView = false;
ui->commentStackedWidget->setCurrentWidget(ui->classicPage);
}
}
void GxsCommentDialog::onFlatViewCommentReply(const RsGxsMessageId &parentId)
{
RsGxsId voterId;
ui->idChooser->getChosenId(voterId);
GxsCreateCommentDialog dlg(mCommentService,
RsGxsGrpMsgIdPair(mGrpId, parentId),
mMostRecentMsgId, voterId, this);
if (mFlatViewWidget) {
CommentItemWidget *parentWidget = mFlatViewWidget->getCommentWidget(parentId);
if (parentWidget) {
dlg.loadComment(parentWidget->getCommentText(), parentWidget->getAuthorName(), parentWidget->getAuthorId());
}
}
dlg.exec();
// Reload so the new reply appears
if (mFlatViewWidget)
mFlatViewWidget->loadCommentsForPost(mGrpId, mMsgVersions, mMostRecentMsgId);
}

View File

@ -23,6 +23,8 @@
#include "gui/gxs/GxsCommentContainer.h"
class FlatViewCommentWidget;
namespace Ui {
class GxsCommentDialog;
}
@ -44,20 +46,28 @@ public:
RsGxsGroupId groupId() { return mGrpId; }
RsGxsMessageId messageId() { return mMostRecentMsgId; }
// Flat view methods
void setUseFlatView(bool useFlatView) { mUseFlatView = useFlatView; }
void loadFlatViewComments(const std::vector<RsGxsComment> &comments);
public slots:
void refresh();
void loadFlatView();
private slots:
void idChooserReady();
void voterSelectionChanged( int index );
void sortComments(int);
void notifyCommentsLoaded(int n);
void onFlatViewToggled(bool checked);
void onFlatViewCommentReply(const RsGxsMessageId &parentId);
signals:
void commentsLoaded(int);
private:
void init(const RsGxsId &default_author);
void setupFlatViewWidget();
RsGxsGroupId mGrpId;
RsGxsMessageId mMostRecentMsgId;
@ -65,6 +75,10 @@ private:
/* UI - from Designer */
Ui::GxsCommentDialog *ui;
bool mUseFlatView;
FlatViewCommentWidget *mFlatViewWidget;
RsGxsCommentService *mCommentService;
};
#endif

View File

@ -20,54 +20,6 @@
<string>Form</string>
</property>
<layout class="QGridLayout" name="GxsCommentDialogGLayout">
<item row="1" column="0">
<widget class="GxsCommentTreeWidget" name="treeWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<column>
<property name="text">
<string>Comment</string>
</property>
</column>
<column>
<property name="text">
<string>Author</string>
</property>
</column>
<column>
<property name="text">
<string>Date</string>
</property>
</column>
<column>
<property name="text">
<string>Score</string>
</property>
</column>
<column>
<property name="text">
<string>UpVotes</string>
</property>
</column>
<column>
<property name="text">
<string>DownVotes</string>
</property>
</column>
<column>
<property name="text">
<string>OwnVote</string>
</property>
</column>
</widget>
</item>
<item row="0" column="0">
<layout class="QHBoxLayout" name="toolBarHLayout">
<item>
@ -145,6 +97,33 @@
</item>
</widget>
</item>
<item>
<widget class="QToolButton" name="viewModeButton">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Threaded view / flat view&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="icon">
<iconset resource="../icons.qrc">
<normaloff>:/icons/svg/threaded-view.svg</normaloff>
<normalon>:/icons/svg/flat-view.svg</normalon>:/icons/svg/threaded-view.svg</iconset>
</property>
<property name="iconSize">
<size>
<width>22</width>
<height>22</height>
</size>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonIconOnly</enum>
</property>
</widget>
</item>
<item>
<spacer name="toolBarHSpacer">
<property name="orientation">
@ -177,6 +156,81 @@
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QStackedWidget" name="commentStackedWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<widget class="QWidget" name="classicPage">
<layout class="QVBoxLayout" name="classicPageLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="GxsCommentTreeWidget" name="treeWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<column>
<property name="text">
<string>Comment</string>
</property>
</column>
<column>
<property name="text">
<string>Author</string>
</property>
</column>
<column>
<property name="text">
<string>Date</string>
</property>
</column>
<column>
<property name="text">
<string>Score</string>
</property>
</column>
<column>
<property name="text">
<string>UpVotes</string>
</property>
</column>
<column>
<property name="text">
<string>DownVotes</string>
</property>
</column>
<column>
<property name="text">
<string>OwnVote</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="flatViewPage"/>
</widget>
</item>
</layout>
</widget>
<customwidgets>

View File

@ -1452,6 +1452,8 @@ gxsgui {
gui/gxs/GxsCommentTreeWidget.h \
gui/gxs/GxsCommentContainer.h \
gui/gxs/GxsCommentDialog.h \
gui/gxs/CommentItemWidget.h \
gui/gxs/FlatViewCommentWidget.h \
gui/gxs/GxsCreateCommentDialog.h \
gui/gxs/GxsGroupFrameDialog.h \
gui/gxs/GxsMessageFrameWidget.h \
@ -1467,6 +1469,8 @@ gxsgui {
FORMS += gui/gxs/GxsGroupDialog.ui \
gui/gxs/GxsCommentContainer.ui \
gui/gxs/GxsCommentDialog.ui \
gui/gxs/CommentItemWidget.ui \
gui/gxs/FlatViewCommentWidget.ui \
gui/gxs/GxsCreateCommentDialog.ui \
gui/gxs/GxsGroupFrameDialog.ui\
gui/gxs/GxsGroupShareKey.ui
@ -1484,6 +1488,8 @@ gxsgui {
gui/gxs/GxsCommentTreeWidget.cpp \
gui/gxs/GxsCommentContainer.cpp \
gui/gxs/GxsCommentDialog.cpp \
gui/gxs/CommentItemWidget.cpp \
gui/gxs/FlatViewCommentWidget.cpp \
gui/gxs/GxsCreateCommentDialog.cpp \
gui/gxs/GxsGroupFrameDialog.cpp \
gui/gxs/GxsMessageFrameWidget.cpp \