Browse Source

refactor(group): move peer tracking logic to Group from GroupChatForm

* increase signal granularity
* reduce state in GroupChatForm
* remove differentiation of "joined" and "online" peers, it doesn't exist in toxcore and can't be tracked reliably in qTox
* add system message when peer name changes, even if due to alias
* add system message when self name changes, for clarity
reviewable/pr5661/r3
Anthony Bilinski 6 years ago
parent
commit
b7bd7c6215
No known key found for this signature in database
GPG Key ID: 2AA8E0DA1B31FB3C
  1. 70
      src/model/group.cpp
  2. 12
      src/model/group.h
  3. 110
      src/widget/form/groupchatform.cpp
  4. 10
      src/widget/form/groupchatform.h
  5. 3
      src/widget/friendlistwidget.cpp
  6. 15
      src/widget/groupwidget.cpp
  7. 4
      src/widget/groupwidget.h
  8. 5
      src/widget/widget.cpp
  9. 2
      src/widget/widget.h

70
src/model/group.cpp

@ -53,8 +53,8 @@ void Group::setName(const QString& newTitle)
if (!shortTitle.isEmpty() && title != shortTitle) { if (!shortTitle.isEmpty() && title != shortTitle) {
title = shortTitle; title = shortTitle;
emit displayedNameChanged(title); emit displayedNameChanged(title);
emit titleChangedByUser(groupId, title); emit titleChangedByUser(title);
emit titleChanged(groupId, selfName, title); emit titleChanged(selfName, title);
} }
} }
@ -64,7 +64,7 @@ void Group::setTitle(const QString& author, const QString& newTitle)
if (!shortTitle.isEmpty() && title != shortTitle) { if (!shortTitle.isEmpty() && title != shortTitle) {
title = shortTitle; title = shortTitle;
emit displayedNameChanged(title); emit displayedNameChanged(title);
emit titleChanged(groupId, author, title); emit titleChanged(author, title);
} }
} }
@ -80,40 +80,52 @@ QString Group::getDisplayedName() const
void Group::regeneratePeerList() void Group::regeneratePeerList()
{ {
// NOTE: there's a bit of a race here. Core emits a signal for both groupPeerlistChanged and groupPeerNameChanged
// back-to-back when a peer joins our group. If we get both before we process this slot, core->getGroupPeerNames
// will contain the new peer name, and we'll ignore the name changed signal, and emit a single userJoined with
// the correct name. But, if we receive the name changed signal a little later, we will emit userJoined before we
// have their username, using just their ToxPk, then shortly after emit another peerNameChanged signal. This can
// cause double-updated to UI and chatlog, but is unavoidable given the API of toxcore.
const Core* core = Core::getInstance(); const Core* core = Core::getInstance();
QStringList peers = core->getGroupPeerNames(toxGroupNum); QStringList peers = core->getGroupPeerNames(toxGroupNum);
const auto oldPeers = peerDisplayNames.keys(); const auto oldPeerNames = peerDisplayNames;
peerDisplayNames.clear(); peerDisplayNames.clear();
const int nPeers = peers.size(); const int nPeers = peers.size();
for (int i = 0; i < nPeers; ++i) { for (int i = 0; i < nPeers; ++i) {
const auto pk = core->getGroupPeerPk(toxGroupNum, i); const auto pk = core->getGroupPeerPk(toxGroupNum, i);
Friend* f = FriendList::findFriend(pk);
if (f != nullptr && f->hasAlias()) {
peerDisplayNames[pk] = f->getDisplayedName();
empty_nick[pk] = false;
continue;
}
empty_nick[pk] = peers[i].isEmpty();
peerDisplayNames[pk] = FriendList::decideNickname(pk, peers[i]); peerDisplayNames[pk] = FriendList::decideNickname(pk, peers[i]);
} }
if (avGroupchat) { for (const auto& pk: oldPeerNames.keys()) {
stopAudioOfDepartedPeers(oldPeers, peerDisplayNames); if (!peerDisplayNames.contains(pk)) {
emit userLeft(pk, oldPeerNames.value(pk));
stopAudioOfDepartedPeers(pk);
}
}
for (const auto& pk: peerDisplayNames.keys()) {
if (!oldPeerNames.contains(pk)) {
emit userJoined(pk, peerDisplayNames.value(pk));
}
}
for (const auto& pk: peerDisplayNames.keys()) {
if (oldPeerNames.contains(pk) && oldPeerNames.value(pk) != peerDisplayNames.value(pk)) {
emit peerNameChanged(pk, oldPeerNames.value(pk), peerDisplayNames.value(pk));
}
}
if (oldPeerNames.size() != nPeers) {
emit numPeersChanged(nPeers);
} }
emit userListChanged(groupId, peerDisplayNames);
}
bool Group::peerHasNickname(ToxPk pk)
{
return !empty_nick[pk];
} }
void Group::updateUsername(ToxPk pk, const QString newName) void Group::updateUsername(ToxPk pk, const QString newName)
{ {
peerDisplayNames[pk] = newName; const QString displayName = FriendList::decideNickname(pk, newName);
emit userListChanged(groupId, peerDisplayNames); assert(peerDisplayNames.contains(pk));
if (peerDisplayNames[pk] != displayName) {
// there could be no actual change even if their username changed due to an alias being set
const auto oldName = peerDisplayNames[pk];
peerDisplayNames[pk] = displayName;
emit peerNameChanged(pk, oldName, displayName);
}
} }
bool Group::isAvGroupchat() const bool Group::isAvGroupchat() const
@ -186,12 +198,10 @@ QString Group::getSelfName() const
return selfName; return selfName;
} }
void Group::stopAudioOfDepartedPeers(const QList<ToxPk>& oldPks, const QMap<ToxPk, QString>& newPks) void Group::stopAudioOfDepartedPeers(const ToxPk& peerPk)
{ {
Core* core = Core::getInstance(); if (avGroupchat) {
for(const auto& pk: oldPks) { Core* core = Core::getInstance();
if(!newPks.contains(pk)) { core->getAv()->invalidateGroupCallPeerSource(toxGroupNum, peerPk);
core->getAv()->invalidateGroupCallPeerSource(toxGroupNum, pk);
}
} }
} }

12
src/model/group.h

@ -59,18 +59,20 @@ public:
QString getSelfName() const; QString getSelfName() const;
signals: signals:
void titleChangedByUser(const GroupId& groupId, const QString& title); void titleChangedByUser(const QString& title);
void titleChanged(const GroupId& groupId, const QString& author, const QString& title); void titleChanged(const QString& author, const QString& title);
void userListChanged(const GroupId& groupId, const QMap<ToxPk, QString>& toxpks); void userJoined(const ToxPk& user, const QString& name);
void userLeft(const ToxPk& user, const QString& name);
void numPeersChanged(int numPeers);
void peerNameChanged(const ToxPk& peer, const QString& oldName, const QString& newName);
private: private:
void stopAudioOfDepartedPeers(const QList<ToxPk>& oldPks, const QMap<ToxPk, QString>& newPks); void stopAudioOfDepartedPeers(const ToxPk& peerPk);
private: private:
QString selfName; QString selfName;
QString title; QString title;
QMap<ToxPk, QString> peerDisplayNames; QMap<ToxPk, QString> peerDisplayNames;
QMap<ToxPk, bool> empty_nick;
bool hasNewMessages; bool hasNewMessages;
bool userWasMentioned; bool userWasMentioned;
int toxGroupNum; int toxGroupNum;

110
src/widget/form/groupchatform.cpp

@ -126,11 +126,14 @@ GroupChatForm::GroupChatForm(Group* chatGroup)
connect(headWidget, &ChatFormHeader::micMuteToggle, this, &GroupChatForm::onMicMuteToggle); connect(headWidget, &ChatFormHeader::micMuteToggle, this, &GroupChatForm::onMicMuteToggle);
connect(headWidget, &ChatFormHeader::volMuteToggle, this, &GroupChatForm::onVolMuteToggle); connect(headWidget, &ChatFormHeader::volMuteToggle, this, &GroupChatForm::onVolMuteToggle);
connect(headWidget, &ChatFormHeader::nameChanged, chatGroup, &Group::setName); connect(headWidget, &ChatFormHeader::nameChanged, chatGroup, &Group::setName);
connect(group, &Group::userListChanged, this, &GroupChatForm::onUserListChanged);
connect(group, &Group::titleChanged, this, &GroupChatForm::onTitleChanged); connect(group, &Group::titleChanged, this, &GroupChatForm::onTitleChanged);
connect(group, &Group::userJoined, this, &GroupChatForm::onUserJoined);
connect(group, &Group::userLeft, this, &GroupChatForm::onUserLeft);
connect(group, &Group::peerNameChanged, this, &GroupChatForm::onPeerNameChanged);
connect(group, &Group::numPeersChanged, this, &GroupChatForm::updateUserCount);
connect(&Settings::getInstance(), &Settings::blackListChanged, this, &GroupChatForm::updateUserNames); connect(&Settings::getInstance(), &Settings::blackListChanged, this, &GroupChatForm::updateUserNames);
onUserListChanged(); updateUserNames();
setAcceptDrops(true); setAcceptDrops(true);
Translator::registerHandler(std::bind(&GroupChatForm::retranslateUi, this), this); Translator::registerHandler(std::bind(&GroupChatForm::retranslateUi, this), this);
} }
@ -165,32 +168,8 @@ void GroupChatForm::onSendTriggered()
} }
} }
/** void GroupChatForm::onTitleChanged(const QString& author, const QString& title)
* @brief This slot is intended to connect to Group::userListChanged signal.
* Brief list of actions made by slot:
* 1) sets text of how many people are in the group;
* 2) creates lexicographically sorted comma-separated list of user names, each name in its own
* label;
* 3) sets call button style depending on peer count and etc.
*/
void GroupChatForm::onUserListChanged()
{
updateUserCount();
updateUserNames();
sendJoinLeaveMessages();
// Enable or disable call button
const int peersCount = group->getPeersCount();
const bool online = peersCount > 1;
headWidget->updateCallButtons(online, inCall);
if (inCall && (!online || !group->isAvGroupchat())) {
leaveGroupCall();
}
}
void GroupChatForm::onTitleChanged(const GroupId& groupId, const QString& author, const QString& title)
{ {
Q_UNUSED(groupId);
if (author.isEmpty()) { if (author.isEmpty()) {
return; return;
} }
@ -306,61 +285,22 @@ void GroupChatForm::updateUserNames()
} }
} }
void GroupChatForm::sendJoinLeaveMessages() void GroupChatForm::onUserJoined(const ToxPk& user, const QString& name)
{ {
const auto peers = group->getPeerList(); addSystemInfoMessage(tr("%1 has joined the group").arg(name), ChatMessage::INFO, QDateTime::currentDateTime());
updateUserNames();
// no need to do anything without any peers }
if (peers.isEmpty()) {
return;
}
// generate user list from the current group if it's empty void GroupChatForm::onUserLeft(const ToxPk& user, const QString& name)
if (groupLast.isEmpty()) { {
groupLast = group->getPeerList(); addSystemInfoMessage(tr("%1 has left the group").arg(name), ChatMessage::INFO, QDateTime::currentDateTime());
return; updateUserNames();
} }
// user joins void GroupChatForm::onPeerNameChanged(const ToxPk& peer, const QString& oldName, const QString& newName)
for (const auto& peerPk : peers.keys()) { {
const QString name = FriendList::decideNickname(peerPk, peers.value(peerPk)); addSystemInfoMessage(tr("%1 is now known as %2").arg(oldName, newName), ChatMessage::INFO, QDateTime::currentDateTime());
if (!firstTime.value(peerPk, false)) { updateUserNames();
if (!groupLast.contains(peerPk)) {
if (group->peerHasNickname(peerPk)) {
firstTime[peerPk] = true;
groupLast.insert(peerPk, name);
addSystemInfoMessage(tr("%1 is online").arg(name), ChatMessage::INFO, QDateTime::currentDateTime());
continue;
}
addSystemInfoMessage(tr("A new user has connected to the group"), ChatMessage::INFO, QDateTime::currentDateTime());
}
firstTime[peerPk] = true;
continue;
}
if (!groupLast.contains(peerPk)) {
groupLast.insert(peerPk, name);
addSystemInfoMessage(tr("%1 has joined the group").arg(name), ChatMessage::INFO, QDateTime::currentDateTime());
} else {
Friend *f = FriendList::findFriend(peerPk);
if (groupLast[peerPk] != name
&& peers.value(peerPk) == name
&& peerPk != Core::getInstance()->getSelfPublicKey() // ignore myself
&& !(f != nullptr && f->hasAlias()) // ignore friends with aliases
) {
addSystemInfoMessage(tr("%1 is now known as %2").arg(groupLast[peerPk], name), ChatMessage::INFO, QDateTime::currentDateTime());
groupLast[peerPk] = name;
}
}
}
// user leaves
for (const auto& peerPk : groupLast.keys()) {
const QString name = FriendList::decideNickname(peerPk, groupLast.value(peerPk));
if (!peers.contains(peerPk)) {
groupLast.remove(peerPk);
firstTime.remove(peerPk);
addSystemInfoMessage(tr("%1 has left the group").arg(name), ChatMessage::INFO, QDateTime::currentDateTime());
}
}
} }
void GroupChatForm::peerAudioPlaying(ToxPk peerPk) void GroupChatForm::peerAudioPlaying(ToxPk peerPk)
@ -510,15 +450,19 @@ void GroupChatForm::keyReleaseEvent(QKeyEvent* ev)
/** /**
* @brief Updates users' count label text * @brief Updates users' count label text
*/ */
void GroupChatForm::updateUserCount() void GroupChatForm::updateUserCount(int numPeers)
{ {
const int peersCount = group->getPeersCount(); nusersLabel->setText(tr("%n user(s) in chat", "Number of users in chat", numPeers));
nusersLabel->setText(tr("%n user(s) in chat", "Number of users in chat", peersCount)); const bool online = numPeers > 1;
headWidget->updateCallButtons(online, inCall);
if (inCall && (!online || !group->isAvGroupchat())) {
leaveGroupCall();
}
} }
void GroupChatForm::retranslateUi() void GroupChatForm::retranslateUi()
{ {
updateUserCount(); updateUserCount(group->getPeersCount());
} }
void GroupChatForm::onLabelContextMenuRequested(const QPoint& localPos) void GroupChatForm::onLabelContextMenuRequested(const QPoint& localPos)

10
src/widget/form/groupchatform.h

@ -49,8 +49,10 @@ private slots:
void onMicMuteToggle(); void onMicMuteToggle();
void onVolMuteToggle(); void onVolMuteToggle();
void onCallClicked(); void onCallClicked();
void onUserListChanged(); void onUserJoined(const ToxPk& user, const QString& name);
void onTitleChanged(const GroupId& groupId, const QString& author, const QString& title); void onUserLeft(const ToxPk& user, const QString& name);
void onPeerNameChanged(const ToxPk& peer, const QString& oldName, const QString& newName);
void onTitleChanged(const QString& author, const QString& title);
void searchInBegin(const QString& phrase, const ParameterSearch& parameter) override; void searchInBegin(const QString& phrase, const ParameterSearch& parameter) override;
void onSearchUp(const QString& phrase, const ParameterSearch& parameter) override; void onSearchUp(const QString& phrase, const ParameterSearch& parameter) override;
void onSearchDown(const QString& phrase, const ParameterSearch& parameter) override; void onSearchDown(const QString& phrase, const ParameterSearch& parameter) override;
@ -66,7 +68,7 @@ protected:
private: private:
void retranslateUi(); void retranslateUi();
void updateUserCount(); void updateUserCount(int numPeers);
void updateUserNames(); void updateUserNames();
void sendJoinLeaveMessages(); void sendJoinLeaveMessages();
void leaveGroupCall(); void leaveGroupCall();
@ -75,8 +77,6 @@ private:
Group* group; Group* group;
QMap<ToxPk, QLabel*> peerLabels; QMap<ToxPk, QLabel*> peerLabels;
QMap<ToxPk, QTimer*> peerAudioTimers; QMap<ToxPk, QTimer*> peerAudioTimers;
QMap<ToxPk, QString> groupLast;
QMap<ToxPk, bool> firstTime;
FlowLayout* namesListLayout; FlowLayout* namesListLayout;
QLabel* nusersLabel; QLabel* nusersLabel;
TabCompleter* tabber; TabCompleter* tabber;

3
src/widget/friendlistwidget.cpp

@ -283,8 +283,7 @@ void FriendListWidget::addGroupWidget(GroupWidget* widget)
groupLayout.addSortedWidget(widget); groupLayout.addSortedWidget(widget);
Group* g = widget->getGroup(); Group* g = widget->getGroup();
connect(g, &Group::titleChanged, connect(g, &Group::titleChanged,
[=](const GroupId& groupId, const QString& author, const QString& name) { [=](const QString& author, const QString& name) {
Q_UNUSED(groupId);
Q_UNUSED(author); Q_UNUSED(author);
renameGroupWidget(widget, name); renameGroupWidget(widget, name);
}); });

15
src/widget/groupwidget.cpp

@ -55,11 +55,11 @@ GroupWidget::GroupWidget(std::shared_ptr<GroupChatroom> chatroom, bool compact)
Group* g = chatroom->getGroup(); Group* g = chatroom->getGroup();
nameLabel->setText(g->getName()); nameLabel->setText(g->getName());
updateUserCount(); updateUserCount(g->getPeersCount());
setAcceptDrops(true); setAcceptDrops(true);
connect(g, &Group::titleChanged, this, &GroupWidget::updateTitle); connect(g, &Group::titleChanged, this, &GroupWidget::updateTitle);
connect(g, &Group::userListChanged, this, &GroupWidget::updateUserCount); connect(g, &Group::numPeersChanged, this, &GroupWidget::updateUserCount);
connect(nameLabel, &CroppingLabel::editFinished, g, &Group::setName); connect(nameLabel, &CroppingLabel::editFinished, g, &Group::setName);
Translator::registerHandler(std::bind(&GroupWidget::retranslateUi, this), this); Translator::registerHandler(std::bind(&GroupWidget::retranslateUi, this), this);
} }
@ -69,9 +69,8 @@ GroupWidget::~GroupWidget()
Translator::unregister(this); Translator::unregister(this);
} }
void GroupWidget::updateTitle(const GroupId& groupId, const QString& author, const QString& newName) void GroupWidget::updateTitle(const QString& author, const QString& newName)
{ {
Q_UNUSED(groupId);
Q_UNUSED(author); Q_UNUSED(author);
nameLabel->setText(newName); nameLabel->setText(newName);
} }
@ -159,10 +158,9 @@ void GroupWidget::mouseMoveEvent(QMouseEvent* ev)
} }
} }
void GroupWidget::updateUserCount() void GroupWidget::updateUserCount(int numPeers)
{ {
int peersCount = chatroom->getGroup()->getPeersCount(); statusMessageLabel->setText(tr("%n user(s) in chat", "Number of users in chat", numPeers));
statusMessageLabel->setText(tr("%n user(s) in chat", "Number of users in chat", peersCount));
} }
void GroupWidget::setAsActiveChatroom() void GroupWidget::setAsActiveChatroom()
@ -262,5 +260,6 @@ void GroupWidget::setName(const QString& name)
void GroupWidget::retranslateUi() void GroupWidget::retranslateUi()
{ {
updateUserCount(); const Group* group = chatroom->getGroup();
updateUserCount(group->getPeersCount());
} }

4
src/widget/groupwidget.h

@ -57,8 +57,8 @@ protected:
private slots: private slots:
void retranslateUi(); void retranslateUi();
void updateTitle(const GroupId& groupId, const QString& author, const QString& newName); void updateTitle(const QString& author, const QString& newName);
void updateUserCount(); void updateUserCount(int numPeers);
public: public:
GroupId groupId; GroupId groupId;

5
src/widget/widget.cpp

@ -1810,9 +1810,10 @@ void Widget::onGroupTitleChanged(uint32_t groupnumber, const QString& author, co
widget->searchName(ui->searchContactText->text(), filterGroups(filter)); widget->searchName(ui->searchContactText->text(), filterGroups(filter));
} }
void Widget::titleChangedByUser(const GroupId& groupId, const QString& title) void Widget::titleChangedByUser(const QString& title)
{ {
const auto& group = GroupList::findGroup(groupId); const auto* group = qobject_cast<Group*>(sender());
assert(group != nullptr);
emit changeGroupTitle(group->getId(), title); emit changeGroupTitle(group->getId(), title);
} }

2
src/widget/widget.h

@ -176,7 +176,7 @@ public slots:
void onGroupPeerlistChanged(uint32_t groupnumber); void onGroupPeerlistChanged(uint32_t groupnumber);
void onGroupPeerNameChanged(uint32_t groupnumber, const ToxPk& peerPk, const QString& newName); void onGroupPeerNameChanged(uint32_t groupnumber, const ToxPk& peerPk, const QString& newName);
void onGroupTitleChanged(uint32_t groupnumber, const QString& author, const QString& title); void onGroupTitleChanged(uint32_t groupnumber, const QString& author, const QString& title);
void titleChangedByUser(const GroupId& groupId, const QString& title); void titleChangedByUser(const QString& title);
void onGroupPeerAudioPlaying(int groupnumber, ToxPk peerPk); void onGroupPeerAudioPlaying(int groupnumber, ToxPk peerPk);
void onGroupSendFailed(uint32_t groupnumber); void onGroupSendFailed(uint32_t groupnumber);
void onFriendTypingChanged(uint32_t friendnumber, bool isTyping); void onFriendTypingChanged(uint32_t friendnumber, bool isTyping);

Loading…
Cancel
Save