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
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; |
|
} |
|
} |
|
}
|
|
|