From f71e5531e0219497acb9063083f91010fd910ab3 Mon Sep 17 00:00:00 2001 From: evgeny Date: Wed, 7 Oct 2026 01:22:34 +0200 Subject: [PATCH] Open reaction picker from desktop heart and toggle existing reactions --- tools/chatgui/src/messagefeedback.cpp | 30 +++++++++++++++++++++++++++ tools/chatgui/src/messagelist.cpp | 12 ++--------- tools/chatgui/src/messagelist.h | 3 +++ 3 files changed, 35 insertions(+), 10 deletions(-) diff --git a/tools/chatgui/src/messagefeedback.cpp b/tools/chatgui/src/messagefeedback.cpp index 3eda490a..f6bb6e9c 100644 --- a/tools/chatgui/src/messagefeedback.cpp +++ b/tools/chatgui/src/messagefeedback.cpp @@ -18,6 +18,8 @@ #include #include #include +#include +#include #include extern "C" { @@ -26,6 +28,34 @@ extern "C" { static QString feedbackText(const char* source) { return QCoreApplication::translate("ChatGui", source); } +void MessageList::addReactionActions(QMenu* menu, const QString& channel, const QString& messageId) { + QSet mine; + for (const auto& value : m_feedback.value(messageId).value("reactions").toArray()) + if (value.toObject().value("mine").toBool()) mine.insert(value.toObject().value("id").toInt()); + for (unsigned i = 0; i < CHAT_REACTION_COUNT; i++) { + auto* action = menu->addAction(QString::fromUtf8(chat_reaction_emoji(i))); + action->setCheckable(true); action->setChecked(mine.contains(i)); + connect(action, &QAction::triggered, this, [this, channel, messageId, i]() { + GUI_DEBUG("reaction: chosen ch=%s message=%s emoji=%u", qPrintable(channel), qPrintable(messageId), i); + sendFeedback({{"cmd", "react"}, {"ch", channel}, {"message_id", messageId}, {"emoji", (int)i}}); + }); + } +} + +void MessageList::requestReaction(const QModelIndex& index, int emoji) { + const QString channel = index.data(MsgChannelIdRole).toString(), messageId = index.data(MsgMessageKeyRole).toString(); + if (!index.isValid() || channel.isEmpty() || isDmChannel(channel) || messageId.size() != 64 + || emoji < -1 || emoji >= CHAT_REACTION_COUNT) { + GUI_WARN("reaction: invalid target ch=%s key_bytes=%d emoji=%d", qPrintable(channel), messageId.size(), emoji); return; + } + if (emoji >= 0) { + GUI_DEBUG("reaction: toggled ch=%s message=%s emoji=%d", qPrintable(channel), qPrintable(messageId), emoji); + sendFeedback({{"cmd", "react"}, {"ch", channel}, {"message_id", messageId}, {"emoji", emoji}}); return; + } + GUI_DEBUG("reaction: picker opened ch=%s message=%s", qPrintable(channel), qPrintable(messageId)); + QMenu menu(m_view); addReactionActions(&menu, channel, messageId); menu.exec(QCursor::pos()); +} + /* Итог для баббла и открытого диалога; строковые поля показываем как обычный текст. */ static QString pollSummary(const QJsonObject& poll) { QString text = poll.value("question").toString(); diff --git a/tools/chatgui/src/messagelist.cpp b/tools/chatgui/src/messagelist.cpp index 743b0164..e5c25189 100644 --- a/tools/chatgui/src/messagelist.cpp +++ b/tools/chatgui/src/messagelist.cpp @@ -451,6 +451,7 @@ MessageList::MessageList(DbManager* db, AudioRecorder* recorder, QWidget *parent connect(msgDelegate, &MessageDelegate::avatarRequested, this, [](const QString& channel, quint64 owner) { showAvatarGallery(channel, owner); }); + connect(msgDelegate, &MessageDelegate::reactionRequested, this, &MessageList::requestReaction); msgDelegate->setDownloadCallback([](const QModelIndex& index) { QString target = index.data(MsgChannelIdRole).toString(); qint64 id = index.data(MsgIdRole).toLongLong(); @@ -1065,16 +1066,7 @@ void MessageList::onMessageContextMenu(const QPoint& pos) { if (ct == "poll") connect(menu.addAction(QCoreApplication::translate("ChatGui", "Vote / results")), &QAction::triggered, this, [this, messageId]() { showPoll(messageId); }); auto* reactions = menu.addMenu(QCoreApplication::translate("ChatGui", "React")); - QSet mine; - for (const auto& value : m_feedback.value(messageId).value("reactions").toArray()) - if (value.toObject().value("mine").toBool()) mine.insert(value.toObject().value("id").toInt()); - for (unsigned i = 0; i < CHAT_REACTION_COUNT; i++) { - auto* action = reactions->addAction(QString::fromUtf8(chat_reaction_emoji(i))); - action->setCheckable(true); action->setChecked(mine.contains(i)); - connect(action, &QAction::triggered, this, [this, messageId, i]() { - sendFeedback({{"cmd", "react"}, {"message_id", messageId}, {"emoji", (int)i}}); - }); - } + addReactionActions(reactions, idx.data(MsgChannelIdRole).toString(), messageId); connect(menu.addAction(QCoreApplication::translate("ChatGui", "Clear reactions")), &QAction::triggered, this, [this, messageId]() { sendFeedback({{"cmd", "react"}, {"message_id", messageId}, {"clear", true}}); }); } diff --git a/tools/chatgui/src/messagelist.h b/tools/chatgui/src/messagelist.h index 65cf75ff..fad3dc1e 100644 --- a/tools/chatgui/src/messagelist.h +++ b/tools/chatgui/src/messagelist.h @@ -21,6 +21,7 @@ class QLabel; class QPushButton; class RadioPanel; class QDialog; +class QMenu; struct MessageRow; class MessageList : public QWidget { @@ -90,6 +91,8 @@ private: void createPoll(); void showPoll(const QString& messageId); void applyFeedback(const QJsonObject& state); + void requestReaction(const QModelIndex& index, int emoji); + void addReactionActions(QMenu* menu, const QString& channel, const QString& messageId); QString authorName(quint64 nodeId) const; void restoreScrollPosition(const QString& channelId);