mirror of
https://github.com/RetroShare/RetroShare.git
synced 2026-09-14 11:06:01 +05:00
Merge 754c0a90f2 into 21c981a430
This commit is contained in:
commit
e9acb2d550
@ -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
|
||||
|
||||
215
retroshare-gui/src/gui/gxs/CommentItemWidget.cpp
Normal file
215
retroshare-gui/src/gui/gxs/CommentItemWidget.cpp
Normal 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);
|
||||
}
|
||||
103
retroshare-gui/src/gui/gxs/CommentItemWidget.h
Normal file
103
retroshare-gui/src/gui/gxs/CommentItemWidget.h
Normal 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
|
||||
243
retroshare-gui/src/gui/gxs/CommentItemWidget.ui
Normal file
243
retroshare-gui/src/gui/gxs/CommentItemWidget.ui
Normal 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>
|
||||
354
retroshare-gui/src/gui/gxs/FlatViewCommentWidget.cpp
Normal file
354
retroshare-gui/src/gui/gxs/FlatViewCommentWidget.cpp
Normal 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);
|
||||
}
|
||||
90
retroshare-gui/src/gui/gxs/FlatViewCommentWidget.h
Normal file
90
retroshare-gui/src/gui/gxs/FlatViewCommentWidget.h
Normal 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
|
||||
85
retroshare-gui/src/gui/gxs/FlatViewCommentWidget.ui
Normal file
85
retroshare-gui/src/gui/gxs/FlatViewCommentWidget.ui
Normal 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>
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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><html><head/><body><p>Threaded view / flat view</p></body></html></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>
|
||||
|
||||
@ -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 \
|
||||
|
||||
Loading…
Reference in New Issue
Block a user