Browse Source

Verify poll clicks preserve selection and message controls

master
evgeny 4 days ago
parent
commit
b6d8e822b4
  1. 3
      tools/chatgui/chatgui_doc.md
  2. 12
      tools/chatgui/src/messagefeedback.cpp
  3. 60
      tools/chatgui/tests/test_invite_links.cpp

3
tools/chatgui/chatgui_doc.md

@ -107,6 +107,9 @@ X11 idle time в Wayland-сессии не используется: он не
- Inline-анимированные эмодзи: при hover над сообщением — перебор кадров из LottieIcon, отрисовка поверх текста через QTextLayout.
- Растягивающийся InputBar (QTextEdit, Enter — отправка, Shift+Enter — перевод строки).
Карточка опроса открывает «Голосование / результаты» одиночным левым кликом по телу сообщения.
Перетаскивание выделяет текст; аватар и строка реакций обрабатывают свои действия.
**EmojiPanel** (`src/emojipanel.h`) — QTabWidget с категориями (смайлики, жесты, символы, анимации). Каждая категория — QGridLayout из EmojiButton (QPushButton без Q_OBJECT, hover-анимация через per-widget QTimer). Статические эмодзи — Unicode символы. Анимированные — TGS-файлы, рендерятся в EmojiButton::paintEvent.
**LottieIcon** (`src/lottieicon.h`) — загрузка и рендер TGS-анимаций:

12
tools/chatgui/src/messagefeedback.cpp

@ -189,9 +189,15 @@ void MessageList::createPoll() {
}
void MessageList::showPoll(const QString& messageId) {
if (m_pollDialog) { m_pollDialog->raise(); return; }
if (m_pollDialog) {
GUI_DEBUG("poll: raising open dialog message=%s", qPrintable(m_pollDialogMessage));
m_pollDialog->raise(); return;
}
auto state = m_feedback.value(messageId), poll = state.value("poll").toObject();
if (poll.isEmpty()) { m_pendingPoll = messageId; sendFeedback({{"cmd", "feedback"}, {"message_id", messageId}}); return; }
if (poll.isEmpty()) {
GUI_DEBUG("poll: requesting state ch=%s message=%s", qPrintable(m_currentChannelId), qPrintable(messageId));
m_pendingPoll = messageId; sendFeedback({{"cmd", "feedback"}, {"message_id", messageId}}); return;
}
QDialog dialog(this); dialog.setWindowTitle(feedbackText("Vote / results"));
m_pollDialog = &dialog; m_pollDialogMessage = messageId; auto* layout = new QVBoxLayout(&dialog);
auto* title = new QLabel(poll.value("question").toString(), &dialog); title->setTextFormat(Qt::PlainText); title->setWordWrap(true);
@ -236,5 +242,7 @@ void MessageList::showPoll(const QString& messageId) {
}
sendFeedback({{"cmd", "vote"}, {"message_id", messageId}, {"choices", ids}, {"custom", text}}); dialog.accept();
});
GUI_INFO("poll: opening dialog ch=%s message=%s options=%d", qPrintable(m_currentChannelId), qPrintable(messageId), int(values.size()));
applyFeedback(state); dialog.resize(450, 560); dialog.exec(); m_pollDialog = nullptr; m_pollDialogMessage.clear();
GUI_DEBUG("poll: dialog closed message=%s", qPrintable(messageId));
}

60
tools/chatgui/tests/test_invite_links.cpp

@ -3,11 +3,13 @@
#include "../src/chatview.h"
#include "../src/accountdelegate.h"
#include "../src/message_links.h"
#include "../../../lib/debug_config.h"
#include <QtTest>
#include <QStandardItemModel>
#include <QClipboard>
#include <QPainter>
#include <QDir>
#include <QScopedValueRollback>
extern "C" {
#include "chat/invite_link.h"
#include "chat/chat_member.h"
@ -16,6 +18,64 @@ extern "C" {
class InviteLinksTest : public QObject {
Q_OBJECT
private slots:
void pollLeftClick_data() {
QTest::addColumn<bool>("outgoing"); QTest::addColumn<int>("width");
for (bool outgoing : {false, true}) for (int width : {280, 600})
QTest::newRow(qPrintable(QString("%1-%2").arg(outgoing).arg(width))) << outgoing << width;
}
void pollLeftClick() {
QFETCH(bool, outgoing); QFETCH(int, width);
QScopedValueRollback<debug_level_t> logging(g_debug_config.category_levels[DEBUG_CATEGORY_GENERAL], DEBUG_LEVEL_INFO);
QStandardItemModel model;
auto* message = new QStandardItem;
const QString key(64, 'a');
message->setData("Which of these desktop options should everyone choose today?\nOption A\nOption B", MsgTextRole);
message->setData("poll", MsgContentTypeRole); message->setData(outgoing, MsgIsOutgoingRole);
message->setData("1001", MsgChannelIdRole); message->setData(key, MsgMessageKeyRole);
message->setData("Alice", MsgAuthorRole); message->setData("12:34", MsgTimeRole);
model.appendRow(message);
ChatView view; MessageDelegate delegate;
view.setModel(&model); view.setItemDelegate(&delegate); view.setFrameShape(QFrame::NoFrame);
view.setSelectionMode(QAbstractItemView::NoSelection); view.setEditTriggers(QAbstractItemView::NoEditTriggers);
QFont font = view.font(); font.setPixelSize(14); view.setFont(font);
view.resize(width, 400); view.show(); QTest::qWait(20);
QSignalSpy polls(&delegate, &MessageDelegate::pollRequested);
const QModelIndex index = model.index(0, 0);
const QPoint body(view.viewport()->width() / 2, QFontMetrics(view.font()).height() + 12);
QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::NoModifier, body);
QCOMPARE(polls.count(), 1); QCOMPARE(polls.first().first().toString(), key);
QTest::mouseClick(view.viewport(), Qt::RightButton, Qt::NoModifier, body);
QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(0, body.y()));
const QPoint footer(body.x(), view.visualRect(index).bottom() - 10);
QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::NoModifier, footer);
if (width == 600) {
const QPoint avatar(outgoing ? view.viewport()->width() - 35 : 35, view.visualRect(index).bottom() - 30);
QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::NoModifier, avatar);
}
QCOMPARE(polls.count(), 1);
QTest::mousePress(view.viewport(), Qt::LeftButton, Qt::NoModifier, body);
QTest::mouseRelease(view.viewport(), Qt::LeftButton, Qt::NoModifier, body + QPoint(20, 0));
QCOMPARE(polls.count(), 1);
QTest::mousePress(view.viewport(), Qt::LeftButton, Qt::NoModifier, body);
for (const QPoint point : {body + QPoint(20, 0), body}) {
QMouseEvent drag(QEvent::MouseMove, point, view.viewport()->mapToGlobal(point), Qt::NoButton, Qt::LeftButton, Qt::NoModifier);
QApplication::sendEvent(view.viewport(), &drag);
}
QTest::mouseRelease(view.viewport(), Qt::LeftButton, Qt::NoModifier, body);
QVERIFY(view.hasSelection()); QCOMPARE(polls.count(), 1); // Возврат мыши не превращает выделение в клик.
QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::NoModifier, body);
QCOMPARE(polls.count(), 2);
message->setData(QString(), MsgMessageKeyRole);
QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::NoModifier, body);
QCOMPARE(polls.count(), 2);
message->setData(key, MsgMessageKeyRole); message->setData("text/plain", MsgContentTypeRole);
QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::NoModifier, body);
QCOMPARE(polls.count(), 2);
}
void reactionFooter_data() {
QTest::addColumn<QString>("type"); QTest::addColumn<bool>("outgoing"); QTest::addColumn<int>("width");
for (const auto& type : {"text/plain", "audio/opus", "image/png", "video/mp4", "application/octet-stream", "poll"})

Loading…
Cancel
Save