FEAT(tts): Allow disabling Text-To-Speech per user

These changes make it possible to turn of TTS for a specific user while
still receiving their messages.

This setting can be managed from the context menu of a user that is
connected to the server and is only visible when TTS is enabled
globally.

A new DB table called 'ignored_tts' had to be created to persist this
setting.
This commit is contained in:
Popkornium18 2020-06-13 01:27:39 +02:00 committed by Robert Adam
parent d73465e3a2
commit 1a4d134a35
10 changed files with 83 additions and 12 deletions

View File

@ -19,6 +19,7 @@ ClientUser::ClientUser(QObject *p) : QObject(p),
tsState(Settings::Passive),
tLastTalkStateChange(false),
bLocalIgnore(false),
bLocalIgnoreTTS(false),
bLocalMute(false),
fPowerMin(0.0f),
fPowerMax(0.0f),
@ -134,6 +135,8 @@ QString ClientUser::getFlagsString() const {
flags << ClientUser::tr("Deafened (server)");
if (bLocalIgnore)
flags << ClientUser::tr("Local Ignore (Text messages)");
if (bLocalIgnoreTTS)
flags << ClientUser::tr("Local Ignore (Text-To-Speech)");
if (bLocalMute)
flags << ClientUser::tr("Local Mute");
if (bSelfMute)
@ -190,6 +193,10 @@ void ClientUser::setLocalIgnore(bool ignore) {
emit muteDeafStateChanged();
}
void ClientUser::setLocalIgnoreTTS(bool ignoreTTS) {
bLocalIgnoreTTS = ignoreTTS;
}
void ClientUser::setLocalMute(bool mute) {
if (bLocalMute == mute)
return;

View File

@ -21,6 +21,7 @@ class ClientUser : public QObject, public User {
Settings::TalkState tsState;
Timer tLastTalkStateChange;
bool bLocalIgnore;
bool bLocalIgnoreTTS;
bool bLocalMute;
float fPowerMin, fPowerMax;
@ -67,6 +68,7 @@ class ClientUser : public QObject, public User {
void setDeaf(bool deaf);
void setSuppress(bool suppress);
void setLocalIgnore(bool ignore);
void setLocalIgnoreTTS(bool ignoreTTS);
void setLocalMute(bool mute);
void setSelfMute(bool mute);
void setSelfDeaf(bool deaf);

View File

@ -137,6 +137,9 @@ Database::Database(const QString &dbname) {
execQueryAndLogFailure(query, QLatin1String("CREATE TABLE IF NOT EXISTS `ignored` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `hash` TEXT)"));
execQueryAndLogFailure(query, QLatin1String("CREATE UNIQUE INDEX IF NOT EXISTS `ignored_hash` ON `ignored`(`hash`)"));
execQueryAndLogFailure(query, QLatin1String("CREATE TABLE IF NOT EXISTS `ignored_tts` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `hash` TEXT)"));
execQueryAndLogFailure(query, QLatin1String("CREATE UNIQUE INDEX IF NOT EXISTS `ignored_tts_hash` ON `ignored_tts`(`hash`)"));
execQueryAndLogFailure(query, QLatin1String("CREATE TABLE IF NOT EXISTS `muted` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `hash` TEXT)"));
execQueryAndLogFailure(query, QLatin1String("CREATE UNIQUE INDEX IF NOT EXISTS `muted_hash` ON `muted`(`hash`)"));
execQueryAndLogFailure(query, QLatin1String("CREATE TABLE IF NOT EXISTS `volume` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `hash` TEXT, `volume` FLOAT)"));
@ -238,6 +241,26 @@ void Database::setLocalIgnored(const QString &hash, bool ignored) {
execQueryAndLogFailure(query);
}
bool Database::isLocalIgnoredTTS(const QString &hash) {
QSqlQuery query(db);
query.prepare(QLatin1String("SELECT `hash` FROM `ignored_tts` WHERE `hash` = ?"));
query.addBindValue(hash);
execQueryAndLogFailure(query);
return query.next();
}
void Database::setLocalIgnoredTTS(const QString &hash, bool ignoredTTS) {
QSqlQuery query(db);
if (ignoredTTS)
query.prepare(QLatin1String("INSERT INTO `ignored_tts` (`hash`) VALUES (?)"));
else
query.prepare(QLatin1String("DELETE FROM `ignored_tts` WHERE `hash` = ?"));
query.addBindValue(hash);
execQueryAndLogFailure(query);
}
bool Database::isLocalMuted(const QString &hash) {
QSqlQuery query(db);

View File

@ -37,6 +37,9 @@ class Database : public QObject {
bool isLocalIgnored(const QString &hash);
void setLocalIgnored(const QString &hash, bool ignored);
bool isLocalIgnoredTTS(const QString &hash);
void setLocalIgnoredTTS(const QString &hash, bool ignoredTTS);
bool isLocalMuted(const QString &hash);
void setLocalMuted(const QString &hash, bool muted);

View File

@ -324,15 +324,15 @@ QString Log::formatChannel(::Channel *c) {
return QString::fromLatin1("<a href='channelid://%1/%3' class='log-channel'>%2</a>").arg(c->iId).arg(c->qsName.toHtmlEscaped()).arg(QString::fromLatin1(g.sh->qbaDigest.toBase64()));
}
void Log::logOrDefer(Log::MsgType mt, const QString &console, const QString &terse, bool ownMessage, const QString &overrideTTS) {
void Log::logOrDefer(Log::MsgType mt, const QString &console, const QString &terse, bool ownMessage, const QString &overrideTTS, bool ignoreTTS) {
if (g.l) {
// log directly as it seems the log-UI has been set-up already
g.l->log(mt, console, terse, ownMessage, overrideTTS);
g.l->log(mt, console, terse, ownMessage, overrideTTS, ignoreTTS);
} else {
// defer the log
QMutexLocker mLock(&Log::qmDeferredLogs);
qvDeferredLogs.append(LogMessage(mt, console, terse, ownMessage, overrideTTS));
qvDeferredLogs.append(LogMessage(mt, console, terse, ownMessage, overrideTTS, ignoreTTS));
}
}
@ -494,7 +494,7 @@ QString Log::validHtml(const QString &html, QTextCursor *tc) {
}
}
void Log::log(MsgType mt, const QString &console, const QString &terse, bool ownMessage, const QString &overrideTTS) {
void Log::log(MsgType mt, const QString &console, const QString &terse, bool ownMessage, const QString &overrideTTS, bool ignoreTTS) {
QDateTime dt = QDateTime::currentDateTime();
int ignore = qmIgnore.value(mt);
@ -601,7 +601,7 @@ void Log::log(MsgType mt, const QString &console, const QString &terse, bool own
}
// Message notification with Text-To-Speech
if (g.s.bDeaf || !g.s.bTTS || !(flags & Settings::LogTTS)) {
if (g.s.bDeaf || !g.s.bTTS || !(flags & Settings::LogTTS) || ignoreTTS) {
return;
}
@ -658,7 +658,7 @@ void Log::processDeferredLogs() {
while (!qvDeferredLogs.isEmpty()) {
LogMessage msg = qvDeferredLogs.takeFirst();
log(msg.mt, msg.console, msg.terse, msg.ownMessage, msg.overrideTTS);
log(msg.mt, msg.console, msg.terse, msg.ownMessage, msg.overrideTTS, msg.ignoreTTS);
}
}
@ -682,8 +682,8 @@ void Log::postQtNotification(MsgType mt, const QString &plain) {
}
}
LogMessage::LogMessage(Log::MsgType mt, const QString &console, const QString &terse, bool ownMessage, const QString &overrideTTS) : mt(mt), console(console),
terse(terse), ownMessage(ownMessage), overrideTTS(overrideTTS) {
LogMessage::LogMessage(Log::MsgType mt, const QString &console, const QString &terse, bool ownMessage, const QString &overrideTTS, bool ignoreTTS) : mt(mt), console(console),
terse(terse), ownMessage(ownMessage), overrideTTS(overrideTTS), ignoreTTS(ignoreTTS) {
}
LogDocument::LogDocument(QObject *p)

View File

@ -90,9 +90,9 @@ class Log : public QObject {
static QString formatChannel(::Channel *c);
/// Either defers the LogMessage or defers it, depending on whether Global::l is created already
/// (if it is, it is used to directly log the msg)
static void logOrDefer(Log::MsgType mt, const QString &console, const QString &terse=QString(), bool ownMessage = false, const QString &overrideTTS=QString());
static void logOrDefer(Log::MsgType mt, const QString &console, const QString &terse=QString(), bool ownMessage = false, const QString &overrideTTS=QString(), bool ignoreTTS = false);
public slots:
void log(MsgType mt, const QString &console, const QString &terse=QString(), bool ownMessage = false, const QString &overrideTTS=QString());
void log(MsgType mt, const QString &console, const QString &terse=QString(), bool ownMessage = false, const QString &overrideTTS=QString(), bool ignoreTTS = false);
/// Logs LogMessages that have been deferred so far
void processDeferredLogs();
};
@ -104,9 +104,10 @@ class LogMessage {
QString terse;
bool ownMessage;
QString overrideTTS;
bool ignoreTTS;
LogMessage() = default;
LogMessage(Log::MsgType mt, const QString &console, const QString &terse, bool ownMessage, const QString &overrideTTS);
LogMessage(Log::MsgType mt, const QString &console, const QString &terse, bool ownMessage, const QString &overrideTTS, bool ignoreTTS);
};
class LogDocument : public QTextDocument {

View File

@ -1526,6 +1526,8 @@ void MainWindow::qmUser_aboutToShow() {
qmUser->addAction(qaUserPrioritySpeaker);
qmUser->addAction(qaUserLocalMute);
qmUser->addAction(qaUserLocalIgnore);
if (g.s.bTTS)
qmUser->addAction(qaUserLocalIgnoreTTS);
qmUser->addAction(qaUserLocalVolume);
if (isSelf)
@ -1585,6 +1587,7 @@ void MainWindow::qmUser_aboutToShow() {
qaUserLocalMute->setEnabled(false);
qaUserLocalVolume->setEnabled(false);
qaUserLocalIgnore->setEnabled(false);
qaUserLocalIgnoreTTS->setEnabled(false);
qaUserCommentReset->setEnabled(false);
qaUserTextureReset->setEnabled(false);
qaUserCommentView->setEnabled(false);
@ -1595,6 +1598,7 @@ void MainWindow::qmUser_aboutToShow() {
qaUserLocalMute->setEnabled(! isSelf);
qaUserLocalVolume->setEnabled(! isSelf);
qaUserLocalIgnore->setEnabled(! isSelf);
qaUserLocalIgnoreTTS->setEnabled(! isSelf);
// If the server's version is less than 1.4.0 it won't support the new permission to reset a comment/avatar, so fall back to the old method
if (g.sh->uiVersion < 0x010400) {
qaUserCommentReset->setEnabled(! p->qbaCommentHash.isEmpty() && (g.pPermissions & (ChanACL::Move | ChanACL::Write)));
@ -1610,6 +1614,7 @@ void MainWindow::qmUser_aboutToShow() {
qaUserPrioritySpeaker->setChecked(p->bPrioritySpeaker);
qaUserLocalMute->setChecked(p->bLocalMute);
qaUserLocalIgnore->setChecked(p->bLocalIgnore);
qaUserLocalIgnoreTTS->setChecked(p->bLocalIgnoreTTS);
}
updateMenuPermissions();
}
@ -1690,6 +1695,18 @@ void MainWindow::on_qaUserLocalIgnore_triggered() {
g.db->setLocalIgnored(p->qsHash, ignored);
}
void MainWindow::on_qaUserLocalIgnoreTTS_triggered() {
ClientUser *p = getContextMenuUser();
if (!p)
return;
bool ignoredTTS = qaUserLocalIgnoreTTS->isChecked();
p->setLocalIgnoreTTS(ignoredTTS);
if (! p->qsHash.isEmpty())
g.db->setLocalIgnoredTTS(p->qsHash, ignoredTTS);
}
void MainWindow::on_qaUserLocalVolume_triggered() {
ClientUser *p = getContextMenuUser();
if (!p) {

View File

@ -206,6 +206,7 @@ class MainWindow : public QMainWindow, public MessageHandler, public Ui::MainWin
void on_qaSelfPrioritySpeaker_triggered();
void on_qaUserPrioritySpeaker_triggered();
void on_qaUserLocalIgnore_triggered();
void on_qaUserLocalIgnoreTTS_triggered();
void on_qaUserLocalMute_triggered();
void on_qaUserLocalVolume_triggered();
void on_qaUserTextMessage_triggered();

View File

@ -955,6 +955,20 @@ the channel's context menu.</string>
<string>Joins the channel of this user.</string>
</property>
</action>
<action name="qaUserLocalIgnoreTTS">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Disable Text-To-Speech</string>
</property>
<property name="toolTip">
<string>Locally disable Text-To-Speech for this user's text chat messages.</string>
</property>
<property name="whatsThis">
<string>Silently disables Text-To-Speech for all text messages from the user.</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>

View File

@ -504,6 +504,8 @@ void MainWindow::msgUserState(const MumbleProto::UserState &msg) {
pDst->setLocalMute(true);
if (g.db->isLocalIgnored(pDst->qsHash))
pDst->setLocalIgnore(true);
if (g.db->isLocalIgnoredTTS(pDst->qsHash))
pDst->setLocalIgnoreTTS(true);
pDst->fLocalVolume = g.db->getUserLocalVolume(pDst->qsHash);
}
@ -909,7 +911,8 @@ void MainWindow::msgTextMessage(const MumbleProto::TextMessage &msg) {
tr("%2%1: %3").arg(name).arg(target).arg(u8(msg.message())),
tr("Message from %1").arg(plainName),
false,
overrideTTS.isNull() ? QString() : overrideTTS);
overrideTTS.isNull() ? QString() : overrideTTS,
pSrc->bLocalIgnoreTTS);
}
/// This message is being received when the server informs the client about the access control list (ACL) for