Browse Source

Track radio speakers independently of member list refreshes

master
evgeny 4 days ago
parent
commit
5cbab70035
  1. 58
      tools/chatgui/src/memberlistmodel.cpp
  2. 9
      tools/chatgui/src/memberlistmodel.h

58
tools/chatgui/src/memberlistmodel.cpp

@ -1,5 +1,6 @@
#include <QCoreApplication>
#include "memberlistmodel.h"
#include "debug_ui.h"
#include "avatars/avatarmanager.h"
#include "../transport/gui_bridge.h"
#include <QPainter>
@ -76,6 +77,7 @@ QVariant MemberListModel::data(const QModelIndex& index, int role) const {
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;
if (role == RadioSpeakingRole) return it->isSelf ? m_selfTransmitting : m_radioTalkers.contains(it->nodeId);
return {};
}
return {};
@ -83,6 +85,11 @@ QVariant MemberListModel::data(const QModelIndex& index, int role) const {
void MemberListModel::setChannel(const QString& channelId, bool isOwner) {
beginResetModel();
if (m_channelId != channelId) {
DEBUG_DEBUG(DEBUG_CATEGORY_RADIO, "radio members: channel %s -> %s cleared talkers=%d self_tx=%d",
qPrintable(m_channelId), qPrintable(channelId), int(m_radioTalkers.size()), m_selfTransmitting);
m_radioTalkers.clear(); m_radioActive = false; m_selfTransmitting = false;
}
m_channelId = channelId;
m_isOwner = isOwner;
m_totalCount = 0;
@ -188,12 +195,7 @@ void MemberListModel::onMemberUpdated(const QString& chId, const uint8_t* data)
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();
}
onMemberRemoved(chId, item.nodeId);
return;
}
@ -202,24 +204,64 @@ void MemberListModel::onMemberUpdated(const QString& chId, const uint8_t* data)
emit dataChanged(index(row), index(row));
} else {
/* new member — add at end */
row = m_totalCount++;
m_cache[row] = item;
row = m_totalCount;
beginInsertRows(QModelIndex(), row, row);
m_cache[row] = item;
m_totalCount++;
endInsertRows();
}
}
void MemberListModel::onMemberRemoved(const QString& chId, uint64_t nodeId) {
if (chId != m_channelId) return;
if (m_radioTalkers.remove(nodeId))
DEBUG_DEBUG(DEBUG_CATEGORY_RADIO, "radio members: removed talker ch=%s node=%016llx",
qPrintable(chId), (unsigned long long)nodeId);
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);
for (int next = row + 1; next < m_totalCount; ++next) m_cache[next - 1] = m_cache.take(next);
m_totalCount--;
endRemoveRows();
return;
}
}
}
/* Состояние собственного TX берём из аудио-движка: оно включает PTT и VAD. */
void MemberListModel::setRadioState(quint64 groupId, bool active, bool transmitting) {
active = active && groupId != 0 && groupId == m_channelId.toULongLong();
transmitting = active && transmitting;
if (m_radioActive == active && m_selfTransmitting == transmitting) return;
DEBUG_DEBUG(DEBUG_CATEGORY_RADIO, "radio members: state ch=%s group=%016llx active=%d self_tx=%d talkers=%d",
qPrintable(m_channelId), (unsigned long long)groupId, active, transmitting, int(m_radioTalkers.size()));
m_radioActive = active; m_selfTransmitting = transmitting;
if (!active) m_radioTalkers.clear();
if (m_totalCount) emit dataChanged(index(0), index(m_totalCount - 1), {RadioSpeakingRole});
}
/* START может прийти до списка участников; STOP снимает только указанный источник. */
void MemberListModel::onRadioTalk(quint64 groupId, quint64 nodeId, bool on) {
if (!groupId || !nodeId) {
DEBUG_WARN(DEBUG_CATEGORY_RADIO, "radio members: invalid talk group=%016llx node=%016llx on=%d",
(unsigned long long)groupId, (unsigned long long)nodeId, on);
return;
}
if (groupId != m_channelId.toULongLong() || (on && !m_radioActive)) {
DEBUG_DEBUG(DEBUG_CATEGORY_RADIO, "radio members: ignored talk ch=%s group=%016llx node=%016llx on=%d active=%d",
qPrintable(m_channelId), (unsigned long long)groupId, (unsigned long long)nodeId, on, m_radioActive);
return;
}
if (m_radioTalkers.contains(nodeId) == on) return;
if (on) m_radioTalkers.insert(nodeId);
else m_radioTalkers.remove(nodeId);
DEBUG_DEBUG(DEBUG_CATEGORY_RADIO, "radio members: talk ch=%s node=%016llx on=%d talkers=%d",
qPrintable(m_channelId), (unsigned long long)nodeId, on, int(m_radioTalkers.size()));
for (auto it = m_cache.cbegin(); it != m_cache.cend(); ++it) if (it->nodeId == nodeId) {
emit dataChanged(index(it.key()), index(it.key()), {RadioSpeakingRole});
return;
}
}

9
tools/chatgui/src/memberlistmodel.h

@ -6,6 +6,7 @@
#include <QAbstractListModel>
#include <QColor>
#include <QMap>
#include <QSet>
#include <QString>
#include <QIcon>
#include <QPixmap>
@ -30,6 +31,7 @@ public:
/* Высота строки 36 px, между аватарками остаётся 2 px. */
static constexpr int kRowHeight = 36;
static constexpr int kAvatarSize = kRowHeight - 2;
static constexpr int RadioSpeakingRole = Qt::UserRole + 5;
explicit MemberListModel(QObject* parent = nullptr);
@ -47,6 +49,10 @@ public:
void onMemberUpdated(const QString& chId, const uint8_t* data);
void onMemberRemoved(const QString& chId, uint64_t nodeId);
/* Эфемерное состояние рации; вызывается в GUI-потоке, независимо от обновлений строк. */
void setRadioState(quint64 groupId, bool active, bool transmitting);
void onRadioTalk(quint64 groupId, quint64 nodeId, bool on);
private:
void loadBatch(int from, int count);
QPixmap makeAvatar(QColor color, QChar letter) const;
@ -55,6 +61,9 @@ private:
bool m_isOwner = false;
int m_totalCount = 0;
mutable QMap<int, MemberListItem> m_cache;
QSet<quint64> m_radioTalkers;
bool m_radioActive = false;
bool m_selfTransmitting = false;
QTimer* m_rttTimer;
};

Loading…
Cancel
Save