chatgui: per-event sound settings with DB persistence

- Sound files loaded from filesystem (sound_dir in INI config, absolute or relative)
- Per-event enable/disable + volume stored in DB (ui_state table)
- SoundManager: setEvent(), effective volume = master * per-event
- SoundSettingsPage UI: 4 event rows (checkbox + slider)
- chat_core_save_ui_state() for async DB writes via uasync thread
- DbManager::getUiStateInt() for reading settings from GUI thread
- Default sounds copied to sounds/ dir next to DB, config entries written
This commit is contained in:
Evgeny committed 2026-07-19 15:17:52 +03:00
1 parent 1593079da8
commit 98bfb9dcd2
12 files changed
+267 -114

No files matched your search

+123 -62
View File
@@ -1,16 +1,22 @@
#include "soundsettingspage.h"
#include "sound_manager.h"
#include "../db/db_manager.h"
#include "../transport/gui_bridge.h"
#include "../../lib/mem.h"
extern "C" {
#include "../transport/chat_core.h"
}
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QCheckBox>
#include <QSlider>
#include <QLabel>
#include <QFile>
#include <QTextStream>
#include <QFrame>
#include <QDebug>
SoundSettingsPage::SoundSettingsPage(QWidget* parent)
SoundSettingsPage::SoundSettingsPage(DbManager* db, QWidget* parent)
: QWidget(parent)
, m_db(db)
{
auto* layout = new QVBoxLayout(this);
layout->setContentsMargins(0, 12, 0, 0);
@@ -28,8 +34,9 @@ SoundSettingsPage::SoundSettingsPage(QWidget* parent)
m_volumeSlider = new QSlider(Qt::Horizontal, this);
m_volumeSlider->setRange(0, 100);
m_volumeSlider->setFixedWidth(200);
m_volumeSlider->setStyleSheet("QSlider::groove:horizontal { height: 6px; border-radius: 3px; background: palette(mid); }"
"QSlider::handle:horizontal { width: 14px; height: 14px; margin: -4px 0; border-radius: 7px; background: #3390EC; }");
m_volumeSlider->setStyleSheet(
"QSlider::groove:horizontal { height: 6px; border-radius: 3px; background: palette(mid); }"
"QSlider::handle:horizontal { width: 14px; height: 14px; margin: -4px 0; border-radius: 7px; background: #3390EC; }");
volLayout->addWidget(m_volumeSlider);
m_volumeLabel = new QLabel("80%", this);
@@ -39,82 +46,136 @@ SoundSettingsPage::SoundSettingsPage(QWidget* parent)
volLayout->addStretch();
layout->addLayout(volLayout);
auto* sep = new QFrame(this);
sep->setFrameShape(QFrame::HLine);
sep->setStyleSheet("QFrame { color: palette(mid); }");
layout->addWidget(sep);
auto* header = new QLabel("Events", this);
header->setStyleSheet("font-size: 13px; font-weight: bold;");
layout->addWidget(header);
m_events[0] = {"msg_incoming", "Incoming message", nullptr, nullptr, nullptr};
m_events[1] = {"msg_outgoing", "Outgoing message", nullptr, nullptr, nullptr};
m_events[2] = {"connect", "Peer connected", nullptr, nullptr, nullptr};
m_events[3] = {"disconnect", "Peer disconnected", nullptr, nullptr, nullptr};
for (int i = 0; i < 4; i++) {
auto& e = m_events[i];
auto* row = new QHBoxLayout();
row->setSpacing(8);
auto* lbl = new QLabel(e.label, this);
lbl->setFixedWidth(140);
lbl->setStyleSheet("font-size: 13px;");
row->addWidget(lbl);
e.check = new QCheckBox(this);
row->addWidget(e.check);
e.slider = new QSlider(Qt::Horizontal, this);
e.slider->setRange(0, 100);
e.slider->setFixedWidth(100);
e.slider->setStyleSheet(
"QSlider::groove:horizontal { height: 4px; border-radius: 2px; background: palette(mid); }"
"QSlider::handle:horizontal { width: 10px; height: 10px; margin: -3px 0; border-radius: 5px; background: #3390EC; }");
row->addWidget(e.slider);
e.volLabel = new QLabel("80%", this);
e.volLabel->setFixedWidth(30);
e.volLabel->setStyleSheet("font-size: 12px;");
row->addWidget(e.volLabel);
row->addStretch();
layout->addLayout(row);
connect(e.slider, &QSlider::valueChanged, this, [this, i](int val) {
m_events[i].volLabel->setText(QString::number(val) + "%");
});
}
layout->addStretch();
connect(m_enabledCheck, &QCheckBox::stateChanged, this, &SoundSettingsPage::onEnabledChanged);
connect(m_enabledCheck, &QCheckBox::stateChanged, this, [this](int state) {
bool on = (state == Qt::Checked);
m_volumeSlider->setEnabled(on);
m_volumeLabel->setEnabled(on);
});
connect(m_volumeSlider, &QSlider::valueChanged, this, [this](int val) {
m_volumeLabel->setText(QString::number(val) + "%");
});
}
void SoundSettingsPage::onEnabledChanged(int state) {
m_volumeSlider->setEnabled(state == Qt::Checked);
m_volumeLabel->setEnabled(state == Qt::Checked);
}
void SoundSettingsPage::loadFromConfig(const QString& path) {
QFile f(path);
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) return;
bool enabled = true;
int volume = 80;
bool inGui = false;
QTextStream in(&f);
while (!in.atEnd()) {
QString line = in.readLine().trimmed();
if (line == "[gui]") { inGui = true; continue; }
if (line.startsWith("[") && line.endsWith("]")) inGui = false;
if (!inGui) continue;
if (line.startsWith("sound_enabled="))
enabled = line.mid(QString("sound_enabled=").size()).trimmed() != "0";
else if (line.startsWith("sound_volume="))
volume = line.mid(QString("sound_volume=").size()).trimmed().toInt();
}
f.close();
void SoundSettingsPage::loadFromDb() {
if (!m_db) return;
bool enabled = m_db->getUiStateInt("sound_enabled", 1);
m_enabledCheck->setChecked(enabled);
m_volumeSlider->setValue(volume);
int vol = m_db->getUiStateInt("sound_volume", 80);
m_volumeSlider->setValue(vol);
m_volumeSlider->setEnabled(enabled);
m_volumeLabel->setEnabled(enabled);
m_volumeLabel->setText(QString::number(volume) + "%");
m_volumeLabel->setText(QString::number(vol) + "%");
for (int i = 0; i < 4; i++) {
auto& e = m_events[i];
QString onKey = QStringLiteral("sound_%1_on").arg(e.key);
QString volKey = QStringLiteral("sound_%1_vol").arg(e.key);
bool on = m_db->getUiStateInt(onKey, i == 0 ? 1 : 0); // only msg_incoming on by default
int ev = m_db->getUiStateInt(volKey, 80);
e.check->setChecked(on);
e.slider->setValue(ev);
e.volLabel->setText(QString::number(ev) + "%");
}
}
void SoundSettingsPage::saveToConfig(const QString& path) const {
QFile f(path);
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) return;
QString content = f.readAll();
f.close();
void SoundSettingsPage::applyAndSave() {
SoundManager* sm = SoundManager::instance();
QStringList lines = content.split('\n');
bool enabled = m_enabledCheck->isChecked();
float masterVol = m_volumeSlider->value() / 100.0f;
sm->setEnabled(enabled);
sm->setVolume(masterVol);
bool inGui = false;
bool foundEnabled = false, foundVolume = false;
for (int i = 0; i < lines.size(); i++) {
QString line = lines[i].trimmed();
if (line == "[gui]") { inGui = true; continue; }
if (line.startsWith("[") && line.endsWith("]")) inGui = false;
if (!inGui) continue;
for (int i = 0; i < 4; i++) {
auto& e = m_events[i];
bool on = e.check->isChecked();
float ev = e.slider->value() / 100.0f;
sm->setEvent(e.key, on, ev);
if (line.startsWith("sound_enabled=")) {
lines[i] = QString("sound_enabled=%1").arg(m_enabledCheck->isChecked() ? 1 : 0);
foundEnabled = true;
} else if (line.startsWith("sound_volume=")) {
lines[i] = QString("sound_volume=%1").arg(m_volumeSlider->value());
foundVolume = true;
/* async DB persist — key\0value\0 */
QString onKey = QStringLiteral("sound_%1_on").arg(e.key);
QString volKey = QStringLiteral("sound_%1_vol").arg(e.key);
QByteArray onVal = on ? QByteArray("1") : QByteArray("0");
QByteArray volVal = QByteArray::number(e.slider->value());
for (int j = 0; j < 2; j++) {
const char* k = j == 0 ? onKey.toUtf8().constData() : volKey.toUtf8().constData();
const char* v = j == 0 ? onVal.constData() : volVal.constData();
size_t klen = strlen(k), vlen = strlen(v);
size_t total = klen + 1 + vlen + 1;
if (total > 256) continue;
void* arg = u_malloc(total);
memcpy(arg, k, klen + 1);
memcpy((char*)arg + klen + 1, v, vlen + 1);
gui_bridge_post_uasync_fn(chat_core_save_ui_state_trampoline, arg);
}
}
if (!foundEnabled || !foundVolume) {
for (int i = 0; i < lines.size(); i++) {
if (lines[i].trimmed() == "[gui]") {
if (!foundEnabled) lines.insert(i + 1, QString("sound_enabled=%1").arg(m_enabledCheck->isChecked() ? 1 : 0));
if (!foundVolume) lines.insert(i + 1, QString("sound_volume=%1").arg(m_volumeSlider->value()));
break;
}
/* save global settings */
{
const char* keys[] = {"sound_enabled", "sound_volume"};
QByteArray vals[] = {enabled ? QByteArray("1") : QByteArray("0"),
QByteArray::number(m_volumeSlider->value())};
for (int j = 0; j < 2; j++) {
size_t klen = strlen(keys[j]), vlen = vals[j].size();
size_t total = klen + 1 + vlen + 1;
if (total > 256) continue;
void* arg = u_malloc(total);
memcpy(arg, keys[j], klen + 1);
memcpy((char*)arg + klen + 1, vals[j].constData(), vlen + 1);
gui_bridge_post_uasync_fn(chat_core_save_ui_state_trampoline, arg);
}
}
f.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text);
f.write(lines.join('\n').toUtf8());
f.close();
}