- u_async.c: process immediate_queue BEFORE epoll_wait in uasync_poll - stcp_link.c: defensive NULL etcp_conn/etcp_link in stcp_link_close - utun_instance.c: move stcp_server_list_destroy_all to Phase J (before ETCP), add deferred drain in both Phase J and L - stcp.h/c: STCP_HS_ENC_CLIENT 6→38, STCP_HS_ENC_SERVER 7→39, exchange ed25519_pubkey in both handshake directions - etcp_connections.c: copy peer_ed25519 from STCP to ETCP_CONN in etcp_link_enter_ready_tcp; guard link->etcp in tcp_link_close_cb - chat_sync.c: fix lk->conn NULL dereference for TCP links - test_stcp.c: update stcp_server_create/stcp_client_connect callsites - tools/chat_tcp_test: change transport udp→tcp
192 lines
5.8 KiB
C++
192 lines
5.8 KiB
C++
#include "chatview.h"
|
|
#include "messagedelegate.h"
|
|
#include <QPainter>
|
|
#include <QClipboard>
|
|
#include <QApplication>
|
|
#include <QDebug>
|
|
#include <QMouseEvent>
|
|
#include <QKeyEvent>
|
|
#include <QScrollBar>
|
|
|
|
ChatView::ChatView(QWidget *parent)
|
|
: QListView(parent)
|
|
{
|
|
setMouseTracking(true);
|
|
|
|
m_scrollDownBtn = new QPushButton(QStringLiteral("\u25BC"), this);
|
|
m_scrollDownBtn->setFixedSize(36, 36);
|
|
m_scrollDownBtn->setCursor(Qt::PointingHandCursor);
|
|
m_scrollDownBtn->setStyleSheet(
|
|
"QPushButton {"
|
|
" background: rgba(60,60,60,180);"
|
|
" color: #ffffff;"
|
|
" border: none;"
|
|
" border-radius: 18px;"
|
|
" font-size: 16px;"
|
|
"}"
|
|
"QPushButton:hover {"
|
|
" background: rgba(90,90,90,220);"
|
|
"}"
|
|
);
|
|
m_scrollDownBtn->setToolTip(tr("Scroll to bottom"));
|
|
m_scrollDownBtn->setVisible(false);
|
|
|
|
connect(m_scrollDownBtn, &QPushButton::clicked, this, [this]() {
|
|
scrollToBottom();
|
|
});
|
|
connect(verticalScrollBar(), &QScrollBar::valueChanged, this, &ChatView::updateScrollDownBtn);
|
|
connect(verticalScrollBar(), &QScrollBar::rangeChanged, this, &ChatView::updateScrollDownBtn);
|
|
|
|
m_scrollDownBtn->raise();
|
|
}
|
|
|
|
void ChatView::setBackgroundImage(const QString &path) {
|
|
if (!m_background.load(path)) {
|
|
qWarning() << "ChatView: failed to load background" << path;
|
|
return;
|
|
}
|
|
viewport()->update();
|
|
}
|
|
|
|
void ChatView::resizeEvent(QResizeEvent *event) {
|
|
QListView::resizeEvent(event);
|
|
scheduleDelayedItemsLayout();
|
|
updateScrollDownBtn();
|
|
}
|
|
|
|
void ChatView::scrollContentsBy(int dx, int dy) {
|
|
QListView::scrollContentsBy(dx, dy);
|
|
viewport()->update();
|
|
}
|
|
|
|
void ChatView::paintEvent(QPaintEvent *event) {
|
|
if (!m_background.isNull()) {
|
|
QPainter p(viewport());
|
|
p.drawTiledPixmap(viewport()->rect(), m_background);
|
|
}
|
|
QListView::paintEvent(event);
|
|
}
|
|
|
|
bool ChatView::isRowSelected(const QModelIndex &idx) const {
|
|
if (!m_selStart.isValid() || !m_selEnd.isValid() || !idx.isValid())
|
|
return false;
|
|
int r = idx.row();
|
|
int a = m_selStart.row();
|
|
int b = m_selEnd.row();
|
|
if (a > b) qSwap(a, b);
|
|
return r >= a && r <= b;
|
|
}
|
|
|
|
void ChatView::mousePressEvent(QMouseEvent *event) {
|
|
if (event->button() != Qt::LeftButton) {
|
|
QListView::mousePressEvent(event);
|
|
return;
|
|
}
|
|
if (hasSelection()) {
|
|
m_selStart = QPersistentModelIndex();
|
|
m_selEnd = QPersistentModelIndex();
|
|
m_textSel = ChatTextSelection{};
|
|
m_selecting = false;
|
|
viewport()->update();
|
|
}
|
|
QModelIndex idx = indexAt(viewport()->mapFrom(this, event->pos()));
|
|
m_pressIndex = QPersistentModelIndex(idx);
|
|
m_pressPos = event->pos();
|
|
QListView::mousePressEvent(event);
|
|
}
|
|
|
|
void ChatView::mouseMoveEvent(QMouseEvent *event) {
|
|
if (!m_selecting && m_pressIndex.isValid()) {
|
|
int dx = event->pos().x() - m_pressPos.x();
|
|
int dy = event->pos().y() - m_pressPos.y();
|
|
if (dx * dx + dy * dy >= 25) {
|
|
m_selStart = m_pressIndex;
|
|
m_selEnd = m_pressIndex;
|
|
m_selecting = true;
|
|
QRect r = visualRect(QModelIndex(m_pressIndex));
|
|
m_textSel.index = m_pressIndex;
|
|
m_textSel.clickPos = m_pressPos - r.topLeft();
|
|
m_textSel.dragPos = m_textSel.clickPos;
|
|
m_textSel.active = true;
|
|
viewport()->update();
|
|
}
|
|
}
|
|
if (!m_selecting) {
|
|
QModelIndex idx = indexAt(viewport()->mapFrom(this, event->pos()));
|
|
if (idx != QModelIndex(m_lastHovered)) {
|
|
m_lastHovered = idx;
|
|
emit hoveredIndexChanged(idx);
|
|
}
|
|
}
|
|
if (!m_selecting) {
|
|
QListView::mouseMoveEvent(event);
|
|
return;
|
|
}
|
|
QModelIndex idx = indexAt(viewport()->mapFrom(this, event->pos()));
|
|
if (idx.isValid()) {
|
|
m_selEnd = idx;
|
|
}
|
|
if (m_textSel.active) {
|
|
QRect r = visualRect(QModelIndex(m_textSel.index));
|
|
m_textSel.dragPos = event->pos() - r.topLeft();
|
|
}
|
|
viewport()->update();
|
|
event->accept();
|
|
}
|
|
|
|
void ChatView::mouseReleaseEvent(QMouseEvent *event) {
|
|
if (event->button() == Qt::LeftButton && m_selecting) {
|
|
m_selecting = false;
|
|
viewport()->update();
|
|
}
|
|
m_pressIndex = QPersistentModelIndex();
|
|
QListView::mouseReleaseEvent(event);
|
|
}
|
|
|
|
void ChatView::keyPressEvent(QKeyEvent *event) {
|
|
if (event == QKeySequence::Copy) {
|
|
copySelection();
|
|
return;
|
|
}
|
|
QListView::keyPressEvent(event);
|
|
}
|
|
|
|
void ChatView::copySelection() {
|
|
if (!hasSelection()) return;
|
|
auto *m = model();
|
|
if (!m) return;
|
|
|
|
if (selStart() == selEnd()) {
|
|
QModelIndex idx(selStart());
|
|
QString text = idx.data(MsgTextRole).toString();
|
|
if (m_textSel.startChar >= 0 && m_textSel.endChar > m_textSel.startChar)
|
|
text = text.mid(m_textSel.startChar, m_textSel.endChar - m_textSel.startChar);
|
|
QApplication::clipboard()->clear(QClipboard::Clipboard);
|
|
QApplication::clipboard()->setText(text);
|
|
} else {
|
|
QString r;
|
|
int a = selStart().row(), b = selEnd().row();
|
|
if (a > b) qSwap(a, b);
|
|
for (int i = a; i <= b; ++i) {
|
|
QModelIndex idx = m->index(i, 0);
|
|
if (!r.isEmpty()) r += '\n';
|
|
r += '[' + idx.data(MsgTimeRole).toString() + "] ";
|
|
r += idx.data(MsgAuthorRole).toString() + ": ";
|
|
r += idx.data(MsgTextRole).toString();
|
|
}
|
|
QApplication::clipboard()->clear(QClipboard::Clipboard);
|
|
QApplication::clipboard()->setText(r);
|
|
}
|
|
}
|
|
|
|
void ChatView::updateScrollDownBtn() {
|
|
QScrollBar* sb = verticalScrollBar();
|
|
bool atBottom = sb && (sb->value() >= sb->maximum());
|
|
m_scrollDownBtn->setVisible(!atBottom);
|
|
if (!atBottom) {
|
|
QRect vr = viewport()->geometry();
|
|
m_scrollDownBtn->move(vr.right() - 44, vr.bottom() - 44);
|
|
m_scrollDownBtn->raise();
|
|
}
|
|
}
|