Verify poll clicks preserve selection and message controls
This commit is contained in:
1 parent
ddcc36aa26
commit
b6d8e822b4
3 files changed
+73
-2
No files matched your search
@@ -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-анимаций:
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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"})
|
||||
|
||||
Reference in new issue
Block a user