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