Merge pull request #4439: FEAT(client, mainwindow): Show volume adjustments

This feature optionally allows users to display the set volume
adjustments next to the user's/listener's name.
It will only be shown if it is not equal to 0 dB (aka: it is actually
having an effect).

By default this option is enabled since it is only visible if volume
adjustments are set up anyways.
This commit is contained in:
Robert Adam 2020-08-29 19:35:40 +02:00 committed by GitHub
commit ade9e9e46b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 146 additions and 53 deletions

View File

@ -518,7 +518,7 @@ bool AudioOutput::mix(void *outbuff, unsigned int frameCount) {
AudioOutputSpeech *speech = qobject_cast<AudioOutputSpeech *>(aop);
if (speech) {
const ClientUser *user = speech->p;
volumeAdjustment *= user->fLocalVolume;
volumeAdjustment *= user->getLocalVolumeAdjustments();
if (user->cChannel && ChannelListener::isListening(g.uiSession, user->cChannel->iId) && (speech->ucFlags & SpeechFlags::Listen)) {
// We are receiving this audio packet only because we are listening to the channel

View File

@ -24,11 +24,14 @@ ClientUser::ClientUser(QObject *p) : QObject(p),
fPowerMin(0.0f),
fPowerMax(0.0f),
fAverageAvailable(0.0f),
fLocalVolume(1.0f),
iFrames(0),
iSequence(0) {
}
float ClientUser::getLocalVolumeAdjustments() const {
return m_localVolume;
}
ClientUser *ClientUser::get(unsigned int uiSession) {
QReadLocker lock(&c_qrwlUsers);
ClientUser *p = c_qmUsers.value(uiSession);
@ -239,6 +242,13 @@ void ClientUser::setRecording(bool recording) {
emit recordingStateChanged();
}
void ClientUser::setLocalVolumeAdjustment(float adjustment) {
float oldAdjustment = m_localVolume;
m_localVolume = adjustment;
emit localVolumeAdjustmentsChanged(m_localVolume, oldAdjustment);
}
bool ClientUser::lessThanOverlay(const ClientUser *first, const ClientUser *second) {
if (g.s.os.osSort == OverlaySettings::LastStateChange) {
// Talkers above non-talkers

View File

@ -17,6 +17,10 @@ class ClientUser : public QObject, public User {
private:
Q_OBJECT
Q_DISABLE_COPY(ClientUser)
protected:
float m_localVolume = 1.0f;
public:
Settings::TalkState tsState;
Timer tLastTalkStateChange;
@ -26,7 +30,6 @@ class ClientUser : public QObject, public User {
float fPowerMin, fPowerMax;
float fAverageAvailable;
float fLocalVolume;
int iFrames;
int iSequence;
@ -37,6 +40,8 @@ class ClientUser : public QObject, public User {
QString getFlagsString() const;
ClientUser(QObject *p = nullptr);
float getLocalVolumeAdjustments() const;
/**
* Determines whether a user is active or not
* A user is active when it is currently speaking or when the user has
@ -74,11 +79,13 @@ class ClientUser : public QObject, public User {
void setSelfDeaf(bool deaf);
void setPrioritySpeaker(bool priority);
void setRecording(bool recording);
void setLocalVolumeAdjustment(float adjustment);
signals:
void talkingStateChanged();
void muteDeafStateChanged();
void prioritySpeakerStateChanged();
void recordingStateChanged();
void localVolumeAdjustmentsChanged(float newAdjustment, float oldAdjustment);
};
#endif

View File

@ -189,6 +189,7 @@ void LookConfig::load(const Settings &r) {
loadCheckBox(qcbHideTray, r.bHideInTray);
loadCheckBox(qcbStateInTray, r.bStateInTray);
loadCheckBox(qcbShowUserCount, r.bShowUserCount);
loadCheckBox(qcbShowVolumeAdjustments, r.bShowVolumeAdjustments);
loadCheckBox(qcbShowContextMenuInMenuBar, r.bShowContextMenuInMenuBar);
loadCheckBox(qcbShowTransmitModeComboBox, r.bShowTransmitModeComboBox);
loadCheckBox(qcbHighContrast, r.bHighContrast);
@ -249,6 +250,7 @@ void LookConfig::save() const {
s.bHideInTray = qcbHideTray->isChecked();
s.bStateInTray = qcbStateInTray->isChecked();
s.bShowUserCount = qcbShowUserCount->isChecked();
s.bShowVolumeAdjustments = qcbShowVolumeAdjustments->isChecked();
s.bShowContextMenuInMenuBar = qcbShowContextMenuInMenuBar->isChecked();
s.bShowTransmitModeComboBox = qcbShowTransmitModeComboBox->isChecked();
s.bHighContrast = qcbHighContrast->isChecked();

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>728</width>
<height>1120</height>
<height>1148</height>
</rect>
</property>
<property name="windowTitle">
@ -671,6 +671,33 @@
<string>Channel Tree</string>
</property>
<layout class="QGridLayout" name="gridLayout_2" columnstretch="0,0">
<item row="7" column="0" colspan="2">
<widget class="QCheckBox" name="qcbFilterHidesEmptyChannels">
<property name="text">
<string>Filter automatically hides empty channels</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="MUComboBox" name="qcbExpand">
<property name="toolTip">
<string>When to automatically expand channels</string>
</property>
<property name="whatsThis">
<string>This sets which channels to automatically expand. &lt;i&gt;None&lt;/i&gt; and &lt;i&gt;All&lt;/i&gt; will expand no or all channels, while &lt;i&gt;Only with users&lt;/i&gt; will expand and collapse channels as users join and leave them.</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="MUComboBox" name="qcbUserDrag">
<property name="toolTip">
<string>This changes the behavior when moving users.</string>
</property>
<property name="whatsThis">
<string>This sets the behavior of user drags; it can be used to prevent accidental dragging. &lt;i&gt;Move&lt;/i&gt; moves the user without prompting. &lt;i&gt;Do Nothing&lt;/i&gt; does nothing and prints an error message. &lt;i&gt;Ask&lt;/i&gt; uses a message box to confirm if you really wanted to move the user.</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="qliUserDrag">
<property name="text">
@ -678,13 +705,6 @@
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="qliExpand">
<property name="text">
<string>Expand</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="qliChannelDrag">
<property name="text">
@ -692,6 +712,20 @@
</property>
</widget>
</item>
<item row="6" column="0" colspan="2">
<widget class="QCheckBox" name="qcbChatBarUseSelection">
<property name="text">
<string>Use selected item as the chat bar target</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="qliExpand">
<property name="text">
<string>Expand</string>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<widget class="QCheckBox" name="qcbShowUserCount">
<property name="toolTip">
@ -726,36 +760,12 @@
</widget>
</item>
<item row="5" column="0" colspan="2">
<widget class="QCheckBox" name="qcbChatBarUseSelection">
<property name="text">
<string>Use selected item as the chat bar target</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="MUComboBox" name="qcbExpand">
<widget class="QCheckBox" name="qcbShowVolumeAdjustments">
<property name="toolTip">
<string>When to automatically expand channels</string>
<string>Show the local volume adjustment for each user (if any).</string>
</property>
<property name="whatsThis">
<string>This sets which channels to automatically expand. &lt;i&gt;None&lt;/i&gt; and &lt;i&gt;All&lt;/i&gt; will expand no or all channels, while &lt;i&gt;Only with users&lt;/i&gt; will expand and collapse channels as users join and leave them.</string>
</property>
</widget>
</item>
<item row="6" column="0" colspan="2">
<widget class="QCheckBox" name="qcbFilterHidesEmptyChannels">
<property name="text">
<string>Filter automatically hides empty channels</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="MUComboBox" name="qcbUserDrag">
<property name="toolTip">
<string>This changes the behavior when moving users.</string>
</property>
<property name="whatsThis">
<string>This sets the behavior of user drags; it can be used to prevent accidental dragging. &lt;i&gt;Move&lt;/i&gt; moves the user without prompting. &lt;i&gt;Do Nothing&lt;/i&gt; does nothing and prints an error message. &lt;i&gt;Ask&lt;/i&gt; uses a message box to confirm if you really wanted to move the user.</string>
<string>Show volume adjustments</string>
</property>
</widget>
</item>

View File

@ -522,7 +522,7 @@ void MainWindow::msgUserState(const MumbleProto::UserState &msg) {
pDst->setLocalIgnore(true);
if (g.db->isLocalIgnoredTTS(pDst->qsHash))
pDst->setLocalIgnoreTTS(true);
pDst->fLocalVolume = g.db->getUserLocalVolume(pDst->qsHash);
pDst->setLocalVolumeAdjustment(g.db->getUserLocalVolume(pDst->qsHash));
}
if (msg.has_self_deaf() || msg.has_self_mute()) {

View File

@ -362,6 +362,7 @@ Settings::Settings() {
bStateInTray = true;
bUsage = true;
bShowUserCount = false;
bShowVolumeAdjustments = true;
bChatBarUseSelection = false;
bFilterHidesEmptyChannels = true;
bFilterActive = false;
@ -1184,6 +1185,8 @@ void Settings::save() {
SAVELOAD(bStateInTray, "ui/stateintray");
SAVELOAD(bUsage, "ui/usage");
SAVELOAD(bShowUserCount, "ui/showusercount");
SAVELOAD(bShowVolumeAdjustments, "ui/showVolumeAdjustments");
SAVELOAD(bShowVolumeAdjustments, "ui/showVolumeAdjustments");
SAVELOAD(bChatBarUseSelection, "ui/chatbaruseselection");
SAVELOAD(bFilterHidesEmptyChannels, "ui/filterhidesemptychannels");
SAVELOAD(bFilterActive, "ui/filteractive");

View File

@ -324,6 +324,7 @@ struct Settings {
bool bStateInTray;
bool bUsage;
bool bShowUserCount;
bool bShowVolumeAdjustments;
bool bChatBarUseSelection;
bool bFilterHidesEmptyChannels;
bool bFilterActive;

View File

@ -306,10 +306,7 @@ void TalkingUI::addUser(const ClientUser *user) {
// 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.
// Strip HTML from the displayed name
const QString name = QTextDocumentFragment::fromHtml(m_entries[user->uiSession].name->text()).toPlainText();
nameMatches = name == user->qsName;
nameMatches = m_entries[user->uiSession].qsName == user->qsName;
if (!nameMatches) {
// Hide the stale user
@ -342,11 +339,12 @@ void TalkingUI::addUser(const ClientUser *user) {
background->setProperty("userName", user->qsName);
QLabel *name = new QLabel(background);
const QString displayString = UserModel::createDisplayString(*user, false, nullptr);
if (isSelf) {
// Indicate self by bold name
name->setText(QString::fromLatin1("<b>%1</b>").arg(user->qsName));
name->setText(QString::fromLatin1("<b>%1</b>").arg(displayString));
} else {
name->setText(user->qsName);
name->setText(displayString);
}
QLabel *icon = new QLabel(background);
@ -354,7 +352,7 @@ void TalkingUI::addUser(const ClientUser *user) {
icon->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
// As a default use the passive state
Entry entry = {icon, name, background, nullptr, user->uiSession, Settings::Passive};
Entry entry = {icon, name, background, nullptr, user->uiSession, Settings::Passive, user->qsName};
setTalkingIcon(entry);
m_entries.insert(user->uiSession, entry);
@ -374,6 +372,8 @@ void TalkingUI::addUser(const ClientUser *user) {
m_timers.insert(user->uiSession, timer);
QObject::connect(user, &ClientUser::localVolumeAdjustmentsChanged, this, &TalkingUI::on_userLocalVolumeAdjustmentsChanged);
// If this user is currently selected, mark him/her as such
if (g.mw && g.mw->pmModel && g.mw->pmModel->getSelectedUser() == user) {
if (m_entries.contains(user->uiSession)) {
@ -862,3 +862,12 @@ void TalkingUI::on_muteDeafStateChanged() {
}
}
void TalkingUI::on_userLocalVolumeAdjustmentsChanged(float, float) {
ClientUser *user = qobject_cast<ClientUser *>(sender());
if (user && m_entries.contains(user->uiSession)) {
Entry entry = m_entries[user->uiSession];
entry.name->setText(UserModel::createDisplayString(*user, false, nullptr));
}
}

View File

@ -35,6 +35,7 @@ struct Entry {
QLabel *statusIcons;
unsigned int userSession;
Settings::TalkState talkingState;
QString qsName;
};
/// The talking UI is a widget that will display the users you are currently
@ -142,6 +143,7 @@ class TalkingUI : public QWidget {
void on_settingsChanged();
void on_clientDisconnected(unsigned int userSession);
void on_muteDeafStateChanged();
void on_userLocalVolumeAdjustmentsChanged(float newAdjustment, float oldAdjustment);
};
#endif // MUMBLE_MUMBLE_TALKINGUI_H_

View File

@ -60,7 +60,7 @@ UserLocalVolumeDialog::UserLocalVolumeDialog(unsigned int sessionId,
if (user) {
QString title = tr("Adjusting local volume for %1").arg(user->qsName);
setWindowTitle(title);
qsUserLocalVolume->setValue(qRound(log2(user->fLocalVolume) * 6.0));
qsUserLocalVolume->setValue(qRound(log2(user->getLocalVolumeAdjustments()) * 6.0));
m_originalVolumeAdjustmentDecibel = qsUserLocalVolume->value();
}
@ -92,7 +92,7 @@ void UserLocalVolumeDialog::on_qsUserLocalVolume_valueChanged(int value) {
ClientUser *user = ClientUser::get(m_clientSession);
if (user) {
// Decibel formula: +6db = *2
user->fLocalVolume = static_cast<float>(pow(2.0, qsUserLocalVolume->value() / 6.0));
user->setLocalVolumeAdjustment(static_cast<float>(pow(2.0, qsUserLocalVolume->value() / 6.0)));
}
}
@ -108,7 +108,7 @@ void UserLocalVolumeDialog::on_qbbUserLocalVolume_clicked(QAbstractButton *butto
ClientUser *user = ClientUser::get(m_clientSession);
if (user) {
if (!user->qsHash.isEmpty()) {
g.db->setUserLocalVolume(user->qsHash, user->fLocalVolume);
g.db->setUserLocalVolume(user->qsHash, user->getLocalVolumeAdjustments());
} else {
g.mw->logChangeNotPermanent(QObject::tr("Local Volume Adjustment..."), user);
}

View File

@ -454,11 +454,12 @@ QVariant UserModel::data(const QModelIndex &idx, int role) const {
break;
case Qt::DisplayRole:
if (idx.column() == 0) {
if (! p->qsFriendName.isEmpty() && (p->qsFriendName.toLower() != p->qsName.toLower()))
return QString::fromLatin1("%1 (%2)").arg(p->qsName).arg(p->qsFriendName);
else
return p->qsName;
// Get the channel the user/listener is in
const Channel *parentChannel = item->parent ? item->parent->cChan : nullptr;
return createDisplayString(*p, item->isListener, parentChannel);
}
// Most of the following icons are for non-listeners (as listeners are merely proxies) only
// but in order to not change the order of the icons, the condition is added to each case
// individually instead of checking it up front.
@ -1847,3 +1848,40 @@ void UserModel::updateOverlay() const {
#endif
g.lcd->updateUserView();
}
QString UserModel::createDisplayString(const ClientUser &user, bool isChannelListener, const Channel *parentChannel) {
// Get the configured volume adjustment. Depending on whether
// this display string is for a ChannelListener or a regular user, we have to fetch
// the volume adjustment differently.
float volumeAdjustment = isChannelListener && parentChannel
? ChannelListener::getListenerLocalVolumeAdjustment(parentChannel->iId) : user.getLocalVolumeAdjustments();
// Transform the adjustment into dB
// *2 == 6 dB
int localVolumeDecibel = std::round(log2f(volumeAdjustment) * 6);
// Create a friend-tag
QString friendTag;
if (!user.qsFriendName.isEmpty() && user.qsName.compare(user.qsFriendName, Qt::CaseInsensitive) != 0) {
friendTag = QString::fromLatin1("(%2)").arg(user.qsFriendName);
}
// Create a tag that indicates the volume adjustments
QString volumeTag;
if (std::abs(localVolumeDecibel) > 0 && g.s.bShowVolumeAdjustments) {
volumeTag = QString::asprintf("|%+d|", localVolumeDecibel);
}
QString displayString = user.qsName;
if (!friendTag.isEmpty()) {
displayString += " " + friendTag;
}
if (!volumeTag.isEmpty()) {
displayString += " " + volumeTag;
}
return displayString;
}

View File

@ -186,6 +186,9 @@ class UserModel : public QAbstractItemModel {
unsigned int uiSessionComment;
int iChannelDescription;
static QString createDisplayString(const ClientUser &user, bool isChannelListener, const Channel *parentChannel);
public slots:
/// Invalidates the model data of the ClientUser triggering this slot.
void userStateChanged();

View File

@ -4267,6 +4267,14 @@ The setting only applies for new messages, the already shown ones will retain th
<source>Silent user lifetime</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Show the local volume adjustment for each user (if any).</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Show volume adjustments</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MainWindow</name>