mumble-voip_mumble/src/mumble/TalkingUIEntry.cpp

402 lines
12 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 "TalkingUIEntry.h"
#include "Channel.h"
#include "ClientUser.h"
#include "TalkingUI.h"
#include "TalkingUIContainer.h"
#include "UserModel.h"
#include "Global.h"
#include <QHBoxLayout>
#include <QLabel>
#include <QObject>
#include <QPainter>
#include <QPixmap>
#include <QVariant>
#include <QWidget>
TalkingUIEntry::TalkingUIEntry(unsigned int associatedUserSession) : m_associatedUserSession(associatedUserSession) {
}
unsigned int TalkingUIEntry::getAssociatedUserSession() const {
return m_associatedUserSession;
}
TalkingUIContainer *TalkingUIEntry::getContainer() {
return m_container;
}
const TalkingUIContainer *TalkingUIEntry::getContainer() const {
return m_container;
}
int TalkingUIEntry::compare(const TalkingUIEntry &other) const {
if (getPriority() != other.getPriority()) {
return static_cast< int >(getPriority()) - static_cast< int >(other.getPriority());
}
if (getType() != other.getType()) {
return other.getType() > getType() ? -1 : 1;
}
if (other.getAssociatedUserSession() == getAssociatedUserSession()) {
return 0;
}
return other.getAssociatedUserSession() > m_associatedUserSession ? -1 : 1;
}
void TalkingUIEntry::setPriority(EntryPriority priority) {
m_priority = priority;
}
EntryPriority TalkingUIEntry::getPriority() const {
return m_priority;
}
bool TalkingUIEntry::operator==(const TalkingUIEntry &other) const {
return compare(other) == 0;
}
bool TalkingUIEntry::operator!=(const TalkingUIEntry &other) const {
return compare(other) != 0;
}
bool TalkingUIEntry::operator<(const TalkingUIEntry &other) const {
return compare(other) < 0;
}
bool TalkingUIEntry::operator<=(const TalkingUIEntry &other) const {
return compare(other) <= 0;
}
bool TalkingUIEntry::operator>(const TalkingUIEntry &other) const {
return compare(other) > 0;
}
bool TalkingUIEntry::operator>=(const TalkingUIEntry &other) const {
return compare(other) >= 0;
}
static const int USER_CONTAINER_HORIZONTAL_MARGIN = 2;
static const int USER_CONTAINER_VERTICAL_MARGIN = 3;
TalkingUIUser::TalkingUIUser(const ClientUser &user)
: TalkingUIEntry(user.uiSession), m_backgroundWidget(new QWidget()),
m_backgroundWidgetStyleWrapper(m_backgroundWidget), m_name(user.qsName), m_timer() {
// Create background widget and its layout that we'll use to place all other
// components on
m_backgroundWidget->setProperty("selected", false);
QLayout *backgroundLayout = new QHBoxLayout();
backgroundLayout->setContentsMargins(USER_CONTAINER_HORIZONTAL_MARGIN, USER_CONTAINER_VERTICAL_MARGIN,
USER_CONTAINER_HORIZONTAL_MARGIN, USER_CONTAINER_VERTICAL_MARGIN);
m_backgroundWidget->setLayout(backgroundLayout);
m_backgroundWidget->setAutoFillBackground(true);
// Create the label we use to display the user's name
m_nameLabel = new QLabel(m_backgroundWidget);
const QString displayString = UserModel::createDisplayString(user, false, nullptr);
setDisplayString(displayString);
// Create the label we'll use to display the user's TalkingIcon
m_talkingIcon = new QLabel(m_backgroundWidget);
m_talkingIcon->setAlignment(Qt::AlignCenter);
m_talkingIcon->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
// Create the label we'll use to display any status icons for the user
m_statusIcons = new QLabel(m_backgroundWidget);
m_statusIcons->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
m_statusIcons->hide(); // hide by default
// Uncomment the line below if you want to debug the icons
// m_statusIcons->setStyleSheet(QLatin1String("border: 1px solid black;"));
// By default we assume that there are no status icons to display and thus we only add
// the label for the TalkingIcon and the name
backgroundLayout->addWidget(m_talkingIcon);
backgroundLayout->addWidget(m_nameLabel);
// init size for talking icon to FontSize
m_iconSize = QFontMetrics(m_backgroundWidget->font()).height();
updateTalkingIcon();
// If the timer runs out, we remove this entry from its container (if any)
// and thereby this will cause its deletion (smart-pointer goes out of scope)
QObject::connect(&m_timer, &QTimer::timeout, [this]() {
if (getContainer()) {
// We let the TalkingUI handle the removing in order for it to do the necessary
// housekeeping (e.Global::get(). making sure there is no active selection for this user).
getContainer()->m_talkingUI.removeUser(getAssociatedUserSession());
}
});
}
TalkingUIUser::~TalkingUIUser() {
// Note that we have to use deleteLater instead of directly deleting the objects since
// the latter can easily cause segmentation faults due to Qt's automatic memory
// management model (parent-child-hierarchy)
m_backgroundWidget->deleteLater();
m_talkingIcon->deleteLater();
m_nameLabel->deleteLater();
m_statusIcons->deleteLater();
}
const QIcon &TalkingUIUser::getTalkingIcon(Settings::TalkState talkState) const {
static const QIcon s_talkingIcon = QIcon(QLatin1String("skin:talking_on.svg"));
static const QIcon s_mutedTalkingIcon = QIcon(QLatin1String("skin:talking_muted.svg"));
static const QIcon s_passiveIcon = QIcon(QLatin1String("skin:talking_off.svg"));
static const QIcon s_shoutingIcon = QIcon(QLatin1String("skin:talking_alt.svg"));
static const QIcon s_whisperingIcon = QIcon(QLatin1String("skin:talking_whisper.svg"));
switch (talkState) {
case Settings::MutedTalking:
return s_mutedTalkingIcon;
case Settings::Talking:
return s_talkingIcon;
case Settings::Whispering:
return s_whisperingIcon;
case Settings::Shouting:
return s_shoutingIcon;
case Settings::Passive:
return s_passiveIcon;
}
// switch above should cover all possible TalkStates
// Return s_passiveIcon as a fallback
qCritical("TalkingUIUser: Encountered invalid TalkState");
return s_passiveIcon;
}
QWidget *TalkingUIUser::getWidget() {
return m_backgroundWidget;
}
const QWidget *TalkingUIUser::getWidget() const {
return m_backgroundWidget;
}
MultiStyleWidgetWrapper &TalkingUIUser::getStylableWidget() {
return m_backgroundWidgetStyleWrapper;
}
EntryType TalkingUIUser::getType() const {
return EntryType::USER;
}
QString TalkingUIUser::getName() const {
return m_name;
}
int TalkingUIUser::compare(const TalkingUIEntry &other) const {
if (other.getType() != getType()) {
return static_cast< int >(other.getType()) - static_cast< int >(getType());
}
const TalkingUIUser &otherUser = static_cast< const TalkingUIUser & >(other);
if (getPriority() != otherUser.getPriority()) {
return getPriority() < other.getPriority() ? 1 : -1;
}
int res = getName().localeAwareCompare(otherUser.getName());
if (res == 0) {
// Make sure that we only consider users the same, if they have the same ID and
// not simply because they happen to have the same name
res = static_cast< int >(otherUser.getAssociatedUserSession()) - static_cast< int >(getAssociatedUserSession());
}
return res;
}
void TalkingUIUser::updateTalkingIcon() {
m_talkingIcon->setPixmap(
getTalkingIcon(m_talkingState).pixmap(QSize(m_iconSize, m_iconSize), QIcon::Normal, QIcon::On));
}
void TalkingUIUser::setTalkingState(Settings::TalkState talkState) {
if (m_talkingState == talkState) {
return;
}
m_talkingState = talkState;
switch (talkState) {
case Settings::Passive:
if (m_restrictLifetime) {
// Start timer as soon as the user has become passive
m_timer.start();
}
break;
case Settings::Talking:
case Settings::Whispering:
case Settings::Shouting:
case Settings::MutedTalking:
// Stop timer as the user is no longer inactive
m_timer.stop();
}
updateTalkingIcon();
}
void TalkingUIUser::setIconSize(int size) {
m_iconSize = size;
updateTalkingIcon();
}
void TalkingUIUser::setDisplayString(const QString &displayString) {
if (Global::get().uiSession == getAssociatedUserSession()) {
// Display own name in bold
m_nameLabel->setText(QString::fromLatin1("<b>%1</b>").arg(displayString));
} else {
m_nameLabel->setText(displayString);
}
}
void TalkingUIUser::setLifeTime(unsigned int time) {
m_timer.setInterval(static_cast< int >(time));
}
void TalkingUIUser::restrictLifetime(bool restrict) {
if (restrict && !m_timer.isActive()) {
// Start timer
m_timer.start();
} else if (!restrict && m_timer.isActive()) {
// Stop timer
m_timer.stop();
}
m_restrictLifetime = restrict;
}
void TalkingUIUser::setStatus(UserStatus status) {
static const QIcon s_muteIcon = QIcon(QLatin1String("skin:muted_server.svg"));
static const QIcon s_deafIcon = QIcon(QLatin1String("skin:deafened_server.svg"));
static const QIcon s_localMuteIcon = QIcon(QLatin1String("skin:muted_local.svg"));
static const QIcon s_selfMuteIcon = QIcon(QLatin1String("skin:muted_self.svg"));
static const QIcon s_selfDeafIcon = QIcon(QLatin1String("skin:deafened_self.svg"));
std::vector< std::reference_wrapper< const QIcon > > icons;
if (status.muted) {
icons.push_back(s_muteIcon);
}
if (status.selfMuted) {
icons.push_back(s_selfMuteIcon);
}
if (status.localMuted) {
icons.push_back(s_localMuteIcon);
}
if (status.deafened) {
icons.push_back(s_deafIcon);
}
if (status.selfDeafened) {
icons.push_back(s_selfDeafIcon);
}
if (icons.size() == 0) {
// No status icons to be shown -> hide the widget
m_statusIcons->hide();
m_backgroundWidget->layout()->removeWidget(m_statusIcons);
} else {
// Create a Pixmap that'll hold all icons
const QSize size(m_iconSize * static_cast< int >(icons.size()), m_iconSize);
QPixmap pixmap(size);
pixmap.fill(Qt::transparent);
// Draw the icons to the Pixmap
QPainter painter(&pixmap);
for (unsigned int i = 0; i < icons.size(); i++) {
painter.drawPixmap(static_cast< int >(i) * m_iconSize, 0,
icons[i].get().pixmap(QSize(m_iconSize, m_iconSize), QIcon::Normal, QIcon::On));
}
m_statusIcons->setPixmap(pixmap);
if (m_backgroundWidget->layout()->indexOf(m_statusIcons) < 0) {
// Add the status icons into the scene
m_statusIcons->show();
m_backgroundWidget->layout()->addWidget(m_statusIcons);
}
}
}
TalkingUIChannelListener::TalkingUIChannelListener(const ClientUser &user, const Channel &channel)
: TalkingUIEntry(user.uiSession), m_backgroundWidget(new QWidget()),
m_backgroundWidgetStyleWrapper(m_backgroundWidget), m_channelID(channel.iId), m_name(user.qsName) {
// Create background widget and its layout that we'll use to place all other
// components on
m_backgroundWidget->setProperty("selected", false);
QLayout *backgroundLayout = new QHBoxLayout();
backgroundLayout->setContentsMargins(USER_CONTAINER_HORIZONTAL_MARGIN, USER_CONTAINER_VERTICAL_MARGIN,
USER_CONTAINER_HORIZONTAL_MARGIN, USER_CONTAINER_VERTICAL_MARGIN);
m_backgroundWidget->setLayout(backgroundLayout);
m_backgroundWidget->setAutoFillBackground(true);
// Create the label we use to display the icon
m_icon = new QLabel(m_backgroundWidget);
m_icon->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
int iconSize = QFontMetrics(m_backgroundWidget->font()).height();
// setIconSize will not only set the size but also the icon itself
setIconSize(iconSize);
// Create the label we use to display the user's name
m_nameLabel = new QLabel(m_backgroundWidget);
const QString displayString = UserModel::createDisplayString(user, true, &channel);
setDisplayString(displayString);
backgroundLayout->addWidget(m_icon);
backgroundLayout->addWidget(m_nameLabel);
}
TalkingUIChannelListener::~TalkingUIChannelListener() {
m_backgroundWidget->deleteLater();
m_icon->deleteLater();
m_nameLabel->deleteLater();
}
EntryType TalkingUIChannelListener::getType() const {
return EntryType::LISTENER;
}
QWidget *TalkingUIChannelListener::getWidget() {
return m_backgroundWidget;
}
const QWidget *TalkingUIChannelListener::getWidget() const {
return m_backgroundWidget;
}
MultiStyleWidgetWrapper &TalkingUIChannelListener::getStylableWidget() {
return m_backgroundWidgetStyleWrapper;
}
void TalkingUIChannelListener::setIconSize(int size) {
static const QIcon s_earIcon = QIcon(QLatin1String("skin:ear.svg"));
m_icon->setPixmap(s_earIcon.pixmap(QSize(size, size), QIcon::Normal, QIcon::On));
}
void TalkingUIChannelListener::setDisplayString(const QString &displayString) {
m_nameLabel->setText(QString::fromLatin1("<i>%1</i>").arg(displayString));
}
int TalkingUIChannelListener::getAssociatedChannelID() const {
return static_cast< int >(m_channelID);
}