mumble-voip_mumble/src/mumble/TalkingUI.cpp

885 lines
29 KiB
C++

// Copyright 2020-2023 The Mumble Developers. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file at the root of the
// Mumble source tree or at <https://www.mumble.info/LICENSE>.
#include "TalkingUI.h"
#include "Channel.h"
#include "ChannelListenerManager.h"
#include "ClientUser.h"
#include "MainWindow.h"
#include "TalkingUIComponent.h"
#include "UserModel.h"
#include "widgets/MultiStyleWidgetWrapper.h"
#include "Global.h"
#include <QGroupBox>
#include <QGuiApplication>
#include <QHBoxLayout>
#include <QItemSelectionModel>
#include <QLabel>
#include <QModelIndex>
#include <QMouseEvent>
#include <QPalette>
#include <QScreen>
#include <QTextDocumentFragment>
#include <QVBoxLayout>
#include <QtCore/QDateTime>
#include <QtCore/QStringList>
#include <QtCore/QTimer>
#include <QtGui/QPainter>
#include <QtGui/QPixmap>
#include <algorithm>
#include <cassert>
TalkingUI::TalkingUI(QWidget *parent) : QWidget(parent), m_containers(), m_currentSelection(nullptr) {
setupUI();
}
int TalkingUI::findContainer(int associatedChannelID, ContainerType type) const {
for (std::size_t i = 0; i < m_containers.size(); i++) {
const std::unique_ptr< TalkingUIContainer > &currentContainer = m_containers[i];
if (currentContainer->getType() == type && currentContainer->getAssociatedChannelID() == associatedChannelID) {
return static_cast< int >(i);
}
}
return -1;
}
std::unique_ptr< TalkingUIContainer > TalkingUI::removeContainer(const TalkingUIContainer &container) {
return removeContainer(container.getAssociatedChannelID(), container.getType());
}
std::unique_ptr< TalkingUIContainer > TalkingUI::removeContainer(int associatedChannelID, ContainerType type) {
int index = findContainer(associatedChannelID, type);
std::unique_ptr< TalkingUIContainer > container(nullptr);
if (index >= 0) {
// Move the container out of the vector
container = std::move(m_containers[static_cast< std::size_t >(index)]);
m_containers.erase(m_containers.begin() + index);
// If the container is currently selected, clear the selection
if (isSelected(*container)) {
setSelection(EmptySelection());
}
}
return container;
}
std::unique_ptr< TalkingUIContainer > TalkingUI::removeIfSuperfluous(const TalkingUIContainer &container) {
if (container.isEmpty() && !container.isPermanent()) {
return removeContainer(container);
}
return nullptr;
}
struct container_ptr_less {
bool operator()(const std::unique_ptr< TalkingUIContainer > &first,
const std::unique_ptr< TalkingUIContainer > &second) {
return *first < *second;
}
};
void TalkingUI::sortContainers() {
// Remove all containers from the UI
for (auto &currentContainer : m_containers) {
layout()->removeWidget(currentContainer->getWidget());
}
// Sort the containers
std::sort(m_containers.begin(), m_containers.end(), container_ptr_less());
// Add them again in the order they appear in the vector
for (auto &currentContainer : m_containers) {
layout()->addWidget(currentContainer->getWidget());
}
}
TalkingUIUser *TalkingUI::findUser(unsigned int userSession) {
for (auto &currentContainer : m_containers) {
TalkingUIEntry *entry = currentContainer->get(userSession, EntryType::USER);
if (entry) {
// We know that it must be a TalkingUIUser since that is what we searched for
return static_cast< TalkingUIUser * >(entry);
}
}
return nullptr;
}
void TalkingUI::removeUser(unsigned int userSession) {
TalkingUIUser *userEntry = findUser(userSession);
if (userEntry) {
// If the user that is going to be deleted is currently selected, clear the selection
if (isSelected(*userEntry)) {
setSelection(EmptySelection());
}
TalkingUIContainer *userContainer = userEntry->getContainer();
userContainer->removeEntry(userEntry);
removeIfSuperfluous(*userContainer);
updateUI();
}
}
void TalkingUI::addListener(const ClientUser *user, const Channel *channel) {
TalkingUIChannelListener *existingEntry = findListener(user->uiSession, channel->iId);
if (!existingEntry) {
// Only create entry if it doesn't exist yet
// First make sure the channel exists
addChannel(channel);
std::unique_ptr< TalkingUIContainer > &channelContainer = m_containers[static_cast< std::size_t >(
findContainer(static_cast< int >(channel->iId), ContainerType::CHANNEL))];
std::unique_ptr< TalkingUIChannelListener > listenerEntry =
std::make_unique< TalkingUIChannelListener >(*user, *channel);
channelContainer->addEntry(std::move(listenerEntry));
sortContainers();
}
}
TalkingUIChannelListener *TalkingUI::findListener(unsigned int userSession, unsigned int channelID) {
int channelIndex = findContainer(static_cast< int >(channelID), ContainerType::CHANNEL);
if (channelIndex >= 0) {
std::unique_ptr< TalkingUIContainer > &channelContainer =
m_containers[static_cast< std::size_t >(channelIndex)];
TalkingUIEntry *entry = channelContainer->get(userSession, EntryType::LISTENER);
if (entry) {
return static_cast< TalkingUIChannelListener * >(entry);
}
}
return nullptr;
}
void TalkingUI::removeListener(unsigned int userSession, unsigned int channelID) {
TalkingUIChannelListener *listenerEntry = findListener(userSession, channelID);
if (listenerEntry) {
// If the user that is going to be deleted is currently selected, clear the selection
if (isSelected(*listenerEntry)) {
setSelection(EmptySelection());
}
TalkingUIContainer *userContainer = listenerEntry->getContainer();
userContainer->removeEntry(listenerEntry);
removeIfSuperfluous(*userContainer);
updateUI();
}
}
void TalkingUI::removeAllListeners() {
// Find all listener entries
std::vector< TalkingUIEntry * > entriesToBeRemoved;
for (auto &currentContainer : m_containers) {
for (auto &currentEntry : currentContainer->getEntries()) {
if (currentEntry->getType() == EntryType::LISTENER) {
entriesToBeRemoved.push_back(currentEntry.get());
}
}
}
// remove the individual entries
for (auto currentEntry : entriesToBeRemoved) {
TalkingUIContainer *container = currentEntry->getContainer();
container->removeEntry(currentEntry);
removeIfSuperfluous(*container);
}
// if we removed something, update the UI
if (entriesToBeRemoved.size() > 0) {
updateUI();
}
}
void TalkingUI::setupUI() {
QVBoxLayout *layout = new QVBoxLayout;
setLayout(layout);
setWindowTitle(QObject::tr("Talking UI"));
setAttribute(Qt::WA_ShowWithoutActivating);
setWindowFlags(Qt::Dialog | Qt::WindowStaysOnTopHint);
// Hide the "?" (context help) button in the title bar of the widget as we don't want
// that due to it taking valuable screen space so that the title can't be displayed
// properly and as the TalkingUI doesn't provide context help anyways, this is not a big loss.
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
connect(Global::get().mw->qtvUsers->selectionModel(), &QItemSelectionModel::currentChanged, this,
&TalkingUI::on_mainWindowSelectionChanged);
}
void TalkingUI::setFontSize(MultiStyleWidgetWrapper &widgetWrapper) {
const double fontFactor = Global::get().s.iTalkingUI_RelativeFontSize / 100.0;
const int origLineHeight = QFontMetrics(font()).height();
if (font().pixelSize() >= 0) {
// font specified in pixels
uint32_t pixelSize = static_cast< uint32_t >(std::max(fontFactor * font().pixelSize(), 1.0));
widgetWrapper.setFontSize(pixelSize, true);
} else {
// font specified in points
uint32_t pointSize = static_cast< uint32_t >(std::max(fontFactor * font().pointSize(), 1.0));
widgetWrapper.setFontSize(pointSize, false);
}
m_currentLineHeight = static_cast< int >(std::max(origLineHeight * fontFactor, 1.0));
}
void TalkingUI::updateStatusIcons(const ClientUser *user) {
TalkingUIUser *userEntry = findUser(user->uiSession);
if (!userEntry) {
return;
}
TalkingUIUser::UserStatus status;
status.muted = user->bMute;
status.selfMuted = user->bSelfMute;
status.localMuted = user->bLocalMute;
status.deafened = user->bDeaf;
status.selfDeafened = user->bSelfDeaf;
userEntry->setStatus(status);
}
void TalkingUI::hideUser(unsigned int session) {
removeUser(session);
updateUI();
}
QString createChannelName(const Channel *chan, bool abbreviateName, int minPrefixChars, int minPostfixChars,
int idealMaxChars, int parentLevel, const QString &separator,
const QString &abbreviationIndicator, bool abbreviateCurrentChannel) {
if (!abbreviateName) {
return chan->qsName;
}
// Assemble list of relevant channel names (representing the channel hierarchy
QStringList nameList;
do {
nameList << chan->qsName;
chan = chan->cParent;
} while (chan && nameList.size() < (parentLevel + 1));
const bool reachedRoot = !chan;
// We also want to abbreviate names that nominally have the same amount of characters before and
// after abbreviation. However as we're typically not using mono-spaced fonts, the abbreviation
// indicator might still occupy less space than the original text.
const int abbreviableSize = minPrefixChars + minPostfixChars + abbreviationIndicator.size();
// Iterate over all names and check how many of them could be abbreviated
int totalCharCount = reachedRoot ? separator.size() : 0;
for (int i = 0; i < nameList.size(); i++) {
totalCharCount += nameList[i].size();
if (i + 1 < nameList.size()) {
// Account for the separator's size as well
totalCharCount += separator.size();
}
}
QString groupName = reachedRoot ? separator : QString();
for (int i = nameList.size() - 1; i >= 0; i--) {
if (totalCharCount > idealMaxChars && nameList[i].size() >= abbreviableSize
&& (abbreviateCurrentChannel || i != 0)) {
// Abbreviate the names as much as possible
groupName += nameList[i].left(minPrefixChars) + abbreviationIndicator + nameList[i].right(minPostfixChars);
} else {
groupName += nameList[i];
}
if (i != 0) {
groupName += separator;
}
}
return groupName;
}
void TalkingUI::addChannel(const Channel *channel) {
if (findContainer(static_cast< int >(channel->iId), ContainerType::CHANNEL) < 0) {
// Create a QGroupBox for this channel
const QString channelName = createChannelName(
channel, Global::get().s.bTalkingUI_AbbreviateChannelNames, Global::get().s.iTalkingUI_PrefixCharCount,
Global::get().s.iTalkingUI_PostfixCharCount, Global::get().s.iTalkingUI_MaxChannelNameLength,
Global::get().s.iTalkingUI_ChannelHierarchyDepth, Global::get().s.qsHierarchyChannelSeparator,
Global::get().s.qsTalkingUI_AbbreviationReplacement, Global::get().s.bTalkingUI_AbbreviateCurrentChannel);
std::unique_ptr< TalkingUIChannel > channelContainer =
std::make_unique< TalkingUIChannel >(channel->iId, channelName, *this);
QWidget *channelWidget = channelContainer->getWidget();
setFontSize(channelContainer->getStylableWidget());
layout()->addWidget(channelWidget);
m_containers.push_back(std::move(channelContainer));
}
}
TalkingUIUser *TalkingUI::findOrAddUser(const ClientUser *user) {
TalkingUIUser *oldUserEntry = findUser(user->uiSession);
bool nameMatches = true;
if (oldUserEntry) {
// We also verify whether the name for that user matches up (if it is contained in m_entries) in case
// a user didn't get removed from the map but its ID got reused by a new client.
nameMatches = oldUserEntry->getName() == user->qsName;
if (!nameMatches) {
// Hide and remove the stale user
hideUser(user->uiSession);
// reset pointer
oldUserEntry = nullptr;
}
}
// Make sure that the user's channel exists in this UI.
// Note that this has to be done **after** the name check above as
// the user might have been renamed in which case the abovementioned
// code block removes the old user entry. If that user was the only
// client in its channel, the channel gets removed as well. However
// the code below expects the user's channel to exist.
addChannel(user->cChannel);
if (!oldUserEntry || !nameMatches) {
// Create an entry for this user
bool isSelf = Global::get().uiSession == user->uiSession;
int channelIndex = findContainer(static_cast< int >(user->cChannel->iId), ContainerType::CHANNEL);
if (channelIndex < 0) {
qCritical("TalkingUI::findOrAddUser User's channel does not exist!");
return nullptr;
}
std::unique_ptr< TalkingUIContainer > &channelContainer =
m_containers[static_cast< std::size_t >(channelIndex)];
if (!channelContainer) {
qCritical("TalkingUI::findOrAddUser requesting unknown channel!");
return nullptr;
}
std::unique_ptr< TalkingUIUser > userEntry = std::make_unique< TalkingUIUser >(*user);
TalkingUIUser *newUserEntry = userEntry.get();
// * 1000 as the setting is in seconds whereas the timer expects milliseconds
userEntry->setLifeTime(static_cast< unsigned int >(Global::get().s.iTalkingUI_SilentUserLifeTime) * 1000);
bool isLocalUserAndLocalUserAlwaysVisible = isSelf && Global::get().s.bTalkingUI_LocalUserStaysVisible;
bool usersAlwaysVisible = Global::get().s.talkingUI_UsersAlwaysVisible;
bool isUserAlwaysVisible = usersAlwaysVisible || isLocalUserAndLocalUserAlwaysVisible;
userEntry->restrictLifetime(!isUserAlwaysVisible);
userEntry->setPriority(isSelf ? EntryPriority::HIGH : EntryPriority::DEFAULT);
QObject::connect(user, &ClientUser::localVolumeAdjustmentsChanged, this,
&TalkingUI::on_userLocalVolumeAdjustmentsChanged);
// If this user is currently selected, mark him/her as such
if (Global::get().mw && Global::get().mw->pmModel && Global::get().mw->pmModel->getSelectedUser() == user) {
setSelection(UserSelection(userEntry->getWidget(), userEntry->getAssociatedUserSession()));
}
// As the font size of the newly created widget did not adapt to any StyleSheet it might have inherited
// from its parents (the channel box), the size the talking icon is initialized to in the entry's
// constructor is incorrect. Therefore we have to explicitly update it here.
userEntry->setIconSize(m_currentLineHeight);
// Actually add the user to the respective channel
channelContainer->addEntry(std::move(userEntry));
sortContainers();
return newUserEntry;
} else {
return oldUserEntry;
}
}
void TalkingUI::moveUserToChannel(unsigned int userSession, unsigned int channelID) {
int targetChanIndex = findContainer(static_cast< int >(channelID), ContainerType::CHANNEL);
if (targetChanIndex < 0) {
qCritical("TalkingUI::moveUserToChannel Can't find channel for speaker");
return;
}
std::unique_ptr< TalkingUIContainer > &targetChannel = m_containers[static_cast< std::size_t >(targetChanIndex)];
if (targetChannel->contains(userSession, EntryType::USER)) {
// The given user is already in the target channel - nothing to do
return;
}
// Iterate all containers in order to find the one the user is currently in
TalkingUIUser *userEntry = findUser(userSession);
if (userEntry) {
TalkingUIContainer *oldContainer = userEntry->getContainer();
targetChannel->addEntry(oldContainer->removeEntry(userEntry));
removeIfSuperfluous(*oldContainer);
sortContainers();
} else {
qCritical("TalkingUI::moveUserToChannel Unable to locate user");
return;
}
updateUI();
}
void TalkingUI::updateUI() {
// Use timer to execute this after all other events have been processed
QTimer::singleShot(0, [this]() { adjustSize(); });
}
void TalkingUI::setSelection(const TalkingUISelection &selection) {
if (dynamic_cast< const EmptySelection * >(&selection)) {
// The selection is set to an empty selection
if (m_currentSelection) {
// There currently is a selection -> clear and remove it
m_currentSelection->discard();
m_currentSelection.reset();
}
} else {
if (m_currentSelection) {
if (selection == *m_currentSelection) {
// Selection hasn't actually changed
return;
}
// Discard old selection (it'll get deleted on re-assignment below)
m_currentSelection->discard();
}
// Use the new selection (which at this point we know is not the empty selection)
m_currentSelection = selection.cloneToHeap();
m_currentSelection->apply();
m_currentSelection->syncToMainWindow();
}
}
bool TalkingUI::isSelected(const TalkingUIComponent &component) const {
if (m_currentSelection) {
return *m_currentSelection == component.getWidget();
}
return false;
}
void TalkingUI::mousePressEvent(QMouseEvent *event) {
bool foundTarget = false;
for (auto &currentContainer : m_containers) {
QRect containerArea(currentContainer->getWidget()->mapToGlobal({ 0, 0 }),
currentContainer->getWidget()->size());
if (containerArea.contains(event->globalPos())) {
for (auto &currentEntry : currentContainer->getEntries()) {
QRect entryArea(currentEntry->getWidget()->mapToGlobal({ 0, 0 }), currentEntry->getWidget()->size());
if (entryArea.contains(event->globalPos())) {
switch (currentEntry->getType()) {
case EntryType::USER:
setSelection(
UserSelection(currentEntry->getWidget(), currentEntry->getAssociatedUserSession()));
break;
case EntryType::LISTENER:
TalkingUIChannelListener *listenerEntry =
static_cast< TalkingUIChannelListener * >(currentEntry.get());
setSelection(ListenerSelection(
listenerEntry->getWidget(), listenerEntry->getAssociatedUserSession(),
static_cast< unsigned int >(listenerEntry->getAssociatedChannelID())));
break;
}
foundTarget = true;
break;
}
}
if (!foundTarget) {
// Select channel itself
setSelection(ChannelSelection(currentContainer->getWidget(),
static_cast< unsigned int >(currentContainer->getAssociatedChannelID())));
foundTarget = true;
}
break;
}
}
if (foundTarget) {
if (event->button() == Qt::RightButton && Global::get().mw) {
// If an entry is selected and the right mouse button was clicked, we pretend as if the user had clicked on
// the client in the MainWindow. For this to work we map the global mouse position to the local coordinate
// system of the UserView in the MainWindow. The function will use some internal logic to determine the user
// to invoke the context menu on but if that fails (which in this case it will), it'll fall back to the
// currently selected item. This item we have updated to the correct one with the setSelection() call above
// resulting in the proper context menu being shown at the position of the mouse which in this case is in
// the TalkingUI.
QMetaObject::invokeMethod(Global::get().mw, "on_qtvUsers_customContextMenuRequested", Qt::QueuedConnection,
Q_ARG(QPoint, Global::get().mw->qtvUsers->mapFromGlobal(event->globalPos())),
Q_ARG(bool, false));
}
} else {
// Clear selection
setSelection(EmptySelection());
}
updateUI();
}
void TalkingUI::setVisible(bool visible) {
if (visible) {
adjustSize();
}
QWidget::setVisible(visible);
}
QSize TalkingUI::sizeHint() const {
// Prefer to occupy at least 10% of the screen's size
// This aims to be a good compromise between not being in the way and not
// being too small to being handled properly.
int width = static_cast< int >(QGuiApplication::screens()[0]->availableSize().width() * 0.1);
return { width, 0 };
}
QSize TalkingUI::minimumSizeHint() const {
return { 0, 0 };
}
void TalkingUI::on_talkingStateChanged() {
ClientUser *user = qobject_cast< ClientUser * >(sender());
if (!user) {
// If the user that caused this event doesn't exist anymore, it means that it
// got deleted in the meantime. This in turn means that the user disconnected
// from the server. In that case it has been removed via on_clientDisconnected
// already (or shortly will be), so it is safe to silently ignore this case
// here.
return;
}
if (!user->cChannel) {
// If the user doesn't have an associated channel, something's either wrong
// or that user has just disconnected. In either way, we want to make sure
// that this user won't stick around in the UI.
hideUser(user->uiSession);
return;
}
TalkingUIUser *userEntry = findOrAddUser(user);
if (userEntry) {
userEntry->setTalkingState(user->tsState);
}
updateUI();
}
void TalkingUI::on_mainWindowSelectionChanged(const QModelIndex &current, const QModelIndex &previous) {
Q_UNUSED(previous);
// Sync the selection in the MainWindow to the TalkingUI
if (Global::get().mw && Global::get().mw->pmModel) {
bool clearSelection = true;
const ClientUser *user = Global::get().mw->pmModel->getUser(current);
const Channel *channel = Global::get().mw->pmModel->getChannel(current);
if (Global::get().mw->pmModel->isChannelListener(current)) {
TalkingUIChannelListener *listenerEntry = findListener(user->uiSession, channel->iId);
if (listenerEntry) {
setSelection(ListenerSelection(listenerEntry->getWidget(), user->uiSession, channel->iId));
clearSelection = false;
}
} else {
if (user) {
TalkingUIUser *userEntry = findUser(user->uiSession);
if (userEntry) {
// Only select the user if there is an actual entry for it in the TalkingUI
setSelection(UserSelection(userEntry->getWidget(), userEntry->getAssociatedUserSession()));
clearSelection = false;
}
} else if (!user && channel) {
// if user != nullptr, the selection is actually a user, but UserModel::getChannel still returns
// the channel of that user. However we only want to select the channel if the user has indeed
// selected the channel and not just one of the users in it.
int index = findContainer(static_cast< int >(channel->iId), ContainerType::CHANNEL);
if (index >= 0) {
// Only select the channel if there is present in the TalkingUI
std::unique_ptr< TalkingUIContainer > &targetContainer =
m_containers[static_cast< std::size_t >(index)];
setSelection(
ChannelSelection(targetContainer->getWidget(),
static_cast< unsigned int >(targetContainer->getAssociatedChannelID())));
clearSelection = false;
}
}
}
if (clearSelection) {
setSelection(EmptySelection());
}
}
}
void TalkingUI::on_serverSynchronized() {
ClientUser *self = ClientUser::get(Global::get().uiSession);
assert(self);
if (Global::get().s.bTalkingUI_LocalUserStaysVisible) {
// According to the settings the local user should always be visible and as we
// can't count on it to change its talking state right after it has connected to
// a server, we have to add it manually.
findOrAddUser(self);
}
// According to the settings the all users should always be visible and as we
// can't count on users to change their talking state right after our user has connected
// to a server, we have to add them manually.
if (Global::get().s.talkingUI_UsersAlwaysVisible) {
for (auto &user : ClientUser::c_qmUsers) {
findOrAddUser(user);
}
}
// The client may have received add listener messages for the user before the
// sync was complete. So we do this to ensure that they appear in the
// TalkingUI. Removing all listeners is probably not necessary but could
// prevent duplicates appearing in the case of a race.
removeAllListeners();
if (Global::get().s.bTalkingUI_ShowLocalListeners) {
if (self) {
const QSet< unsigned int > channels =
Global::get().channelListenerManager->getListenedChannelsForUser(self->uiSession);
for (unsigned int currentChannelID : channels) {
const Channel *channel = Channel::get(currentChannelID);
if (channel) {
addListener(self, channel);
}
}
}
}
}
void TalkingUI::on_serverDisconnected() {
setSelection(EmptySelection());
// If we disconnect from a server, we have to clear all our users, channels, and so on
// Since all entries are owned by their respective containers, we only have to delete
// all containers. These in turn are managed by smart-pointers and therefore it is
// enough to let those go out of scope.
m_containers.clear();
updateUI();
}
void TalkingUI::on_channelChanged(QObject *obj) {
// According to this function's doc, the passed object must be of type ClientUser
ClientUser *user = static_cast< ClientUser * >(obj);
if (!user) {
return;
}
TalkingUIUser *userEntry = findUser(user->uiSession);
if (userEntry) {
// The user is visible, so we call moveUserToChannel in order to update
// the channel this particular user is being displayed in.
// But first we have to make sure there actually exists and entry for
// the new channel.
addChannel(user->cChannel);
moveUserToChannel(user->uiSession, user->cChannel->iId);
}
}
void TalkingUI::on_settingsChanged() {
// The settings might have affected the way we have to display the channel names
// thus we'll update them just in case
for (auto &currentContainer : m_containers) {
// The font size might have changed as well -> update it
// By the hierarchy in the UI the font-size should propagate to all
// sub-elements (entries) as well.
setFontSize(currentContainer->getStylableWidget());
if (currentContainer->getType() != ContainerType::CHANNEL) {
continue;
}
TalkingUIChannel *channelContainer = static_cast< TalkingUIChannel * >(currentContainer.get());
const Channel *channel = Channel::get(static_cast< unsigned int >(currentContainer->getAssociatedChannelID()));
if (channel) {
// Update
channelContainer->setName(createChannelName(
channel, Global::get().s.bTalkingUI_AbbreviateChannelNames, Global::get().s.iTalkingUI_PrefixCharCount,
Global::get().s.iTalkingUI_PostfixCharCount, Global::get().s.iTalkingUI_MaxChannelNameLength,
Global::get().s.iTalkingUI_ChannelHierarchyDepth, Global::get().s.qsHierarchyChannelSeparator,
Global::get().s.qsTalkingUI_AbbreviationReplacement,
Global::get().s.bTalkingUI_AbbreviateCurrentChannel));
} else {
qCritical("TalkingUI: Can't find channel for stored ID");
}
}
int silentUserLifeTime = Global::get().s.iTalkingUI_SilentUserLifeTime;
bool usersAlwaysVisible = Global::get().s.talkingUI_UsersAlwaysVisible;
// If the font has changed, we have to update the icon size as well
for (auto &currentContainer : m_containers) {
for (auto &currentEntry : currentContainer->getEntries()) {
currentEntry->setIconSize(m_currentLineHeight);
if (currentEntry->getType() == EntryType::USER) {
TalkingUIUser *userEntry = static_cast< TalkingUIUser * >(currentEntry.get());
// The time that a silent user may stick around might have changed as well
// * 1000 as the setting is in seconds whereas the timer expects milliseconds
userEntry->setLifeTime(static_cast< unsigned int >(silentUserLifeTime) * 1000);
userEntry->restrictLifetime(!usersAlwaysVisible);
}
}
}
// According to the settings the all users should always be visible and as we
// can't count on users to change their talking state right after settings where changed
// we have to add them manually.
if (usersAlwaysVisible) {
for (auto &user : ClientUser::c_qmUsers) {
findOrAddUser(user);
}
}
const ClientUser *self = ClientUser::get(Global::get().uiSession);
// Whether or not the current user should always be displayed might also have changed,
// so we'll have to update that as well.
TalkingUIUser *localUserEntry = findUser(Global::get().uiSession);
if (localUserEntry) {
bool localUserAlwaysVisible = usersAlwaysVisible || Global::get().s.bTalkingUI_LocalUserStaysVisible;
localUserEntry->restrictLifetime(!localUserAlwaysVisible);
} else {
if (self && (Global::get().s.bTalkingUI_LocalUserStaysVisible || usersAlwaysVisible)) {
// Add the local user as it is requested to be displayed
findOrAddUser(self);
}
}
// Furthermore whether or not to display the local user's listeners might have changed -> clear all
// listeners from the TalkingUI and add them again if appropriate
removeAllListeners();
if (Global::get().s.bTalkingUI_ShowLocalListeners) {
if (self) {
const QSet< unsigned int > channels =
Global::get().channelListenerManager->getListenedChannelsForUser(self->uiSession);
for (unsigned int currentChannelID : channels) {
const Channel *channel = Channel::get(currentChannelID);
if (channel) {
addListener(self, channel);
}
}
}
}
}
void TalkingUI::on_clientDisconnected(unsigned int userSession) {
removeUser(userSession);
}
void TalkingUI::on_muteDeafStateChanged() {
ClientUser *user = qobject_cast< ClientUser * >(sender());
if (user) {
// Update icons for local user only
updateStatusIcons(user);
}
}
void TalkingUI::on_userLocalVolumeAdjustmentsChanged(float, float) {
ClientUser *user = qobject_cast< ClientUser * >(sender());
if (user) {
TalkingUIUser *userEntry = findUser(user->uiSession);
if (userEntry) {
userEntry->setDisplayString(UserModel::createDisplayString(*user, false, nullptr));
}
}
}
void TalkingUI::on_channelListenerAdded(const ClientUser *user, const Channel *channel) {
if (user->uiSession == Global::get().uiSession && Global::get().s.bTalkingUI_ShowLocalListeners) {
addListener(user, channel);
}
}
void TalkingUI::on_channelListenerRemoved(const ClientUser *user, const Channel *channel) {
removeListener(user->uiSession, channel->iId);
}
void TalkingUI::on_channelListenerLocalVolumeAdjustmentChanged(unsigned int channelID, float, float) {
TalkingUIChannelListener *listenerEntry = findListener(Global::get().uiSession, channelID);
const Channel *channel = Channel::get(channelID);
const ClientUser *self = ClientUser::get(Global::get().uiSession);
if (listenerEntry && channel && self) {
listenerEntry->setDisplayString(UserModel::createDisplayString(*self, true, channel));
}
}