You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

198 lines
6.1 KiB

#include "memberlistmodel.h"
#include "../transport/gui_bridge.h"
#include <QPainter>
#include <QFont>
#include <QColor>
#include <cstring>
extern "C" {
#include "../../../src/chat/chat_core.h"
#include "../../../src/chat/chat_member.h"
#include "../../../lib/mem.h"
}
#define CHAT_MEMBER_DISPLAY_SIZE 79
MemberListModel::MemberListModel(QObject* parent)
: QAbstractListModel(parent)
, m_rttTimer(new QTimer(this))
{
m_rttTimer->setInterval(2000);
connect(m_rttTimer, &QTimer::timeout, this, &MemberListModel::refreshRtt);
m_rttTimer->start();
}
int MemberListModel::rowCount(const QModelIndex& parent) const {
(void)parent;
return m_totalCount;
}
QVariant MemberListModel::data(const QModelIndex& index, int role) const {
if (!index.isValid() || index.row() < 0 || index.row() >= m_totalCount)
return {};
auto it = m_cache.find(index.row());
if (it != m_cache.end()) {
if (role == Qt::DisplayRole) return it->name;
if (role == Qt::DecorationRole) return QVariant::fromValue(QIcon(it->avatar));
if (role == Qt::UserRole + 1) {
QString sub;
if (it->rtt >= 0) sub += QString("rtt=%1ms").arg(it->rtt / 10);
if (it->natType >= 0) {
static const char* nat[] = {"NET","DIR","EIM","STR","NAT"};
const char* nt = (it->natType <= 4) ? nat[it->natType] : "?";
if (!sub.isEmpty()) sub += " ";
sub += nt;
}
return sub;
}
if (role == Qt::ToolTipRole)
return QString(it->online ? "online" : "offline") + " | 0x" +
QString("%1").arg(it->nodeId, 16, 16, QChar('0'));
if (role == Qt::UserRole + 2) return it->isSelf;
if (role == Qt::UserRole + 3) return it->flags;
if (role == Qt::UserRole + 4) return (bool)it->storage;
return {};
}
return {};
}
void MemberListModel::setChannel(const QString& channelId, bool isOwner) {
beginResetModel();
m_channelId = channelId;
m_isOwner = isOwner;
m_totalCount = 0;
m_cache.clear();
endResetModel();
if (!channelId.isEmpty()) {
struct chat_member_list_req* req = (struct chat_member_list_req*)u_calloc(1, sizeof(*req));
if (!req) return;
req->inst = gui_bridge_get_inst();
strncpy(req->ch_id, channelId.toUtf8().constData(), sizeof(req->ch_id) - 1);
gui_bridge_post_uasync_fn(chat_core_request_member_list_trampoline, req);
}
}
void MemberListModel::refresh() {
if (!m_channelId.isEmpty()) setChannel(m_channelId, m_isOwner);
}
void MemberListModel::refreshRtt() {
if (m_channelId.isEmpty()) return;
struct chat_member_list_req* req = (struct chat_member_list_req*)u_calloc(1, sizeof(*req));
if (!req) return;
req->inst = gui_bridge_get_inst();
strncpy(req->ch_id, m_channelId.toUtf8().constData(), sizeof(req->ch_id) - 1);
gui_bridge_post_uasync_fn(chat_core_request_member_rtt_trampoline, req);
}
quint64 MemberListModel::nodeIdAt(int row) const {
auto it = m_cache.find(row);
return it != m_cache.end() ? it->nodeId : 0;
}
bool MemberListModel::isOnlineAt(int row) const {
auto it = m_cache.find(row);
return it != m_cache.end() ? it->online : false;
}
static void deserialize_member(const uint8_t* src, MemberListItem& item) {
memcpy(&item.nodeId, src, 8);
uint8_t online = src[8];
item.isSelf = src[9] != 0;
item.flags = src[10];
uint16_t rtt; memcpy(&rtt, src + 11, 2);
item.rtt = (rtt == 0xFFFF) ? -1 : (int)rtt;
int nat = (int)src[13];
item.natType = (nat == 0xFF) ? -1 : nat;
item.storage = src[14];
item.online = (online != 0);
char name[64];
memcpy(name, src + 15, 64); name[63] = '\0';
item.name = QString::fromUtf8(name);
QColor c = item.online ? QColor("#4CAF50") : QColor("#9E9E9E");
QPixmap pm(24, 24); pm.fill(Qt::transparent);
QPainter p(&pm);
p.setRenderHint(QPainter::Antialiasing);
p.setBrush(c);
p.setPen(Qt::NoPen);
p.drawEllipse(2, 2, 20, 20);
p.setPen(Qt::white);
QFont f; f.setBold(true); f.setPixelSize(12);
p.setFont(f);
p.drawText(QRect(0, 0, 24, 24), Qt::AlignCenter, item.name.left(1));
p.end();
item.avatar = pm;
}
void MemberListModel::onMemberList(const QString& chId, int count, const uint8_t* data, int dataLen) {
(void)dataLen;
if (chId != m_channelId) return;
beginResetModel();
m_totalCount = 0;
m_cache.clear();
int row = 0;
for (int i = 0; i < count; i++) {
MemberListItem item;
deserialize_member(data + i * CHAT_MEMBER_DISPLAY_SIZE, item);
if ((item.flags & CHAT_MEMBER_FLAG_DELETED) && !m_isOwner) continue;
m_cache[row++] = item;
}
m_totalCount = row;
endResetModel();
}
void MemberListModel::onMemberUpdated(const QString& chId, const uint8_t* data) {
if (chId != m_channelId) return;
MemberListItem item;
deserialize_member(data, item);
/* find existing row by nodeId */
int row = -1;
for (auto it = m_cache.begin(); it != m_cache.end(); ++it) {
if (it->nodeId == item.nodeId) { row = it.key(); break; }
}
if ((item.flags & CHAT_MEMBER_FLAG_DELETED) && !m_isOwner) {
/* deleted member — remove from non-owner's list */
if (row >= 0) {
beginRemoveRows(QModelIndex(), row, row);
m_cache.remove(row);
m_totalCount--;
endRemoveRows();
}
return;
}
if (row >= 0) {
m_cache[row] = item;
emit dataChanged(index(row), index(row));
} else {
/* new member — add at end */
row = m_totalCount++;
m_cache[row] = item;
beginInsertRows(QModelIndex(), row, row);
endInsertRows();
}
}
void MemberListModel::onMemberRemoved(const QString& chId, uint64_t nodeId) {
if (chId != m_channelId) return;
for (auto it = m_cache.begin(); it != m_cache.end(); ++it) {
if (it->nodeId == nodeId) {
int row = it.key();
beginRemoveRows(QModelIndex(), row, row);
m_cache.remove(row);
m_totalCount--;
endRemoveRows();
return;
}
}
}