Merge pull request #4624: FEAT(client): Add ability to set local nicknames

This feature allows users to set local nicknames for other users. It is saved in the client database for persistence. This feature could be helpful for faster identifying of users with similar names or as a means to hide potentially inappropriate usernames with a setting that only shows the nicknames.

Fixes #3967
This commit is contained in:
Robert Adam 2020-12-20 17:53:37 +01:00 committed by GitHub
commit 72228f6e03
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 343 additions and 3 deletions

View File

@ -209,6 +209,9 @@ set(MUMBLE_SOURCES
"UserInformation.ui"
"UserListModel.cpp"
"UserListModel.h"
"UserLocalNicknameDialog.cpp"
"UserLocalNicknameDialog.h"
"UserLocalNicknameDialog.ui"
"UserLocalVolumeDialog.cpp"
"UserLocalVolumeDialog.h"
"UserLocalVolumeDialog.ui"

View File

@ -24,6 +24,10 @@ float ClientUser::getLocalVolumeAdjustments() const {
return m_localVolume;
}
QString ClientUser::getLocalNickname() const {
return m_localNickname;
}
ClientUser *ClientUser::get(unsigned int uiSession) {
QReadLocker lock(&c_qrwlUsers);
ClientUser *p = c_qmUsers.value(uiSession);
@ -240,6 +244,14 @@ void ClientUser::setLocalVolumeAdjustment(float adjustment) {
emit localVolumeAdjustmentsChanged(m_localVolume, oldAdjustment);
}
void ClientUser::setLocalNickname(const QString &nickname) {
if (m_localNickname != nickname) {
m_localNickname = nickname;
emit localNicknameChanged();
}
}
bool ClientUser::lessThanOverlay(const ClientUser *first, const ClientUser *second) {
if (g.s.os.osSort == OverlaySettings::LastStateChange) {
// Talkers above non-talkers

View File

@ -20,6 +20,7 @@ private:
protected:
float m_localVolume = 1.0f;
QString m_localNickname;
public:
Settings::TalkState tsState;
@ -42,6 +43,8 @@ public:
float getLocalVolumeAdjustments() const;
QString getLocalNickname() const;
/**
* Determines whether a user is active or not
* A user is active when it is currently speaking or when the user has
@ -81,12 +84,14 @@ public slots:
void setPrioritySpeaker(bool priority);
void setRecording(bool recording);
void setLocalVolumeAdjustment(float adjustment);
void setLocalNickname(const QString &nickname);
signals:
void talkingStateChanged();
void muteDeafStateChanged();
void prioritySpeakerStateChanged();
void recordingStateChanged();
void localVolumeAdjustmentsChanged(float newAdjustment, float oldAdjustment);
void localNicknameChanged();
};
#endif

View File

@ -207,6 +207,9 @@ Database::Database(const QString &dbname) {
execQueryAndLogFailure(query, QLatin1String("CREATE TABLE IF NOT EXISTS `volume` (`id` INTEGER PRIMARY KEY "
"AUTOINCREMENT, `hash` TEXT, `volume` FLOAT)"));
execQueryAndLogFailure(query, QLatin1String("CREATE UNIQUE INDEX IF NOT EXISTS `volume_hash` ON `volume`(`hash`)"));
execQueryAndLogFailure(query, QLatin1String("CREATE TABLE IF NOT EXISTS `nicknames` (`id` INTEGER PRIMARY KEY "
"AUTOINCREMENT, `hash` TEXT, `nickname` TEXT)"));
execQueryAndLogFailure(query, QLatin1String("CREATE UNIQUE INDEX IF NOT EXISTS `nicknames_hash` ON `nicknames`(`hash`)"));
// Note: A previous snapshot version created a table called 'hidden'
execQueryAndLogFailure(
@ -365,6 +368,27 @@ float Database::getUserLocalVolume(const QString &hash) {
return 1.0f;
}
void Database::setUserLocalNickname(const QString &hash, const QString &nickname) {
QSqlQuery query(db);
query.prepare(QLatin1String("INSERT OR REPLACE INTO `nicknames` (`hash`, `nickname`) VALUES (?,?)"));
query.addBindValue(hash);
query.addBindValue(nickname);
execQueryAndLogFailure(query);
}
QString Database::getUserLocalNickname(const QString &hash) {
QSqlQuery query(db);
query.prepare(QLatin1String("SELECT `nickname` FROM `nicknames` WHERE `hash` = ?"));
query.addBindValue(hash);
execQueryAndLogFailure(query);
if (query.first()) {
return query.value(0).toString();
}
return QString();
}
void Database::setLocalMuted(const QString &hash, bool muted) {
QSqlQuery query(db);

View File

@ -51,6 +51,9 @@ public:
float getUserLocalVolume(const QString &hash);
void setUserLocalVolume(const QString &hash, float volume);
QString getUserLocalNickname(const QString &hash);
void setUserLocalNickname(const QString &hash, const QString &nickname);
bool isChannelFiltered(const QByteArray &server_cert_digest, const int channel_id);
void setChannelFiltered(const QByteArray &server_cert_digest, const int channel_id, bool hidden);

View File

@ -182,6 +182,7 @@ void LookConfig::load(const Settings &r) {
loadCheckBox(qcbStateInTray, r.bStateInTray);
loadCheckBox(qcbShowUserCount, r.bShowUserCount);
loadCheckBox(qcbShowVolumeAdjustments, r.bShowVolumeAdjustments);
loadCheckBox(qcbShowNicknamesOnly, r.bShowNicknamesOnly);
loadCheckBox(qcbShowContextMenuInMenuBar, r.bShowContextMenuInMenuBar);
loadCheckBox(qcbShowTransmitModeComboBox, r.bShowTransmitModeComboBox);
loadCheckBox(qcbHighContrast, r.bHighContrast);
@ -244,6 +245,7 @@ void LookConfig::save() const {
s.bStateInTray = qcbStateInTray->isChecked();
s.bShowUserCount = qcbShowUserCount->isChecked();
s.bShowVolumeAdjustments = qcbShowVolumeAdjustments->isChecked();
s.bShowNicknamesOnly = qcbShowNicknamesOnly->isChecked();
s.bShowContextMenuInMenuBar = qcbShowContextMenuInMenuBar->isChecked();
s.bShowTransmitModeComboBox = qcbShowTransmitModeComboBox->isChecked();
s.bHighContrast = qcbHighContrast->isChecked();

View File

@ -681,7 +681,7 @@
<string>Channel Tree</string>
</property>
<layout class="QGridLayout" name="gridLayout_2" columnstretch="0,0">
<item row="7" column="0" colspan="2">
<item row="8" column="0" colspan="2">
<widget class="QCheckBox" name="qcbFilterHidesEmptyChannels">
<property name="text">
<string>Filter automatically hides empty channels</string>
@ -722,7 +722,7 @@
</property>
</widget>
</item>
<item row="6" column="0" colspan="2">
<item row="7" column="0" colspan="2">
<widget class="QCheckBox" name="qcbChatBarUseSelection">
<property name="text">
<string>Use selected item as the chat bar target</string>
@ -779,6 +779,16 @@
</property>
</widget>
</item>
<item row="6" column="0" colspan="2">
<widget class="QCheckBox" name="qcbShowNicknamesOnly">
<property name="toolTip">
<string>Hide the username for each user if they have a nickname.</string>
</property>
<property name="text">
<string>Show nicknames only</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>

View File

@ -48,6 +48,7 @@
#include "User.h"
#include "UserEdit.h"
#include "UserInformation.h"
#include "UserLocalNicknameDialog.h"
#include "UserLocalVolumeDialog.h"
#include "UserModel.h"
#include "Utils.h"
@ -1600,6 +1601,7 @@ void MainWindow::qmUser_aboutToShow() {
if (g.s.bTTS)
qmUser->addAction(qaUserLocalIgnoreTTS);
qmUser->addAction(qaUserLocalVolume);
qmUser->addAction(qaUserLocalNickname);
if (isSelf)
qmUser->addAction(qaSelfComment);
@ -1656,6 +1658,7 @@ void MainWindow::qmUser_aboutToShow() {
qaUserKick->setEnabled(false);
qaUserBan->setEnabled(false);
qaUserTextMessage->setEnabled(false);
qaUserLocalNickname->setEnabled(false);
qaUserLocalMute->setEnabled(false);
qaUserLocalVolume->setEnabled(false);
qaUserLocalIgnore->setEnabled(false);
@ -1667,6 +1670,7 @@ void MainWindow::qmUser_aboutToShow() {
qaUserKick->setEnabled(!isSelf);
qaUserBan->setEnabled(!isSelf);
qaUserTextMessage->setEnabled(true);
qaUserLocalNickname->setEnabled(!isSelf);
qaUserLocalMute->setEnabled(!isSelf);
qaUserLocalVolume->setEnabled(!isSelf);
qaUserLocalIgnore->setEnabled(!isSelf);
@ -1969,6 +1973,20 @@ void MainWindow::openTextMessageDialog(ClientUser *p) {
delete texm;
}
void MainWindow::on_qaUserLocalNickname_triggered() {
ClientUser *p = getContextMenuUser();
if (!p)
return;
openUserLocalNicknameDialog(*p);
}
void MainWindow::openUserLocalNicknameDialog(const ClientUser &p) {
unsigned int session = p.uiSession;
UserLocalNicknameDialog::present(session, qmUserNicknameTracker);
}
void MainWindow::on_qaUserCommentView_triggered() {
ClientUser *p = getContextMenuUser();
// This has to be done here because UserModel could've set it.

View File

@ -18,6 +18,7 @@
#include "Mumble.pb.h"
#include "Usage.h"
#include "UserLocalVolumeDialog.h"
#include "UserLocalNicknameDialog.h"
#include "ui_MainWindow.h"
@ -69,6 +70,7 @@ public:
qiIconMuteSuppressed;
QIcon qiTalkingOn, qiTalkingWhisper, qiTalkingShout, qiTalkingOff;
QMap< unsigned int, UserLocalVolumeDialog * > qmUserVolTracker;
std::unordered_map< unsigned int, std::unique_ptr< UserLocalNicknameDialog > > qmUserNicknameTracker;
/// "Action" for when there are no actions available
QAction *qaEmpty;
@ -119,6 +121,7 @@ public:
void updateChatBar();
void openTextMessageDialog(ClientUser *p);
void openUserLocalNicknameDialog(const ClientUser &p);
void openUserLocalVolumeDialog(ClientUser *p);
#ifdef Q_OS_WIN
@ -210,6 +213,7 @@ public slots:
void on_qaUserLocalIgnore_triggered();
void on_qaUserLocalIgnoreTTS_triggered();
void on_qaUserLocalMute_triggered();
void on_qaUserLocalNickname_triggered();
void on_qaUserLocalVolume_triggered();
void on_qaUserTextMessage_triggered();
void on_qaUserRegister_triggered();

View File

@ -378,6 +378,17 @@
<string>Sends a text message to another user.</string>
</property>
</action>
<action name="qaUserLocalNickname">
<property name="text">
<string>&amp;Set Nickname...</string>
</property>
<property name="toolTip">
<string>Set a local nickname</string>
</property>
<property name="whatsThis">
<string>Sets a local nickname for another user.</string>
</property>
</action>
<action name="qaChannelAdd">
<property name="text">
<string>&amp;Add...</string>

View File

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

View File

@ -379,6 +379,7 @@ Settings::Settings() {
bUsage = true;
bShowUserCount = false;
bShowVolumeAdjustments = true;
bShowNicknamesOnly = false;
bChatBarUseSelection = false;
bFilterHidesEmptyChannels = true;
bFilterActive = false;
@ -867,6 +868,7 @@ void Settings::load(QSettings *settings_ptr) {
SAVELOAD(bUsage, "ui/usage");
SAVELOAD(bShowUserCount, "ui/showusercount");
SAVELOAD(bShowVolumeAdjustments, "ui/showVolumeAdjustments");
SAVELOAD(bShowNicknamesOnly, "ui/showNicknamesOnly");
SAVELOAD(bChatBarUseSelection, "ui/chatbaruseselection");
SAVELOAD(bFilterHidesEmptyChannels, "ui/filterhidesemptychannels");
SAVELOAD(bFilterActive, "ui/filteractive");
@ -1237,6 +1239,7 @@ void Settings::save() {
SAVELOAD(bUsage, "ui/usage");
SAVELOAD(bShowUserCount, "ui/showusercount");
SAVELOAD(bShowVolumeAdjustments, "ui/showVolumeAdjustments");
SAVELOAD(bShowNicknamesOnly, "ui/showNicknamesOnly");
SAVELOAD(bChatBarUseSelection, "ui/chatbaruseselection");
SAVELOAD(bFilterHidesEmptyChannels, "ui/filterhidesemptychannels");
SAVELOAD(bFilterActive, "ui/filteractive");

View File

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

View File

@ -0,0 +1,93 @@
// Copyright 2020 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 "UserLocalNicknameDialog.h"
#include "ClientUser.h"
#include "Database.h"
#include "MainWindow.h"
#include <QtGui/QCloseEvent>
#include <QtWidgets/QPushButton>
// We define a global macro called 'g'. This can lead to issues when included code uses 'g' as a type or parameter name
// (like protobuf 3.7 does). As such, for now, we have to make this our last include.
#include "Global.h"
UserLocalNicknameDialog::UserLocalNicknameDialog(
unsigned int sessionId,
std::unordered_map< unsigned int, std::unique_ptr< UserLocalNicknameDialog > > &qmUserNicknameTracker)
: QDialog(nullptr), m_clientSession(sessionId), m_qmUserNicknameTracker(qmUserNicknameTracker) {
setupUi(this);
qleUserLocalNickname->setFocus();
qleUserLocalNickname->setAccessibleName(tr("User nickname"));
ClientUser *user = ClientUser::get(sessionId);
if (!user) {
UserLocalNicknameDialog::close();
} else {
QString title = tr("Adjusting local nickname for %1").arg(user->qsName);
setWindowTitle(title);
qleUserLocalNickname->setText(user->getLocalNickname());
m_originalNickname = qleUserLocalNickname->text();
}
if (g.mw && g.mw->windowFlags() & Qt::WindowStaysOnTopHint) {
// If the main window is set to always be on top of other windows, we should make the
// nickname dialog behave the same in order for it to not get hidden behind the main window.
setWindowFlags(Qt::WindowStaysOnTopHint);
}
}
void UserLocalNicknameDialog::closeEvent(QCloseEvent *event) {
m_qmUserNicknameTracker.erase(m_clientSession);
event->accept();
}
void UserLocalNicknameDialog::present(
unsigned int sessionId,
std::unordered_map< unsigned int, std::unique_ptr< UserLocalNicknameDialog > > &qmUserNicknameTracker) {
if (qmUserNicknameTracker.find(sessionId) != qmUserNicknameTracker.end()) {
qmUserNicknameTracker.at(sessionId)->show();
qmUserNicknameTracker.at(sessionId)->raise();
} else {
std::unique_ptr< UserLocalNicknameDialog > userNickname =
std::make_unique< UserLocalNicknameDialog >(sessionId, qmUserNicknameTracker);
userNickname->show();
qmUserNicknameTracker.insert(std::make_pair(sessionId, std::move(userNickname)));
}
}
void UserLocalNicknameDialog::on_qleUserLocalNickname_textChanged(const QString &text) {
ClientUser *user = ClientUser::get(m_clientSession);
if (user) {
user->setLocalNickname(text);
}
}
void UserLocalNicknameDialog::on_qbbUserLocalNickname_clicked(QAbstractButton *button) {
if (button == qbbUserLocalNickname->button(QDialogButtonBox::Reset)) {
qleUserLocalNickname->setText(m_originalNickname);
} else if (button == qbbUserLocalNickname->button(QDialogButtonBox::Ok)) {
ClientUser *user = ClientUser::get(m_clientSession);
if (user) {
if (!user->qsHash.isEmpty()) {
g.db->setUserLocalNickname(user->qsHash, user->getLocalNickname());
} else {
g.mw->logChangeNotPermanent(QObject::tr("Local Nickname Adjustment..."), user);
}
}
UserLocalNicknameDialog::close();
} else if (button == qbbUserLocalNickname->button(QDialogButtonBox::Cancel)) {
qleUserLocalNickname->setText(m_originalNickname);
UserLocalNicknameDialog::close();
}
}
void UserLocalNicknameDialog::reject() {
m_qmUserNicknameTracker.erase(m_clientSession);
UserLocalNicknameDialog::close();
}

View File

@ -0,0 +1,41 @@
// Copyright 2020 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>.
#ifndef MUMBLE_MUMBLE_USERNICKNAME_H_
#define MUMBLE_MUMBLE_USERNICKNAME_H_
#include <memory>
#include <unordered_map>
#include "ClientUser.h"
#include "ui_UserLocalNicknameDialog.h"
class UserLocalNicknameDialog : public QDialog, private Ui::UserLocalNicknameDialog {
Q_OBJECT
Q_DISABLE_COPY(UserLocalNicknameDialog);
/// The session ID for the user that the dialog is changing the nickname for.
unsigned int m_clientSession;
/// The user's original nickname when entering the dialog.
QString m_originalNickname;
std::unordered_map< unsigned int, std::unique_ptr< UserLocalNicknameDialog > > &m_qmUserNicknameTracker;
public slots:
void closeEvent(QCloseEvent *event);
void on_qleUserLocalNickname_textChanged(const QString &text);
void on_qbbUserLocalNickname_clicked(QAbstractButton *b);
void reject();
public:
static void
present(unsigned int sessionId,
std::unordered_map< unsigned int, std::unique_ptr< UserLocalNicknameDialog > > &qmUserNicknameTracker);
UserLocalNicknameDialog(
unsigned int sessionId,
std::unordered_map< unsigned int, std::unique_ptr< UserLocalNicknameDialog > > &qmUserNicknameTracker);
};
#endif

View File

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>UserLocalNicknameDialog</class>
<widget class="QDialog" name="UserLocalNicknameDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>500</width>
<height>80</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>500</width>
<height>80</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>224</height>
</size>
</property>
<layout class="QGridLayout">
<item row="2" column="0" colspan="2" alignment="Qt::AlignBottom">
<widget class="QDialogButtonBox" name="qbbUserLocalNickname">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Reset</set>
</property>
<property name="centerButtons">
<bool>false</bool>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLineEdit" name="qleUserLocalNickname">
<property name="toolTip">
<string>Local nickname for other users</string>
</property>
<property name="whatsThis">
<string>&lt;b&gt;Adjust the nickname of other users locally&lt;/b&gt;&lt;br /&gt;</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -1019,6 +1019,7 @@ ClientUser *UserModel::addUser(unsigned int id, const QString &name) {
connect(p, SIGNAL(prioritySpeakerStateChanged()), this, SLOT(userStateChanged()));
connect(p, SIGNAL(recordingStateChanged()), this, SLOT(userStateChanged()));
connect(p, &ClientUser::localVolumeAdjustmentsChanged, this, &UserModel::userStateChanged);
connect(p, &ClientUser::localNicknameChanged, this, &UserModel::userStateChanged);
Channel *c = Channel::get(0);
ModelItem *citem = ModelItem::c_qhChannels.value(c);
@ -1959,18 +1960,30 @@ QString UserModel::createDisplayString(const ClientUser &user, bool isChannelLis
friendTag = QString::fromLatin1("(%2)").arg(user.qsFriendName);
}
// Create a nickname-tag
QString nickname = user.getLocalNickname();
// 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;
QString displayString;
if (!g.s.bShowNicknamesOnly || nickname.isEmpty()) {
displayString += user.qsName;
} else {
displayString += nickname;
}
if (!friendTag.isEmpty()) {
displayString += " " + friendTag;
}
if (!g.s.bShowNicknamesOnly && !nickname.isEmpty() && user.qsName.compare(nickname, Qt::CaseInsensitive) != 0) {
displayString += " " + QString::fromLatin1("[%1]").arg(nickname);
}
if (!volumeTag.isEmpty()) {
displayString += " " + volumeTag;
}

View File

@ -4298,6 +4298,14 @@ The setting only applies for new messages, the already shown ones will retain th
<source>Show local user&apos;s listeners (ears)</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Hide the username for each user if they have a nickname.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Show nicknames only</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MainWindow</name>
@ -6138,6 +6146,18 @@ Valid options are:
<comment>Global Shortcut</comment>
<translation type="unfinished"></translation>
</message>
<message>
<source>&amp;Set Nickname...</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Set a local nickname</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Sets a local nickname for another user.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Manual</name>
@ -7129,6 +7149,10 @@ See &lt;a href=&quot;https://wiki.mumble.info/wiki/Installing_Mumble&quot;&gt;th
<source>Do you really want to reset all settings (not only the ones currently visible) to their default value?</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Local Nickname Adjustment...</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>RichTextEditor</name>
@ -7664,6 +7688,25 @@ An access token is a text string, which can be used as a password for very simpl
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>UserLocalNicknameDialog</name>
<message>
<source>Local nickname for other users</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>&lt;b&gt;Adjust the nickname of other users locally&lt;/b&gt;&lt;br /&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>User nickname</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Adjusting local nickname for %1</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>UserLocalVolumeDialog</name>
<message>