Add English and Russian ChatGUI interface translations
This commit is contained in:
1 parent
972c6b7f0a
commit
27d93ecaf9
50 files changed
+4978
-491
No files matched your search
@@ -15,12 +15,47 @@ if(NOT Qt6_FOUND)
|
||||
set(QT_LIBS Qt5::Widgets Qt5::Network)
|
||||
set(QT_CORE Qt5::Core)
|
||||
set(QT_TEST_LIB Qt5::Test)
|
||||
set(QT_PACKAGE Qt5)
|
||||
else()
|
||||
set(QT_LIBS Qt6::Widgets Qt6::Network)
|
||||
set(QT_CORE Qt6::Core)
|
||||
set(QT_TEST_LIB Qt6::Test)
|
||||
set(QT_PACKAGE Qt6)
|
||||
endif()
|
||||
|
||||
# Каталоги в ресурсах: перевод работает и в переносимой сборке без файлов рядом с exe.
|
||||
get_target_property(QT_QMAKE ${QT_PACKAGE}::qmake IMPORTED_LOCATION)
|
||||
execute_process(COMMAND "${QT_QMAKE}" -query QT_HOST_BINS
|
||||
OUTPUT_VARIABLE QT_BINARY_DIR OUTPUT_STRIP_TRAILING_WHITESPACE RESULT_VARIABLE QT_BINARY_RESULT)
|
||||
if(NOT QT_BINARY_RESULT EQUAL 0)
|
||||
message(FATAL_ERROR "Cannot query Qt host tools directory")
|
||||
endif()
|
||||
find_program(QT_GUI_LRELEASE NAMES lrelease HINTS "${QT_BINARY_DIR}" NO_DEFAULT_PATH)
|
||||
if(NOT QT_GUI_LRELEASE)
|
||||
message(FATAL_ERROR "Qt lrelease is required to build GUI translations (install Qt Linguist tools)")
|
||||
endif()
|
||||
execute_process(COMMAND "${QT_QMAKE}" -query QT_INSTALL_TRANSLATIONS
|
||||
OUTPUT_VARIABLE QT_TRANSLATIONS_DIR OUTPUT_STRIP_TRAILING_WHITESPACE RESULT_VARIABLE QT_TRANSLATIONS_RESULT)
|
||||
if(NOT QT_TRANSLATIONS_RESULT EQUAL 0)
|
||||
message(FATAL_ERROR "Cannot query Qt translations directory")
|
||||
endif()
|
||||
set(QT_RUSSIAN_CATALOG "${QT_TRANSLATIONS_DIR}/qtbase_ru.qm")
|
||||
if(NOT EXISTS "${QT_RUSSIAN_CATALOG}")
|
||||
message(FATAL_ERROR "Qt Russian translations missing: ${QT_RUSSIAN_CATALOG}")
|
||||
endif()
|
||||
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/translations")
|
||||
set(GUI_QM_FILES)
|
||||
foreach(LANGUAGE en ru)
|
||||
set(QM_FILE "${CMAKE_CURRENT_BINARY_DIR}/translations/chatgui_${LANGUAGE}.qm")
|
||||
add_custom_command(OUTPUT "${QM_FILE}"
|
||||
COMMAND "${QT_GUI_LRELEASE}" -nounfinished "${CMAKE_CURRENT_SOURCE_DIR}/translations/chatgui_${LANGUAGE}.ts" -qm "${QM_FILE}"
|
||||
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/translations/chatgui_${LANGUAGE}.ts"
|
||||
VERBATIM)
|
||||
list(APPEND GUI_QM_FILES "${QM_FILE}")
|
||||
endforeach()
|
||||
add_custom_target(chatgui_translations DEPENDS ${GUI_QM_FILES})
|
||||
configure_file(translations/translations.qrc.in "${CMAKE_CURRENT_BINARY_DIR}/translations.qrc" @ONLY)
|
||||
|
||||
find_package(rlottie REQUIRED HINTS "${CMAKE_CURRENT_SOURCE_DIR}/rlottie_build")
|
||||
message(STATUS "rlottie package: ${rlottie_DIR}")
|
||||
find_package(ZLIB REQUIRED)
|
||||
@@ -160,7 +195,9 @@ add_executable(vibechat
|
||||
db/db_manager.cpp
|
||||
../../lib/sqlite3.c
|
||||
resources/chatgui.qrc
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/translations.qrc"
|
||||
)
|
||||
add_dependencies(vibechat chatgui_translations)
|
||||
|
||||
target_include_directories(vibechat PRIVATE ${CMAKE_SOURCE_DIR}/../../lib ${CMAKE_SOURCE_DIR}/../../src ${CMAKE_SOURCE_DIR}/db ${CMAKE_SOURCE_DIR}/../../lib/libopus/include ${FFMPEG_INCLUDE_DIRS})
|
||||
target_compile_definitions(vibechat PRIVATE SQLITE_THREADSAFE=1 USE_SQLITE)
|
||||
@@ -228,6 +265,14 @@ endif()
|
||||
|
||||
enable_testing()
|
||||
|
||||
add_executable(test_gui_language tests/test_gui_language.cpp src/networksettingspage.cpp
|
||||
transport/node_config.cpp transport/config_updater.cpp "${CMAKE_CURRENT_BINARY_DIR}/translations.qrc")
|
||||
add_dependencies(test_gui_language chatgui_translations)
|
||||
target_compile_definitions(test_gui_language PRIVATE GUI_TS_DIR="${CMAKE_CURRENT_SOURCE_DIR}/translations")
|
||||
target_link_libraries(test_gui_language PRIVATE ${QT_LIBS} ${QT_TEST_LIB} OpenSSL::Crypto utun pthread)
|
||||
add_test(NAME test_gui_language COMMAND test_gui_language)
|
||||
set_tests_properties(test_gui_language PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=offscreen" TIMEOUT 30)
|
||||
|
||||
add_executable(test_emoji_playback tests/test_emoji_playback.cpp src/animtimer.cpp src/lottieicon.cpp
|
||||
src/emojipanel.cpp src/emojitabbar.cpp src/emoji.cpp src/chatview.cpp)
|
||||
target_link_libraries(test_emoji_playback PRIVATE ${QT_LIBS} ${QT_TEST_LIB} rlottie::rlottie ZLIB::ZLIB utun)
|
||||
|
||||
@@ -55,10 +55,10 @@ install_deps() {
|
||||
echo "[deps] installing build dependencies..."
|
||||
if [ "$OS_ID" = linux ]; then
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential cmake qt6-base-dev librlottie-dev zlib1g-dev libssl-dev \
|
||||
sudo apt-get install -y build-essential cmake qt6-base-dev qt6-tools-dev-tools qt6-translations-l10n librlottie-dev zlib1g-dev libssl-dev \
|
||||
libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev
|
||||
else
|
||||
sudo pkg install -y cmake qt6-base rlottie openssl ffmpeg
|
||||
sudo pkg install -y cmake qt6-base qt6-tools qt6-translations rlottie openssl ffmpeg
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,18 @@
|
||||
|
||||
Десктопный GUI-чат (Telegram-подобный) со встроенным P2P uTun-узлом. Приложение объединяет полнофункциональный интерфейс обмена сообщениями и децентрализованный транспортный слой в едином процессе. Рассчитан на работу без центральных серверов — вся маршрутизация, синхронизация и доставка сообщений идут напрямую между узлами по ETCP-протоколу.
|
||||
|
||||
Язык интерфейса: русский или английский. По умолчанию используется первый поддерживаемый язык из списка языков интерфейса ОС;
|
||||
если поддерживаемых языков нет, используется английский. Выбор в «Настройки → Профиль → Язык интерфейса»:
|
||||
«Язык системы», English или Русский. Смена языка применяется после перезапуска, настройка хранится в `[gui] language=system|en|ru`.
|
||||
Региональный формат дат и чисел остаётся системным. Диагностические дампы ядра сохраняют исходные имена полей.
|
||||
|
||||
Каталоги `translations/chatgui_en.ts` и `translations/chatgui_ru.ts` компилируются `lrelease` и встраиваются в приложение вместе
|
||||
с `qtbase_ru.qm` для стандартных виджетов Qt. Для сборки нужны инструменты Qt Linguist и пакет переводов Qt
|
||||
(Debian/Ubuntu: `qt6-tools-dev-tools`, `qt6-translations-l10n`; для Qt 5: `qttools5-dev-tools`, `qttranslations5-l10n`).
|
||||
После добавления строк обновить каталоги из `tools/chatgui`:
|
||||
`lupdate src -no-obsolete -locations relative -ts translations/chatgui_en.ts translations/chatgui_ru.ts`,
|
||||
заполнить новые переводы и пересобрать. Проверка: `ctest --test-dir build -R test_gui_language --output-on-failure`.
|
||||
|
||||
## 2. Архитектура
|
||||
|
||||
### 2.1. Уровни приложения
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <QCoreApplication>
|
||||
#include "accountlist.h"
|
||||
#include "accountdelegate.h"
|
||||
#include "memberlistmodel.h"
|
||||
@@ -57,10 +58,10 @@ QString AccountList::relativeTimeStr(qint64 timestamp) {
|
||||
if (timestamp <= 0) return "";
|
||||
qint64 diff = QDateTime::currentSecsSinceEpoch() - timestamp;
|
||||
if (diff < 0) diff = 0;
|
||||
if (diff < 60) return QString("%1s").arg(diff);
|
||||
if (diff < 3600) return QString("%1m").arg(diff / 60);
|
||||
if (diff < 86400) return QString("%1h").arg(diff / 3600);
|
||||
return QString("%1d").arg(diff / 86400);
|
||||
if (diff < 60) return QCoreApplication::translate("ChatGui", "%1s").arg(diff);
|
||||
if (diff < 3600) return QCoreApplication::translate("ChatGui", "%1m").arg(diff / 60);
|
||||
if (diff < 86400) return QCoreApplication::translate("ChatGui", "%1h").arg(diff / 3600);
|
||||
return QCoreApplication::translate("ChatGui", "%1d").arg(diff / 86400);
|
||||
}
|
||||
|
||||
AccountList::AccountList(DbManager* db, QWidget *parent)
|
||||
@@ -75,7 +76,7 @@ AccountList::AccountList(DbManager* db, QWidget *parent)
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->setSpacing(0);
|
||||
|
||||
m_header = new QLabel("Members", this);
|
||||
m_header = new QLabel(QCoreApplication::translate("ChatGui", "Members"), this);
|
||||
m_header->setAlignment(Qt::AlignCenter);
|
||||
m_header->setStyleSheet("font-weight: bold; padding: 8px; background: palette(window);");
|
||||
layout->addWidget(m_header);
|
||||
@@ -104,7 +105,7 @@ AccountList::AccountList(DbManager* db, QWidget *parent)
|
||||
detailLayout->setContentsMargins(0, 0, 0, 0);
|
||||
detailLayout->setSpacing(0);
|
||||
|
||||
m_detailHeader = new QLabel("Select a member", m_detailPanel);
|
||||
m_detailHeader = new QLabel(QCoreApplication::translate("ChatGui", "Select a member"), m_detailPanel);
|
||||
m_detailHeader->setWordWrap(true);
|
||||
m_detailHeader->setStyleSheet("font-weight: bold; padding: 4px 8px; background: palette(window);");
|
||||
detailLayout->addWidget(m_detailHeader);
|
||||
@@ -177,11 +178,11 @@ void AccountList::setChannel(const QString& channelId) {
|
||||
m_detailTimer->stop();
|
||||
m_linkTimer->stop();
|
||||
m_detailNodeId = 0;
|
||||
m_detailHeader->setText("Select a member");
|
||||
m_detailHeader->setText(QCoreApplication::translate("ChatGui", "Select a member"));
|
||||
m_detailText->clear();
|
||||
|
||||
m_header->setText(isOwner ? QString::fromUtf8("Members \xF0\x9F\x94\x91") : QStringLiteral("Members"));
|
||||
m_header->setToolTip(isOwner ? QStringLiteral("Channel owner (has private key)") : QString());
|
||||
m_header->setText(isOwner ? QCoreApplication::translate("ChatGui", "Members 🔑") : QCoreApplication::translate("ChatGui", "Members"));
|
||||
m_header->setToolTip(isOwner ? QCoreApplication::translate("ChatGui", "Channel owner (has private key)") : QString());
|
||||
}
|
||||
|
||||
void AccountList::refresh() { m_model->refresh(); }
|
||||
@@ -371,9 +372,9 @@ void AccountList::updateDetailDisplay() {
|
||||
hdr.append(QString("<span style='color:%1;'>●</span> %2 %3")
|
||||
.arg(dotColor, m_detailName.toHtmlEscaped(), nodeHex));
|
||||
if (m_detailLastSeen > 0)
|
||||
hdr.append(QString("seen %1").arg(relativeTimeStr(m_detailLastSeen)));
|
||||
hdr.append(QCoreApplication::translate("ChatGui", "seen %1").arg(relativeTimeStr(m_detailLastSeen)));
|
||||
if (m_detailCreated > 0)
|
||||
hdr.append(QString("created %1").arg(relativeTimeStr(m_detailCreated)));
|
||||
hdr.append(QCoreApplication::translate("ChatGui", "created %1").arg(relativeTimeStr(m_detailCreated)));
|
||||
m_detailHeader->setText(hdr.join(" "));
|
||||
|
||||
/* ── Body ── */
|
||||
@@ -385,7 +386,7 @@ void AccountList::updateDetailDisplay() {
|
||||
};
|
||||
|
||||
if (!m_detailAddrs.isEmpty()) {
|
||||
lines.append("── Addresses ──");
|
||||
lines.append(QCoreApplication::translate("ChatGui", "── Addresses ──"));
|
||||
for (const auto& a : m_detailAddrs) {
|
||||
lines.append(QString(" [sock%1] %2 %3 %4:%5 %6")
|
||||
.arg(a.socket_id)
|
||||
@@ -398,7 +399,7 @@ void AccountList::updateDetailDisplay() {
|
||||
}
|
||||
|
||||
if (isSelf) {
|
||||
lines.append("── Listen ──");
|
||||
lines.append(QCoreApplication::translate("ChatGui", "── Listen ──"));
|
||||
for (const auto& s : m_snapSocks) {
|
||||
const DetailAddress* a = nullptr;
|
||||
for (const auto& da : m_detailAddrs) {
|
||||
@@ -413,7 +414,7 @@ void AccountList::updateDetailDisplay() {
|
||||
}
|
||||
} else {
|
||||
if (!m_snapLinks.isEmpty()) {
|
||||
lines.append("── Connections ──");
|
||||
lines.append(QCoreApplication::translate("ChatGui", "── Connections ──"));
|
||||
for (const auto& lk : m_snapLinks) {
|
||||
bool up = (lk.linkState == 3 && lk.linkStatus == 1);
|
||||
QString c = up ? "#4CAF50" : "#E53935";
|
||||
@@ -482,15 +483,15 @@ void AccountList::onMemberContextMenu(const QPoint& pos) {
|
||||
QString name = m_model->data(index, Qt::DisplayRole).toString();
|
||||
|
||||
QMenu menu(m_listView);
|
||||
QAction* pmAction = menu.addAction(QString::fromUtf8("Написать сообщение"));
|
||||
QAction* pmAction = menu.addAction(QCoreApplication::translate("ChatGui", "Send message"));
|
||||
if (nodeId == m_db->myNodeId()) pmAction->setEnabled(false);
|
||||
QAction* callAction = menu.addAction(QString::fromUtf8("Позвонить"));
|
||||
QAction* callAction = menu.addAction(QCoreApplication::translate("ChatGui", "Call peer"));
|
||||
if (nodeId == m_db->myNodeId() || !m_model->isOnlineAt(index.row())) callAction->setEnabled(false);
|
||||
QAction* connectAction = menu.addAction("Connect");
|
||||
QAction* connectAction = menu.addAction(QCoreApplication::translate("ChatGui", "Connect"));
|
||||
if (nodeId == m_db->myNodeId()) connectAction->setEnabled(false);
|
||||
QAction* renameAction = menu.addAction("Rename");
|
||||
QAction* renameAction = menu.addAction(QCoreApplication::translate("ChatGui", "Rename"));
|
||||
if (nodeId == m_db->myNodeId()) renameAction->setEnabled(false);
|
||||
QAction* editAction = menu.addAction("Edit");
|
||||
QAction* editAction = menu.addAction(QCoreApplication::translate("ChatGui", "Edit"));
|
||||
QAction* chosen = menu.exec(m_listView->viewport()->mapToGlobal(pos));
|
||||
|
||||
if (chosen == pmAction) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <QCoreApplication>
|
||||
#include "audiodevicesettingspage.h"
|
||||
#include "sound_manager.h"
|
||||
#include "audiorecorder.h"
|
||||
@@ -33,14 +34,14 @@ AudioDeviceSettingsPage::AudioDeviceSettingsPage(DbManager* db, AudioRecorder* r
|
||||
formLayout->setContentsMargins(0, 0, 0, 0);
|
||||
formLayout->setSpacing(10);
|
||||
|
||||
auto* outLabel = new QLabel("Playback device:", this);
|
||||
auto* outLabel = new QLabel(QCoreApplication::translate("ChatGui", "Playback device:"), this);
|
||||
m_outputCombo = new QComboBox(this);
|
||||
m_outputCombo->setStyleSheet(
|
||||
"QComboBox { border: 1px solid palette(mid); border-radius: 4px;"
|
||||
" padding: 6px 10px; font-size: 13px; background: palette(base); }");
|
||||
formLayout->addRow(outLabel, m_outputCombo);
|
||||
|
||||
auto* inLabel = new QLabel("Recording device:", this);
|
||||
auto* inLabel = new QLabel(QCoreApplication::translate("ChatGui", "Recording device:"), this);
|
||||
m_inputCombo = new QComboBox(this);
|
||||
m_inputCombo->setStyleSheet(m_outputCombo->styleSheet());
|
||||
formLayout->addRow(inLabel, m_inputCombo);
|
||||
@@ -54,7 +55,7 @@ AudioDeviceSettingsPage::AudioDeviceSettingsPage(DbManager* db, AudioRecorder* r
|
||||
layout->addWidget(sep1);
|
||||
|
||||
/* --- Microphone test --- */
|
||||
auto* micLabel = new QLabel("Microphone test", this);
|
||||
auto* micLabel = new QLabel(QCoreApplication::translate("ChatGui", "Microphone test"), this);
|
||||
micLabel->setStyleSheet("font-size: 13px; font-weight: bold;");
|
||||
layout->addWidget(micLabel);
|
||||
|
||||
@@ -67,7 +68,7 @@ AudioDeviceSettingsPage::AudioDeviceSettingsPage(DbManager* db, AudioRecorder* r
|
||||
m_micLevelBar->setFixedHeight(16);
|
||||
micRow->addWidget(m_micLevelBar, 1);
|
||||
|
||||
m_testMicBtn = new QPushButton("Test Microphone", this);
|
||||
m_testMicBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Test Microphone"), this);
|
||||
m_testMicBtn->setCheckable(true);
|
||||
m_testMicBtn->setStyleSheet(
|
||||
"QPushButton { border: 1px solid palette(mid); border-radius: 4px;"
|
||||
@@ -84,11 +85,11 @@ AudioDeviceSettingsPage::AudioDeviceSettingsPage(DbManager* db, AudioRecorder* r
|
||||
layout->addWidget(sep2);
|
||||
|
||||
/* --- Speaker test --- */
|
||||
auto* spkLabel = new QLabel("Speaker test", this);
|
||||
auto* spkLabel = new QLabel(QCoreApplication::translate("ChatGui", "Speaker test"), this);
|
||||
spkLabel->setStyleSheet("font-size: 13px; font-weight: bold;");
|
||||
layout->addWidget(spkLabel);
|
||||
|
||||
m_testSpeakerBtn = new QPushButton("Test Speaker (440Hz)", this);
|
||||
m_testSpeakerBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Test Speaker (440Hz)"), this);
|
||||
m_testSpeakerBtn->setStyleSheet(
|
||||
"QPushButton { border: 1px solid palette(mid); border-radius: 4px;"
|
||||
" padding: 6px 16px; font-size: 13px; background: palette(button); }"
|
||||
@@ -103,14 +104,14 @@ AudioDeviceSettingsPage::AudioDeviceSettingsPage(DbManager* db, AudioRecorder* r
|
||||
layout->addWidget(sep3);
|
||||
|
||||
/* --- Codec quality --- */
|
||||
auto* codecLabel = new QLabel("Voice codec quality", this);
|
||||
auto* codecLabel = new QLabel(QCoreApplication::translate("ChatGui", "Voice codec quality"), this);
|
||||
codecLabel->setStyleSheet("font-size: 13px; font-weight: bold;");
|
||||
layout->addWidget(codecLabel);
|
||||
|
||||
m_codecPresetCombo = new QComboBox(this);
|
||||
m_codecPresetCombo->addItem("Low (16 kbps, fast)", 0);
|
||||
m_codecPresetCombo->addItem("Standard (32 kbps)", 1);
|
||||
m_codecPresetCombo->addItem("High (64 kbps, best)", 2);
|
||||
m_codecPresetCombo->addItem(QCoreApplication::translate("ChatGui", "Low (16 kbps, fast)"), 0);
|
||||
m_codecPresetCombo->addItem(QCoreApplication::translate("ChatGui", "Standard (32 kbps)"), 1);
|
||||
m_codecPresetCombo->addItem(QCoreApplication::translate("ChatGui", "High (64 kbps, best)"), 2);
|
||||
m_codecPresetCombo->setStyleSheet(m_outputCombo->styleSheet());
|
||||
layout->addWidget(m_codecPresetCombo);
|
||||
|
||||
@@ -121,7 +122,7 @@ AudioDeviceSettingsPage::AudioDeviceSettingsPage(DbManager* db, AudioRecorder* r
|
||||
layout->addWidget(sep5);
|
||||
|
||||
/* --- Radio (PTT) Opus --- */
|
||||
auto* radioLabel = new QLabel("Radio (PTT) — Opus", this);
|
||||
auto* radioLabel = new QLabel(QCoreApplication::translate("ChatGui", "Radio (PTT) — Opus"), this);
|
||||
radioLabel->setStyleSheet("font-size: 13px; font-weight: bold;");
|
||||
layout->addWidget(radioLabel);
|
||||
|
||||
@@ -130,20 +131,20 @@ AudioDeviceSettingsPage::AudioDeviceSettingsPage(DbManager* db, AudioRecorder* r
|
||||
radioForm->setSpacing(10);
|
||||
|
||||
m_radioOpusModeCombo = new QComboBox(this);
|
||||
m_radioOpusModeCombo->addItem("Voice (VOIP)", 0);
|
||||
m_radioOpusModeCombo->addItem("Music (AUDIO)", 1);
|
||||
m_radioOpusModeCombo->addItem(QCoreApplication::translate("ChatGui", "Voice (VOIP)"), 0);
|
||||
m_radioOpusModeCombo->addItem(QCoreApplication::translate("ChatGui", "Music (AUDIO)"), 1);
|
||||
m_radioOpusModeCombo->setStyleSheet(m_outputCombo->styleSheet());
|
||||
radioForm->addRow(new QLabel("Mode:", this), m_radioOpusModeCombo);
|
||||
radioForm->addRow(new QLabel(QCoreApplication::translate("ChatGui", "Mode:"), this), m_radioOpusModeCombo);
|
||||
|
||||
m_radioOpusBitrateCombo = new QComboBox(this);
|
||||
for (int br : {16, 32, 64, 96, 128})
|
||||
m_radioOpusBitrateCombo->addItem(QString::number(br) + " kbps", br);
|
||||
m_radioOpusBitrateCombo->addItem(QString::number(br) + QCoreApplication::translate("ChatGui", " kbps"), br);
|
||||
m_radioOpusBitrateCombo->setStyleSheet(m_outputCombo->styleSheet());
|
||||
radioForm->addRow(new QLabel("Bitrate:", this), m_radioOpusBitrateCombo);
|
||||
radioForm->addRow(new QLabel(QCoreApplication::translate("ChatGui", "Bitrate:"), this), m_radioOpusBitrateCombo);
|
||||
|
||||
layout->addLayout(radioForm);
|
||||
|
||||
m_radioCaptureStereoCb = new QCheckBox("Capture stereo (playback is always stereo)", this);
|
||||
m_radioCaptureStereoCb = new QCheckBox(QCoreApplication::translate("ChatGui", "Capture stereo (playback is always stereo)"), this);
|
||||
m_radioCaptureStereoCb->setStyleSheet("font-size: 12px;");
|
||||
layout->addWidget(m_radioCaptureStereoCb);
|
||||
|
||||
@@ -159,9 +160,9 @@ AudioDeviceSettingsPage::AudioDeviceSettingsPage(DbManager* db, AudioRecorder* r
|
||||
m_jitterReserveSpin->setDecimals(1);
|
||||
m_jitterReserveSpin->setSingleStep(0.1);
|
||||
m_jitterReserveSpin->setValue(1.0);
|
||||
m_jitterReserveSpin->setSuffix(" s");
|
||||
m_jitterReserveSpin->setToolTip("Maximum adaptive jitter reserve for calls. Applies to the next call.");
|
||||
callForm->addRow("Call: maximum jitter reserve", m_jitterReserveSpin);
|
||||
m_jitterReserveSpin->setSuffix(QCoreApplication::translate("ChatGui", " s"));
|
||||
m_jitterReserveSpin->setToolTip(QCoreApplication::translate("ChatGui", "Maximum adaptive jitter reserve for calls. Applies to the next call."));
|
||||
callForm->addRow(QCoreApplication::translate("ChatGui", "Call: maximum jitter reserve"), m_jitterReserveSpin);
|
||||
layout->addLayout(callForm);
|
||||
|
||||
buildCompressorUI(layout);
|
||||
@@ -183,19 +184,19 @@ void AudioDeviceSettingsPage::loadFromDb() {
|
||||
|
||||
/* Output devices */
|
||||
m_outputCombo->clear();
|
||||
m_outputCombo->addItem("Default (auto)", QString());
|
||||
m_outputCombo->addItem(QCoreApplication::translate("ChatGui", "Default (auto)"), QString());
|
||||
for (int i = 0; i < playbackDevs.size(); i++) {
|
||||
QString label = playbackDevs[i].name;
|
||||
if (playbackDevs[i].isDefault) label += " (default)";
|
||||
if (playbackDevs[i].isDefault) label += QCoreApplication::translate("ChatGui", " (default)");
|
||||
m_outputCombo->addItem(label, playbackDevs[i].id);
|
||||
}
|
||||
|
||||
/* Input devices */
|
||||
m_inputCombo->clear();
|
||||
m_inputCombo->addItem("Default (auto)", QString());
|
||||
m_inputCombo->addItem(QCoreApplication::translate("ChatGui", "Default (auto)"), QString());
|
||||
for (int i = 0; i < captureDevs.size(); i++) {
|
||||
QString label = captureDevs[i].name;
|
||||
if (captureDevs[i].isDefault) label += " (default)";
|
||||
if (captureDevs[i].isDefault) label += QCoreApplication::translate("ChatGui", " (default)");
|
||||
m_inputCombo->addItem(label, captureDevs[i].id);
|
||||
}
|
||||
|
||||
@@ -366,11 +367,11 @@ void AudioDeviceSettingsPage::onTestMicToggled() {
|
||||
m_testRecorder->startRecording(true);
|
||||
m_micTestActive = true;
|
||||
m_levelTimer->start();
|
||||
m_testMicBtn->setText("Stop Test");
|
||||
m_testMicBtn->setText(QCoreApplication::translate("ChatGui", "Stop Test"));
|
||||
m_micLevelBar->setValue(0);
|
||||
} else {
|
||||
stopMicTest();
|
||||
m_testMicBtn->setText("Test Microphone");
|
||||
m_testMicBtn->setText(QCoreApplication::translate("ChatGui", "Test Microphone"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -415,15 +416,15 @@ void AudioDeviceSettingsPage::onTestSpeaker() {
|
||||
}
|
||||
|
||||
void AudioDeviceSettingsPage::buildCompressorUI(QVBoxLayout* layout) {
|
||||
auto* compLabel = new QLabel("Voice Compressor", this);
|
||||
auto* compLabel = new QLabel(QCoreApplication::translate("ChatGui", "Voice Compressor"), this);
|
||||
compLabel->setStyleSheet("font-size: 13px; font-weight: bold;");
|
||||
layout->addWidget(compLabel);
|
||||
|
||||
m_compressorEnabledCb = new QCheckBox("Enable compressor (leveler/normalizer)", this);
|
||||
m_compressorEnabledCb = new QCheckBox(QCoreApplication::translate("ChatGui", "Enable compressor (leveler/normalizer)"), this);
|
||||
m_compressorEnabledCb->setStyleSheet("font-size: 12px;");
|
||||
layout->addWidget(m_compressorEnabledCb);
|
||||
|
||||
m_aecEnabledCb = new QCheckBox("Enable echo cancellation (AEC) in calls", this);
|
||||
m_aecEnabledCb = new QCheckBox(QCoreApplication::translate("ChatGui", "Enable echo cancellation (AEC) in calls"), this);
|
||||
m_aecEnabledCb->setStyleSheet("font-size: 12px;");
|
||||
layout->addWidget(m_aecEnabledCb);
|
||||
|
||||
@@ -432,7 +433,7 @@ void AudioDeviceSettingsPage::buildCompressorUI(QVBoxLayout* layout) {
|
||||
auto* row = new QHBoxLayout();
|
||||
row->setSpacing(6);
|
||||
auto* textLabel = new QLabel(name, this);
|
||||
textLabel->setFixedWidth(180);
|
||||
textLabel->setMinimumWidth(180);
|
||||
textLabel->setStyleSheet("font-size: 12px;");
|
||||
row->addWidget(textLabel);
|
||||
slider = new QSlider(Qt::Horizontal, this);
|
||||
@@ -450,9 +451,9 @@ void AudioDeviceSettingsPage::buildCompressorUI(QVBoxLayout* layout) {
|
||||
layout->addLayout(row);
|
||||
};
|
||||
|
||||
makeSliderRow("Max gain:", m_compressorMaxGainSlider, m_compressorMaxGainLabel,
|
||||
makeSliderRow(QCoreApplication::translate("ChatGui", "Max gain:"), m_compressorMaxGainSlider, m_compressorMaxGainLabel,
|
||||
1, 50, 1);
|
||||
makeSliderRow("Rise rate (x/sec):", m_compressorRiseRateSlider, m_compressorRiseRateLabel,
|
||||
makeSliderRow(QCoreApplication::translate("ChatGui", "Rise rate (x/sec):"), m_compressorRiseRateSlider, m_compressorRiseRateLabel,
|
||||
2, 10, 1);
|
||||
|
||||
auto connectSlider = [this](QSlider* slider) {
|
||||
@@ -464,7 +465,7 @@ void AudioDeviceSettingsPage::buildCompressorUI(QVBoxLayout* layout) {
|
||||
|
||||
void AudioDeviceSettingsPage::updateCompressorLabels() {
|
||||
if (m_compressorMaxGainLabel)
|
||||
m_compressorMaxGainLabel->setText(QString::number(m_compressorMaxGainSlider->value()) + " dB");
|
||||
m_compressorMaxGainLabel->setText(QString::number(m_compressorMaxGainSlider->value()) + QCoreApplication::translate("ChatGui", " dB"));
|
||||
if (m_compressorRiseRateLabel) {
|
||||
int v = m_compressorRiseRateSlider->value();
|
||||
m_compressorRiseRateLabel->setText(QString::number(v) + "x");
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <QCoreApplication>
|
||||
#include "callwindow.h"
|
||||
#include "../transport/gui_bridge.h"
|
||||
#include "call_audio_engine.h"
|
||||
@@ -136,7 +137,7 @@ static QWidget* wrapWithCaption(QWidget* btn, const QString& caption, QLabel** c
|
||||
|
||||
CallWindow::CallWindow(QWidget* parent)
|
||||
: QWidget(parent, Qt::Window) {
|
||||
setWindowTitle(QStringLiteral("Звонок"));
|
||||
setWindowTitle(QCoreApplication::translate("ChatGui", "Call"));
|
||||
setFixedSize(360, 480);
|
||||
setAttribute(Qt::WA_DeleteOnClose, false);
|
||||
|
||||
@@ -171,10 +172,10 @@ CallWindow::CallWindow(QWidget* parent)
|
||||
m_statsLabel->setStyleSheet("color: gray; font-size: 11px;");
|
||||
m_statsLabel->hide();
|
||||
|
||||
m_acceptBtn = makeRoundBtn(makeRoundIcon(QColor("#27ae60"), Glyph::Phone, 96), QStringLiteral("Принять"), this);
|
||||
m_declineBtn = makeRoundBtn(makeRoundIcon(QColor("#e74c3c"), Glyph::PhoneDown, 96), QStringLiteral("Отклонить"), this);
|
||||
m_hangupBtn = makeRoundBtn(makeRoundIcon(QColor("#e74c3c"), Glyph::PhoneDown, 96), QStringLiteral("Завершить"), this);
|
||||
m_muteBtn = makeRoundBtn(makeRoundIcon(QColor("#7f8c8d"), Glyph::Mic, 96), QStringLiteral("Mute"), this);
|
||||
m_acceptBtn = makeRoundBtn(makeRoundIcon(QColor("#27ae60"), Glyph::Phone, 96), QCoreApplication::translate("ChatGui", "Accept"), this);
|
||||
m_declineBtn = makeRoundBtn(makeRoundIcon(QColor("#e74c3c"), Glyph::PhoneDown, 96), QCoreApplication::translate("ChatGui", "Decline"), this);
|
||||
m_hangupBtn = makeRoundBtn(makeRoundIcon(QColor("#e74c3c"), Glyph::PhoneDown, 96), QCoreApplication::translate("ChatGui", "End call"), this);
|
||||
m_muteBtn = makeRoundBtn(makeRoundIcon(QColor("#7f8c8d"), Glyph::Mic, 96), QCoreApplication::translate("ChatGui", "Mute"), this);
|
||||
m_muteBtn->setCheckable(true);
|
||||
|
||||
m_deviceCombo = new QComboBox(this);
|
||||
@@ -194,10 +195,10 @@ CallWindow::CallWindow(QWidget* parent)
|
||||
lay->addWidget(m_statsLabel);
|
||||
lay->addStretch(1);
|
||||
|
||||
m_acceptCell = wrapWithCaption(m_acceptBtn, QStringLiteral("Принять"));
|
||||
m_declineCell = wrapWithCaption(m_declineBtn, QStringLiteral("Отклонить"));
|
||||
m_hangupCell = wrapWithCaption(m_hangupBtn, QStringLiteral("Завершить"));
|
||||
m_muteCell = wrapWithCaption(m_muteBtn, QStringLiteral("Mute"), &m_muteCaption);
|
||||
m_acceptCell = wrapWithCaption(m_acceptBtn, QCoreApplication::translate("ChatGui", "Accept"));
|
||||
m_declineCell = wrapWithCaption(m_declineBtn, QCoreApplication::translate("ChatGui", "Decline"));
|
||||
m_hangupCell = wrapWithCaption(m_hangupBtn, QCoreApplication::translate("ChatGui", "End call"));
|
||||
m_muteCell = wrapWithCaption(m_muteBtn, QCoreApplication::translate("ChatGui", "Mute"), &m_muteCaption);
|
||||
|
||||
QHBoxLayout* btn = new QHBoxLayout();
|
||||
btn->setSpacing(10);
|
||||
@@ -237,29 +238,29 @@ void CallWindow::setBgpPath(const QString& path) {
|
||||
}
|
||||
|
||||
void CallWindow::setConnectionType(uint8_t type, quint64 nextHop) {
|
||||
const QString names[] = { QStringLiteral("нет подключения"), QStringLiteral("прямое"),
|
||||
QStringLiteral("reverse"), QStringLiteral("indirect") };
|
||||
m_connectionLabel->setText(QStringLiteral("Подключение: %1").arg(names[type <= 3 ? type : 0]));
|
||||
m_connectionLabel->setToolTip(type == 3 ? QStringLiteral("Посредник: 0x%1").arg(nextHop, 16, 16, QChar('0')) : QString());
|
||||
const QString names[] = { QCoreApplication::translate("ChatGui", "not connected"), QCoreApplication::translate("ChatGui", "direct"),
|
||||
QCoreApplication::translate("ChatGui", "reverse"), QCoreApplication::translate("ChatGui", "indirect") };
|
||||
m_connectionLabel->setText(QCoreApplication::translate("ChatGui", "Connection: %1").arg(names[type <= 3 ? type : 0]));
|
||||
m_connectionLabel->setToolTip(type == 3 ? QCoreApplication::translate("ChatGui", "Relay: 0x%1").arg(nextHop, 16, 16, QChar('0')) : QString());
|
||||
}
|
||||
|
||||
void CallWindow::setState(State s) {
|
||||
m_state = s;
|
||||
switch (s) {
|
||||
case Outgoing:
|
||||
m_statusLabel->setText(QStringLiteral("Пытаемся связаться…"));
|
||||
m_statusLabel->setText(QCoreApplication::translate("ChatGui", "Connecting…"));
|
||||
m_durationLabel->hide();
|
||||
break;
|
||||
case Ringing:
|
||||
m_statusLabel->setText(QStringLiteral("Звоним…"));
|
||||
m_statusLabel->setText(QCoreApplication::translate("ChatGui", "Calling…"));
|
||||
m_durationLabel->hide();
|
||||
break;
|
||||
case Incoming:
|
||||
m_statusLabel->setText(QStringLiteral("Входящий звонок…"));
|
||||
m_statusLabel->setText(QCoreApplication::translate("ChatGui", "Incoming call…"));
|
||||
m_durationLabel->hide();
|
||||
break;
|
||||
case Active:
|
||||
m_statusLabel->setText(QStringLiteral("Разговор"));
|
||||
m_statusLabel->setText(QCoreApplication::translate("ChatGui", "In call"));
|
||||
m_durationLabel->setText(QStringLiteral("00:00"));
|
||||
m_durationLabel->show();
|
||||
m_startMs = QDateTime::currentMSecsSinceEpoch();
|
||||
@@ -269,7 +270,7 @@ void CallWindow::setState(State s) {
|
||||
break;
|
||||
case Ended:
|
||||
setConnectionType(0, 0);
|
||||
m_statusLabel->setText(QStringLiteral("Завершён"));
|
||||
m_statusLabel->setText(QCoreApplication::translate("ChatGui", "Ended"));
|
||||
m_durationTimer->stop();
|
||||
break;
|
||||
}
|
||||
@@ -279,11 +280,11 @@ void CallWindow::setState(State s) {
|
||||
void CallWindow::populateDeviceCombo() {
|
||||
m_deviceCombo->blockSignals(true);
|
||||
m_deviceCombo->clear();
|
||||
m_deviceCombo->addItem(QStringLiteral("Default (auto)"), QString());
|
||||
m_deviceCombo->addItem(QCoreApplication::translate("ChatGui", "Default (auto)"), QString());
|
||||
const auto devs = SoundManager::enumeratePlaybackDevices();
|
||||
for (int i = 0; i < devs.size(); i++) {
|
||||
QString label = devs[i].name;
|
||||
if (devs[i].isDefault) label += QStringLiteral(" (default)");
|
||||
if (devs[i].isDefault) label += QCoreApplication::translate("ChatGui", " (default)");
|
||||
m_deviceCombo->addItem(label, devs[i].id);
|
||||
}
|
||||
QString cur = CallAudioEngine::instance()->playbackDevice();
|
||||
@@ -328,7 +329,7 @@ void CallWindow::onMuteToggled(bool checked) {
|
||||
m_muteBtn->setIcon(makeRoundIcon(checked ? QColor("#e74c3c") : QColor("#7f8c8d"),
|
||||
checked ? Glyph::MicOff : Glyph::Mic, 96));
|
||||
if (m_muteCaption)
|
||||
m_muteCaption->setText(checked ? QStringLiteral("Вкл. микрофон") : QStringLiteral("Mute"));
|
||||
m_muteCaption->setText(checked ? QCoreApplication::translate("ChatGui", "Unmute") : QCoreApplication::translate("ChatGui", "Mute"));
|
||||
}
|
||||
|
||||
void CallWindow::onDeviceChanged(int index) {
|
||||
@@ -354,9 +355,9 @@ void CallWindow::updateDuration() {
|
||||
|
||||
void CallWindow::setAudioStats(int rtt, int buffer, int tempo, int dropped, int underruns,
|
||||
int minimum, int maximum, int reserve) {
|
||||
m_statsLabel->setText(QStringLiteral("Буфер: %1 мс · Запас: %2 мс\n"
|
||||
"За 10 с: минимум %3 · максимум %4 мс\n"
|
||||
"Скорость: %5× · RTT: %6 мс\nПотери буфера: %7 · Недостачи: %8")
|
||||
m_statsLabel->setText(QCoreApplication::translate("ChatGui",
|
||||
"Buffer: %1 ms · Reserve: %2 ms\nOver 10 s: minimum %3 · maximum %4 ms\nSpeed: %5× · RTT: %6 "
|
||||
"ms\nBuffer drops: %7 · Underruns: %8")
|
||||
.arg(buffer).arg(reserve).arg(minimum).arg(maximum)
|
||||
.arg(tempo / 100.0, 0, 'f', 2).arg(rtt).arg(dropped).arg(underruns));
|
||||
m_statsLabel->show();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <QCoreApplication>
|
||||
#include "channellist.h"
|
||||
#include "channeldelegate.h"
|
||||
#include "../db/db_manager.h"
|
||||
@@ -146,7 +147,7 @@ ChannelList::ChannelList(DbManager* db, QWidget *parent)
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->setSpacing(0);
|
||||
|
||||
auto *header = new QLabel("Channels", this);
|
||||
auto *header = new QLabel(QCoreApplication::translate("ChatGui", "Channels"), this);
|
||||
header->setAlignment(Qt::AlignCenter);
|
||||
header->setStyleSheet("font-weight: bold; padding: 8px; background: palette(window);");
|
||||
layout->addWidget(header);
|
||||
@@ -167,7 +168,7 @@ ChannelList::ChannelList(DbManager* db, QWidget *parent)
|
||||
|
||||
auto *gearBtn = new QPushButton(QString::fromUtf8("\xE2\x9A\x99"), footer);
|
||||
gearBtn->setFixedSize(28, 28);
|
||||
gearBtn->setToolTip(QString::fromUtf8("Настройки"));
|
||||
gearBtn->setToolTip(QCoreApplication::translate("ChatGui", "Settings"));
|
||||
gearBtn->setStyleSheet(
|
||||
"QPushButton { border: none; font-size: 18px; background: transparent; color: #666; }"
|
||||
"QPushButton:hover { color: #3390EC; background: rgba(51,144,236,20); border-radius: 14px; }");
|
||||
@@ -177,7 +178,7 @@ ChannelList::ChannelList(DbManager* db, QWidget *parent)
|
||||
|
||||
auto *monitorBtn = new QPushButton(QString::fromUtf8("\xF0\x9F\x94\x8D"), footer); /* 🔍 */
|
||||
monitorBtn->setFixedSize(28, 28);
|
||||
monitorBtn->setToolTip(QString::fromUtf8("Connection Monitor"));
|
||||
monitorBtn->setToolTip(QCoreApplication::translate("ChatGui", "Connection Monitor"));
|
||||
monitorBtn->setStyleSheet(
|
||||
"QPushButton { border: none; font-size: 16px; background: transparent; color: #666; }"
|
||||
"QPushButton:hover { color: #3390EC; background: rgba(51,144,236,20); border-radius: 14px; }");
|
||||
@@ -186,9 +187,9 @@ ChannelList::ChannelList(DbManager* db, QWidget *parent)
|
||||
|
||||
fl->addStretch();
|
||||
|
||||
auto *createBtn = new QPushButton("GRP", footer);
|
||||
auto *createBtn = new QPushButton(QCoreApplication::translate("ChatGui", "GRP"), footer);
|
||||
createBtn->setFixedSize(36, 28);
|
||||
createBtn->setToolTip(QString::fromUtf8("Создать группу"));
|
||||
createBtn->setToolTip(QCoreApplication::translate("ChatGui", "Create group"));
|
||||
createBtn->setStyleSheet(
|
||||
"QPushButton { border: none; border-radius: 6px; font-size: 11px; font-weight: bold;"
|
||||
" color: white; background: #27AE60; }"
|
||||
@@ -198,7 +199,7 @@ ChannelList::ChannelList(DbManager* db, QWidget *parent)
|
||||
|
||||
auto *plusBtn = new QPushButton("+", footer);
|
||||
plusBtn->setFixedSize(28, 28);
|
||||
plusBtn->setToolTip(QString::fromUtf8("Присоединиться"));
|
||||
plusBtn->setToolTip(QCoreApplication::translate("ChatGui", "Join"));
|
||||
plusBtn->setStyleSheet(
|
||||
"QPushButton { border: none; border-radius: 14px; font-size: 18px; font-weight: bold;"
|
||||
" color: white; background: #3390EC; }"
|
||||
@@ -294,7 +295,7 @@ void ChannelList::loadChannels() {
|
||||
auto dms = m_db->getDmConversations();
|
||||
if (!dms.isEmpty()) {
|
||||
auto *section = new QStandardItem();
|
||||
section->setData(QStringLiteral("Личные"), ChannelNameRole);
|
||||
section->setData(QCoreApplication::translate("ChatGui", "Direct messages"), ChannelNameRole);
|
||||
section->setData(true, ChannelIsSectionRole);
|
||||
section->setFlags(Qt::NoItemFlags);
|
||||
m_model->appendRow(section);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <QCoreApplication>
|
||||
#include "channelsettingsdialog.h"
|
||||
#include "../db/db_manager.h"
|
||||
#include "../transport/gui_bridge.h"
|
||||
@@ -16,7 +17,7 @@ ChannelSettingsDialog::ChannelSettingsDialog(const QString& channelId, DbManager
|
||||
, m_channelId(channelId)
|
||||
, m_db(db)
|
||||
{
|
||||
setWindowTitle(QStringLiteral("Channel Settings"));
|
||||
setWindowTitle(QCoreApplication::translate("ChatGui", "Channel Settings"));
|
||||
setMinimumWidth(340);
|
||||
setModal(true);
|
||||
|
||||
@@ -24,7 +25,7 @@ ChannelSettingsDialog::ChannelSettingsDialog(const QString& channelId, DbManager
|
||||
layout->setContentsMargins(20, 16, 20, 16);
|
||||
layout->setSpacing(12);
|
||||
|
||||
auto* titleLabel = new QLabel(QStringLiteral("Settings for channel"), this);
|
||||
auto* titleLabel = new QLabel(QCoreApplication::translate("ChatGui", "Settings for channel"), this);
|
||||
titleLabel->setStyleSheet("font-weight: bold; font-size: 14px;");
|
||||
layout->addWidget(titleLabel);
|
||||
|
||||
@@ -35,16 +36,18 @@ ChannelSettingsDialog::ChannelSettingsDialog(const QString& channelId, DbManager
|
||||
|
||||
layout->addSpacing(8);
|
||||
|
||||
m_autoPlayVoice = new QCheckBox(QStringLiteral("Auto-play voice messages on receive"), this);
|
||||
m_autoPlayVoice->setToolTip(QStringLiteral("Voice messages will play automatically when received. Multiple messages queue up and play sequentially without interrupting current playback."));
|
||||
m_autoPlayVoice = new QCheckBox(QCoreApplication::translate("ChatGui", "Auto-play voice messages on receive"), this);
|
||||
m_autoPlayVoice->setToolTip(QCoreApplication::translate("ChatGui",
|
||||
"Voice messages will play automatically when received. Multiple messages queue up and play "
|
||||
"sequentially without interrupting current playback."));
|
||||
layout->addWidget(m_autoPlayVoice);
|
||||
|
||||
m_soundOnMessage = new QCheckBox(QStringLiteral("Play sound on incoming message"), this);
|
||||
m_soundOnMessage->setToolTip(QStringLiteral("Play a notification sound when a message arrives in this channel."));
|
||||
m_soundOnMessage = new QCheckBox(QCoreApplication::translate("ChatGui", "Play sound on incoming message"), this);
|
||||
m_soundOnMessage->setToolTip(QCoreApplication::translate("ChatGui", "Play a notification sound when a message arrives in this channel."));
|
||||
layout->addWidget(m_soundOnMessage);
|
||||
|
||||
m_pttMode = new QCheckBox(QStringLiteral("Push-To-Talk (PTT) mode"), this);
|
||||
m_pttMode->setToolTip(QStringLiteral("Record voice messages with push-to-talk. (Coming soon)"));
|
||||
m_pttMode = new QCheckBox(QCoreApplication::translate("ChatGui", "Push-To-Talk (PTT) mode"), this);
|
||||
m_pttMode->setToolTip(QCoreApplication::translate("ChatGui", "Record voice messages with push-to-talk. (Coming soon)"));
|
||||
m_pttMode->setEnabled(false);
|
||||
layout->addWidget(m_pttMode);
|
||||
|
||||
@@ -54,8 +57,8 @@ ChannelSettingsDialog::ChannelSettingsDialog(const QString& channelId, DbManager
|
||||
btnLayout->setSpacing(8);
|
||||
btnLayout->addStretch();
|
||||
|
||||
auto* cancelBtn = new QPushButton(QStringLiteral("Cancel"), this);
|
||||
cancelBtn->setFixedWidth(90);
|
||||
auto* cancelBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Cancel"), this);
|
||||
cancelBtn->setMinimumWidth(90);
|
||||
cancelBtn->setStyleSheet(
|
||||
"QPushButton { border: 1px solid palette(mid); border-radius: 4px;"
|
||||
" padding: 6px 16px; font-size: 13px; background: palette(button); }"
|
||||
@@ -63,8 +66,8 @@ ChannelSettingsDialog::ChannelSettingsDialog(const QString& channelId, DbManager
|
||||
connect(cancelBtn, &QPushButton::clicked, this, &QDialog::reject);
|
||||
btnLayout->addWidget(cancelBtn);
|
||||
|
||||
auto* saveBtn = new QPushButton(QStringLiteral("Save"), this);
|
||||
saveBtn->setFixedWidth(90);
|
||||
auto* saveBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Save"), this);
|
||||
saveBtn->setMinimumWidth(90);
|
||||
saveBtn->setDefault(true);
|
||||
saveBtn->setStyleSheet(
|
||||
"QPushButton { border: none; border-radius: 4px;"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <QCoreApplication>
|
||||
#include "chatview.h"
|
||||
#include "messagedelegate.h"
|
||||
#include "emoji.h"
|
||||
@@ -44,7 +45,7 @@ ChatView::ChatView(QWidget *parent)
|
||||
" background: rgba(90,90,90,220);"
|
||||
"}"
|
||||
);
|
||||
m_scrollDownBtn->setToolTip(tr("Scroll to bottom"));
|
||||
m_scrollDownBtn->setToolTip(QCoreApplication::translate("ChatGui", "Scroll to bottom"));
|
||||
m_scrollDownBtn->setVisible(false);
|
||||
|
||||
connect(m_scrollDownBtn, &QPushButton::clicked, this, [this]() {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <QCoreApplication>
|
||||
// connmonitorwindow.cpp — ETCP connection monitor (standalone, non-modal window)
|
||||
#include "connmonitorwindow.h"
|
||||
#include "../transport/gui_bridge.h"
|
||||
@@ -31,7 +32,7 @@ static void connMetricsCb(const uint8_t* data, int len) {
|
||||
ConnMonitorWindow::ConnMonitorWindow(QWidget* parent)
|
||||
: QDialog(parent)
|
||||
{
|
||||
setWindowTitle("Connection Monitor");
|
||||
setWindowTitle(QCoreApplication::translate("ChatGui", "Connection Monitor"));
|
||||
resize(900, 550);
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
@@ -40,7 +41,7 @@ ConnMonitorWindow::ConnMonitorWindow(QWidget* parent)
|
||||
mainLayout->setSpacing(4);
|
||||
mainLayout->setContentsMargins(8, 8, 8, 8);
|
||||
|
||||
auto* title = new QLabel("Active ETCP Connections (10 fps)", this);
|
||||
auto* title = new QLabel(QCoreApplication::translate("ChatGui", "Active ETCP Connections (10 fps)"), this);
|
||||
title->setStyleSheet("font-weight: bold; font-size: 13px;");
|
||||
mainLayout->addWidget(title);
|
||||
|
||||
@@ -63,7 +64,7 @@ ConnMonitorWindow::ConnMonitorWindow(QWidget* parent)
|
||||
m_details->setStyleSheet(
|
||||
"QPlainTextEdit { border: 1px solid palette(mid); border-radius: 4px;"
|
||||
" padding: 8px; background: palette(base); }");
|
||||
m_details->setPlaceholderText("Select a connection to view metrics");
|
||||
m_details->setPlaceholderText(QCoreApplication::translate("ChatGui", "Select a connection to view metrics"));
|
||||
|
||||
splitter->addWidget(m_connList);
|
||||
splitter->addWidget(m_details);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <QCoreApplication>
|
||||
#include "creategroupdialog.h"
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
@@ -8,16 +9,16 @@ CreateGroupDialog::CreateGroupDialog(QWidget* parent)
|
||||
: QDialog(parent)
|
||||
, m_nameEdit(new QLineEdit(this))
|
||||
{
|
||||
setWindowTitle("Создать группу");
|
||||
setWindowTitle(QCoreApplication::translate("ChatGui", "Create group"));
|
||||
setMinimumWidth(350);
|
||||
setModal(true);
|
||||
|
||||
auto* layout = new QVBoxLayout(this);
|
||||
|
||||
auto* nameLabel = new QLabel("Название группы:", this);
|
||||
auto* nameLabel = new QLabel(QCoreApplication::translate("ChatGui", "Group name:"), this);
|
||||
layout->addWidget(nameLabel);
|
||||
|
||||
m_nameEdit->setPlaceholderText("Введите название...");
|
||||
m_nameEdit->setPlaceholderText(QCoreApplication::translate("ChatGui", "Enter a name..."));
|
||||
m_nameEdit->setMaxLength(128);
|
||||
layout->addWidget(m_nameEdit);
|
||||
|
||||
@@ -26,11 +27,11 @@ CreateGroupDialog::CreateGroupDialog(QWidget* parent)
|
||||
auto* btnLayout = new QHBoxLayout();
|
||||
btnLayout->addStretch();
|
||||
|
||||
auto* cancelBtn = new QPushButton("Отмена", this);
|
||||
auto* cancelBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Cancel"), this);
|
||||
connect(cancelBtn, &QPushButton::clicked, this, &QDialog::reject);
|
||||
btnLayout->addWidget(cancelBtn);
|
||||
|
||||
auto* createBtn = new QPushButton("Создать", this);
|
||||
auto* createBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Create"), this);
|
||||
createBtn->setDefault(true);
|
||||
connect(createBtn, &QPushButton::clicked, this, [this]() {
|
||||
if (m_nameEdit->text().trimmed().isEmpty()) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <QCoreApplication>
|
||||
// databasesettingspage.cpp — Database maintenance page in settings
|
||||
#include "databasesettingspage.h"
|
||||
#include "../db/db_manager.h"
|
||||
@@ -17,7 +18,7 @@ DatabaseSettingsPage::DatabaseSettingsPage(DbManager* db, QWidget* parent)
|
||||
layout->setSpacing(10);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
auto* title = new QLabel("Database Maintenance", this);
|
||||
auto* title = new QLabel(QCoreApplication::translate("ChatGui", "Database Maintenance"), this);
|
||||
title->setStyleSheet("font-weight: bold; font-size: 13px;");
|
||||
layout->addWidget(title);
|
||||
|
||||
@@ -35,29 +36,29 @@ DatabaseSettingsPage::DatabaseSettingsPage(DbManager* db, QWidget* parent)
|
||||
QFileInfo fi(dbPath);
|
||||
if (fi.exists()) {
|
||||
qint64 sz = fi.size();
|
||||
if (sz < 1024) dbSize = QString("%1 B").arg(sz);
|
||||
else if (sz < 1024 * 1024) dbSize = QString("%1 KB").arg(sz / 1024);
|
||||
else dbSize = QString("%1 MB").arg(sz / (1024.0 * 1024.0), 0, 'f', 2);
|
||||
if (sz < 1024) dbSize = QCoreApplication::translate("ChatGui", "%1 B").arg(sz);
|
||||
else if (sz < 1024 * 1024) dbSize = QCoreApplication::translate("ChatGui", "%1 KB").arg(sz / 1024);
|
||||
else dbSize = QCoreApplication::translate("ChatGui", "%1 MB").arg(sz / (1024.0 * 1024.0), 0, 'f', 2);
|
||||
}
|
||||
QFileInfo walFi(dbPath + "-wal");
|
||||
if (walFi.exists()) {
|
||||
qint64 wsz = walFi.size();
|
||||
if (wsz < 1024) walSize = QString("%1 B").arg(wsz);
|
||||
else if (wsz < 1024 * 1024) walSize = QString("%1 KB").arg(wsz / 1024);
|
||||
else walSize = QString("%1 MB").arg(wsz / (1024.0 * 1024.0), 0, 'f', 2);
|
||||
if (wsz < 1024) walSize = QCoreApplication::translate("ChatGui", "%1 B").arg(wsz);
|
||||
else if (wsz < 1024 * 1024) walSize = QCoreApplication::translate("ChatGui", "%1 KB").arg(wsz / 1024);
|
||||
else walSize = QCoreApplication::translate("ChatGui", "%1 MB").arg(wsz / (1024.0 * 1024.0), 0, 'f', 2);
|
||||
} else {
|
||||
walSize = "n/a";
|
||||
walSize = QCoreApplication::translate("ChatGui", "n/a");
|
||||
}
|
||||
}
|
||||
}
|
||||
m_filePathLabel = new QLabel(dbPath, this);
|
||||
m_filePathLabel->setStyleSheet("font-size: 11px; color: gray;");
|
||||
m_filePathLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
||||
infoLayout->addRow("File:", m_filePathLabel);
|
||||
infoLayout->addRow(QCoreApplication::translate("ChatGui", "File:"), m_filePathLabel);
|
||||
|
||||
m_fileSizeLabel = new QLabel(dbSize, this);
|
||||
m_fileSizeLabel->setStyleSheet("font-size: 11px;");
|
||||
infoLayout->addRow("Size:", m_fileSizeLabel);
|
||||
infoLayout->addRow(QCoreApplication::translate("ChatGui", "Size:"), m_fileSizeLabel);
|
||||
|
||||
m_walSizeLabel = new QLabel(walSize, this);
|
||||
m_walSizeLabel->setStyleSheet("font-size: 11px;");
|
||||
@@ -76,34 +77,34 @@ DatabaseSettingsPage::DatabaseSettingsPage(DbManager* db, QWidget* parent)
|
||||
btnLayout->setSpacing(6);
|
||||
|
||||
auto* quickRow = new QHBoxLayout();
|
||||
m_quickBtn = new QPushButton("Quick Check", this);
|
||||
m_quickBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Quick Check"), this);
|
||||
m_quickBtn->setStyleSheet(
|
||||
"QPushButton { border: 1px solid palette(mid); border-radius: 4px;"
|
||||
" padding: 5px 14px; font-size: 12px; background: palette(button); }"
|
||||
"QPushButton:hover { background: palette(light); }");
|
||||
connect(m_quickBtn, &QPushButton::clicked, this, &DatabaseSettingsPage::onQuickCheck);
|
||||
quickRow->addWidget(m_quickBtn);
|
||||
auto* quickDesc = new QLabel("Scans structure without verifying indexes (fast)", this);
|
||||
auto* quickDesc = new QLabel(QCoreApplication::translate("ChatGui", "Scans structure without verifying indexes (fast)"), this);
|
||||
quickDesc->setStyleSheet("color: gray; font-size: 11px;");
|
||||
quickRow->addWidget(quickDesc, 1);
|
||||
btnLayout->addLayout(quickRow);
|
||||
|
||||
auto* fullRow = new QHBoxLayout();
|
||||
m_fullBtn = new QPushButton("Full Check", this);
|
||||
m_fullBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Full Check"), this);
|
||||
m_fullBtn->setStyleSheet(m_quickBtn->styleSheet());
|
||||
connect(m_fullBtn, &QPushButton::clicked, this, &DatabaseSettingsPage::onFullCheck);
|
||||
fullRow->addWidget(m_fullBtn);
|
||||
auto* fullDesc = new QLabel("Verifies all data structures and indexes (thorough)", this);
|
||||
auto* fullDesc = new QLabel(QCoreApplication::translate("ChatGui", "Verifies all data structures and indexes (thorough)"), this);
|
||||
fullDesc->setStyleSheet("color: gray; font-size: 11px;");
|
||||
fullRow->addWidget(fullDesc, 1);
|
||||
btnLayout->addLayout(fullRow);
|
||||
|
||||
auto* optRow = new QHBoxLayout();
|
||||
m_optimizeBtn = new QPushButton("Optimize", this);
|
||||
m_optimizeBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Optimize"), this);
|
||||
m_optimizeBtn->setStyleSheet(m_quickBtn->styleSheet());
|
||||
connect(m_optimizeBtn, &QPushButton::clicked, this, &DatabaseSettingsPage::onOptimize);
|
||||
optRow->addWidget(m_optimizeBtn);
|
||||
auto* optDesc = new QLabel("Update query planner statistics for better performance", this);
|
||||
auto* optDesc = new QLabel(QCoreApplication::translate("ChatGui", "Update query planner statistics for better performance"), this);
|
||||
optDesc->setStyleSheet("color: gray; font-size: 11px;");
|
||||
optRow->addWidget(optDesc, 1);
|
||||
btnLayout->addLayout(optRow);
|
||||
@@ -123,13 +124,13 @@ DatabaseSettingsPage::DatabaseSettingsPage(DbManager* db, QWidget* parent)
|
||||
"QPlainTextEdit { font-family: monospace; font-size: 11px;"
|
||||
" background: palette(base); border: 1px solid palette(mid); border-radius: 4px;"
|
||||
" padding: 4px; }");
|
||||
m_resultText->setPlaceholderText("Run a check to see results...");
|
||||
m_resultText->setPlaceholderText(QCoreApplication::translate("ChatGui", "Run a check to see results..."));
|
||||
layout->addWidget(m_resultText);
|
||||
|
||||
layout->addStretch();
|
||||
|
||||
if (!m_db || !m_db->m_db) {
|
||||
m_statusLabel->setText("Database not connected");
|
||||
m_statusLabel->setText(QCoreApplication::translate("ChatGui", "Database not connected"));
|
||||
m_statusLabel->setStyleSheet("font-size: 12px; font-weight: bold; color: red;");
|
||||
m_quickBtn->setEnabled(false);
|
||||
m_fullBtn->setEnabled(false);
|
||||
@@ -140,7 +141,7 @@ DatabaseSettingsPage::DatabaseSettingsPage(DbManager* db, QWidget* parent)
|
||||
|
||||
void DatabaseSettingsPage::refreshStatus() {
|
||||
if (!m_db || !m_db->m_db) {
|
||||
m_statusLabel->setText("Database not connected");
|
||||
m_statusLabel->setText(QCoreApplication::translate("ChatGui", "Database not connected"));
|
||||
m_statusLabel->setStyleSheet("font-size: 12px; font-weight: bold; color: red;");
|
||||
m_quickBtn->setEnabled(false);
|
||||
m_fullBtn->setEnabled(false);
|
||||
@@ -157,16 +158,16 @@ void DatabaseSettingsPage::refreshStatus() {
|
||||
QFileInfo fi(dbPath);
|
||||
if (fi.exists()) {
|
||||
qint64 sz = fi.size();
|
||||
if (sz < 1024) m_fileSizeLabel->setText(QString("%1 B").arg(sz));
|
||||
else if (sz < 1024 * 1024) m_fileSizeLabel->setText(QString("%1 KB").arg(sz / 1024));
|
||||
else m_fileSizeLabel->setText(QString("%1 MB").arg(sz / (1024.0 * 1024.0), 0, 'f', 2));
|
||||
if (sz < 1024) m_fileSizeLabel->setText(QCoreApplication::translate("ChatGui", "%1 B").arg(sz));
|
||||
else if (sz < 1024 * 1024) m_fileSizeLabel->setText(QCoreApplication::translate("ChatGui", "%1 KB").arg(sz / 1024));
|
||||
else m_fileSizeLabel->setText(QCoreApplication::translate("ChatGui", "%1 MB").arg(sz / (1024.0 * 1024.0), 0, 'f', 2));
|
||||
QFileInfo walFi(dbPath + "-wal");
|
||||
if (walFi.exists()) {
|
||||
qint64 wsz = walFi.size();
|
||||
if (wsz < 1024) m_walSizeLabel->setText(QString("%1 B").arg(wsz));
|
||||
else if (wsz < 1024 * 1024) m_walSizeLabel->setText(QString("%1 KB").arg(wsz / 1024));
|
||||
else m_walSizeLabel->setText(QString("%1 MB").arg(wsz / (1024.0 * 1024.0), 0, 'f', 2));
|
||||
} else { m_walSizeLabel->setText("n/a"); }
|
||||
if (wsz < 1024) m_walSizeLabel->setText(QCoreApplication::translate("ChatGui", "%1 B").arg(wsz));
|
||||
else if (wsz < 1024 * 1024) m_walSizeLabel->setText(QCoreApplication::translate("ChatGui", "%1 KB").arg(wsz / 1024));
|
||||
else m_walSizeLabel->setText(QCoreApplication::translate("ChatGui", "%1 MB").arg(wsz / (1024.0 * 1024.0), 0, 'f', 2));
|
||||
} else { m_walSizeLabel->setText(QCoreApplication::translate("ChatGui", "n/a")); }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -176,47 +177,47 @@ void DatabaseSettingsPage::setBusy(bool busy) {
|
||||
m_fullBtn->setEnabled(!busy);
|
||||
m_optimizeBtn->setEnabled(!busy);
|
||||
if (busy) {
|
||||
m_statusLabel->setText("Checking...");
|
||||
m_statusLabel->setText(QCoreApplication::translate("ChatGui", "Checking..."));
|
||||
m_statusLabel->setStyleSheet("font-size: 12px; margin-top: 4px; color: gray;");
|
||||
}
|
||||
}
|
||||
|
||||
void DatabaseSettingsPage::onQuickCheck() {
|
||||
if (!m_db || !m_db->m_db) {
|
||||
m_statusLabel->setText("Database not open");
|
||||
m_statusLabel->setText(QCoreApplication::translate("ChatGui", "Database not open"));
|
||||
m_statusLabel->setStyleSheet("font-size: 12px; color: red;");
|
||||
return;
|
||||
}
|
||||
setBusy(true);
|
||||
QTimer::singleShot(50, this, [this]() {
|
||||
IntegrityResult r = m_db->integrityCheckQuick();
|
||||
showResult("Quick Check", r);
|
||||
showResult(QCoreApplication::translate("ChatGui", "Quick Check"), r);
|
||||
});
|
||||
}
|
||||
|
||||
void DatabaseSettingsPage::onFullCheck() {
|
||||
if (!m_db || !m_db->m_db) {
|
||||
m_statusLabel->setText("Database not open");
|
||||
m_statusLabel->setText(QCoreApplication::translate("ChatGui", "Database not open"));
|
||||
m_statusLabel->setStyleSheet("font-size: 12px; color: red;");
|
||||
return;
|
||||
}
|
||||
setBusy(true);
|
||||
QTimer::singleShot(50, this, [this]() {
|
||||
IntegrityResult r = m_db->integrityCheckFull();
|
||||
showResult("Full Check", r);
|
||||
showResult(QCoreApplication::translate("ChatGui", "Full Check"), r);
|
||||
});
|
||||
}
|
||||
|
||||
void DatabaseSettingsPage::onOptimize() {
|
||||
if (!m_db || !m_db->m_db) {
|
||||
m_statusLabel->setText("Database not open");
|
||||
m_statusLabel->setText(QCoreApplication::translate("ChatGui", "Database not open"));
|
||||
m_statusLabel->setStyleSheet("font-size: 12px; color: red;");
|
||||
return;
|
||||
}
|
||||
setBusy(true);
|
||||
QTimer::singleShot(50, this, [this]() {
|
||||
IntegrityResult r = m_db->integrityOptimize();
|
||||
showResult("Optimize", r);
|
||||
showResult(QCoreApplication::translate("ChatGui", "Optimize"), r);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -226,11 +227,11 @@ void DatabaseSettingsPage::showResult(const QString& checkName, const IntegrityR
|
||||
m_optimizeBtn->setEnabled(true);
|
||||
|
||||
if (result.ok) {
|
||||
m_statusLabel->setText(QString("%1: OK (%2 ms)")
|
||||
m_statusLabel->setText(QCoreApplication::translate("ChatGui", "%1: OK (%2 ms)")
|
||||
.arg(checkName).arg(result.elapsedMs));
|
||||
m_statusLabel->setStyleSheet("font-size: 12px; font-weight: bold; color: green;");
|
||||
} else {
|
||||
m_statusLabel->setText(QString("%1: FAILED (%2 ms)")
|
||||
m_statusLabel->setText(QCoreApplication::translate("ChatGui", "%1: FAILED (%2 ms)")
|
||||
.arg(checkName).arg(result.elapsedMs));
|
||||
m_statusLabel->setStyleSheet("font-size: 12px; font-weight: bold; color: red;");
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <QCoreApplication>
|
||||
#include "dminvitedialog.h"
|
||||
#include "debug_ui.h"
|
||||
#include "../db/db_manager.h"
|
||||
@@ -18,28 +19,29 @@ extern "C" {
|
||||
|
||||
DmInviteDialog::DmInviteDialog(DbManager* db, quint64 peer, const QString& name, const QString& source, QWidget* parent)
|
||||
: QDialog(parent), m_peer(peer), m_name(name), m_source(source) {
|
||||
setWindowTitle(QStringLiteral("Пригласить в группу"));
|
||||
setWindowTitle(QCoreApplication::translate("ChatGui", "Invite to group"));
|
||||
setMinimumWidth(400);
|
||||
auto* layout = new QVBoxLayout(this);
|
||||
layout->addWidget(new QLabel(QStringLiteral("Получатель: %1").arg(name), this));
|
||||
layout->addWidget(new QLabel(QCoreApplication::translate("ChatGui", "Recipient: %1").arg(name), this));
|
||||
m_groups = new QComboBox(this);
|
||||
for (const auto& group : db->getChannels()) {
|
||||
if (db->isChannelMember(group.channelId, db->myNodeId())) m_groups->addItem(group.name, group.channelId);
|
||||
}
|
||||
m_groups->setCurrentIndex(-1);
|
||||
m_groups->setPlaceholderText(QStringLiteral("Выберите группу"));
|
||||
m_groups->setPlaceholderText(QCoreApplication::translate("ChatGui", "Select a group"));
|
||||
layout->addWidget(m_groups);
|
||||
auto* expiry = new QLabel(QStringLiteral("Ссылка действует %1 минут с момента создания.\n"
|
||||
"При поздней доставке PM потребуется новое приглашение.").arg(JOIN_KEY_TTL_SECONDS / 60), this);
|
||||
auto* expiry = new QLabel(QCoreApplication::translate("ChatGui",
|
||||
"The link expires %1 minutes after creation.\nIf the direct message arrives late, a new "
|
||||
"invitation will be needed.").arg(JOIN_KEY_TTL_SECONDS / 60), this);
|
||||
expiry->setWordWrap(true);
|
||||
layout->addWidget(expiry);
|
||||
m_status = new QLabel(this);
|
||||
m_status->setWordWrap(true);
|
||||
if (m_groups->count() == 0) m_status->setText(QStringLiteral("Нет групп для приглашения."));
|
||||
if (m_groups->count() == 0) m_status->setText(QCoreApplication::translate("ChatGui", "No groups available to invite to."));
|
||||
layout->addWidget(m_status);
|
||||
auto* buttons = new QDialogButtonBox(QDialogButtonBox::Cancel, this);
|
||||
buttons->button(QDialogButtonBox::Cancel)->setText(QStringLiteral("Отмена"));
|
||||
m_send = buttons->addButton(QStringLiteral("Пригласить"), QDialogButtonBox::AcceptRole);
|
||||
buttons->button(QDialogButtonBox::Cancel)->setText(QCoreApplication::translate("ChatGui", "Cancel"));
|
||||
m_send = buttons->addButton(QCoreApplication::translate("ChatGui", "Invite"), QDialogButtonBox::AcceptRole);
|
||||
m_send->setEnabled(false);
|
||||
layout->addWidget(buttons);
|
||||
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
@@ -70,10 +72,11 @@ void DmInviteDialog::sendInvite() {
|
||||
if (m_pending || m_groups->currentIndex() < 0) return;
|
||||
if (!gui_bridge_is_uasync_ready() || !gui_bridge_get_inst()) {
|
||||
GUI_ERROR("invite PM: core unavailable");
|
||||
m_status->setText(QStringLiteral("Ядро не запущено.")); return;
|
||||
m_status->setText(QCoreApplication::translate("ChatGui", "The core is not running.")); return;
|
||||
}
|
||||
auto* req = (struct dm_invite_req*)u_calloc(1, sizeof(struct dm_invite_req));
|
||||
if (!req) { GUI_ERROR("invite PM: request allocation failed"); m_status->setText(QStringLiteral("Недостаточно памяти.")); return; }
|
||||
if (!req) { GUI_ERROR("invite PM: request allocation failed"); m_status->setText(QCoreApplication::translate("ChatGui",
|
||||
"Not enough memory.")); return; }
|
||||
static uint64_t nextRequestId = 0;
|
||||
req->inst = gui_bridge_get_inst(); req->request_id = m_requestId = ++nextRequestId; req->peer_node_id = m_peer;
|
||||
req->channel_id = m_groups->currentData().toString().toULongLong();
|
||||
@@ -81,7 +84,7 @@ void DmInviteDialog::sendInvite() {
|
||||
snprintf(req->peer_name, sizeof(req->peer_name), "%s", m_name.toUtf8().constData());
|
||||
m_pending = true;
|
||||
m_groups->setEnabled(false); m_send->setEnabled(false);
|
||||
m_status->setText(QStringLiteral("Создание и отправка приглашения…"));
|
||||
m_status->setText(QCoreApplication::translate("ChatGui", "Creating and sending invitation…"));
|
||||
gui_bridge_post_uasync_fn(dm_invite_trampoline, req);
|
||||
}
|
||||
|
||||
@@ -93,11 +96,11 @@ void DmInviteDialog::onResult(const uint8_t* data, int len) {
|
||||
if (!m_pending || request != m_requestId || peer != m_peer) return;
|
||||
m_pending = false;
|
||||
if (result == DM_INVITE_OK) { m_convId = QString::number(conv); accept(); return; }
|
||||
const QString error = result == DM_INVITE_START_ERROR ? QStringLiteral("Не удалось создать личную беседу.")
|
||||
: result == DM_INVITE_SEND_ERROR ? QStringLiteral("Не удалось отправить приглашение.")
|
||||
: result == DM_INVITE_BUSY ? QStringLiteral("Другое приглашение ещё создаётся. Повторите попытку.")
|
||||
: result == DM_INVITE_CANCELLED ? QStringLiteral("Создание приглашения отменено.")
|
||||
: QStringLiteral("Не удалось создать приглашение. Проверьте подключение и повторите попытку.");
|
||||
const QString error = result == DM_INVITE_START_ERROR ? QCoreApplication::translate("ChatGui", "Failed to create a direct conversation.")
|
||||
: result == DM_INVITE_SEND_ERROR ? QCoreApplication::translate("ChatGui", "Failed to send the invitation.")
|
||||
: result == DM_INVITE_BUSY ? QCoreApplication::translate("ChatGui", "Another invitation is still being created. Try again.")
|
||||
: result == DM_INVITE_CANCELLED ? QCoreApplication::translate("ChatGui", "Invitation creation cancelled.")
|
||||
: QCoreApplication::translate("ChatGui", "Failed to create an invitation. Check the connection and try again.");
|
||||
GUI_ERROR("invite PM dialog: failed request=%llu result=%d", (unsigned long long)request, result);
|
||||
m_status->setText(error); m_groups->setEnabled(true); m_send->setEnabled(m_groups->currentIndex() >= 0);
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <QCoreApplication>
|
||||
#include "emoji.h"
|
||||
|
||||
QHash<QString, QString> g_animatedEmojiMap;
|
||||
@@ -133,10 +134,10 @@ static QVector<EmojiData> animations() {
|
||||
|
||||
QVector<EmojiCategory> builtinEmojiSet() {
|
||||
return {
|
||||
{"Smileys & People", smileys()},
|
||||
{"Gestures", gestures()},
|
||||
{"Hearts & Symbols", hearts()},
|
||||
{"Animations", animations()},
|
||||
{QCoreApplication::translate("ChatGui", "Smileys & People"), smileys()},
|
||||
{QCoreApplication::translate("ChatGui", "Gestures"), gestures()},
|
||||
{QCoreApplication::translate("ChatGui", "Hearts & Symbols"), hearts()},
|
||||
{QCoreApplication::translate("ChatGui", "Animations"), animations()},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
/* Выбор языка интерфейса независимо от регионального формата дат и чисел. */
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QLocale>
|
||||
|
||||
inline QString resolveGuiLanguage(const QString& preference, const QStringList& systemLanguages = QLocale::system().uiLanguages()) {
|
||||
if (preference == QStringLiteral("en") || preference == QStringLiteral("ru")) return preference;
|
||||
for (const QString& language : systemLanguages) {
|
||||
const QString code = language.section('-', 0, 0).section('_', 0, 0).toLower();
|
||||
if (code == QStringLiteral("en") || code == QStringLiteral("ru")) return code;
|
||||
}
|
||||
return QStringLiteral("en");
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <QCoreApplication>
|
||||
#include "imageviewer_window.h"
|
||||
#include "../../lib/debug_config.h"
|
||||
#include <QPainter>
|
||||
@@ -34,14 +35,14 @@ static QPointF wheelPos(const QWheelEvent* e) {
|
||||
ImageViewerWindow::ImageViewerWindow(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
setWindowTitle(QStringLiteral("Фото"));
|
||||
setWindowTitle(QCoreApplication::translate("ChatGui", "Photo"));
|
||||
setAttribute(Qt::WA_DeleteOnClose, false);
|
||||
setMinimumSize(320, 240);
|
||||
resize(800, 600);
|
||||
setMouseTracking(true);
|
||||
|
||||
m_closeBtn = new QPushButton(QStringLiteral("✕"), this);
|
||||
m_closeBtn->setToolTip(QStringLiteral("Закрыть"));
|
||||
m_closeBtn->setToolTip(QCoreApplication::translate("ChatGui", "Close"));
|
||||
m_closeBtn->setCursor(Qt::ArrowCursor);
|
||||
m_closeBtn->setStyleSheet(
|
||||
"QPushButton { background: rgba(0,0,0,0.5); color: white; border: none; "
|
||||
@@ -56,7 +57,7 @@ ImageViewerWindow::ImageViewerWindow(QWidget *parent)
|
||||
m_closeFade = new QPropertyAnimation(m_closeEffect, "opacity", this);
|
||||
m_closeFade->setEasingCurve(QEasingCurve::InOutQuad);
|
||||
|
||||
m_hintLabel = new QLabel(QStringLiteral("Ctrl+колесо — зум"), this);
|
||||
m_hintLabel = new QLabel(QCoreApplication::translate("ChatGui", "Ctrl+wheel — zoom"), this);
|
||||
m_hintLabel->setAlignment(Qt::AlignCenter);
|
||||
m_hintLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
m_hintLabel->setStyleSheet(
|
||||
@@ -126,7 +127,7 @@ void ImageViewerWindow::paintEvent(QPaintEvent*) {
|
||||
|
||||
if (m_image.isNull()) {
|
||||
p.setPen(QColor(150, 150, 150));
|
||||
p.drawText(rect(), Qt::AlignCenter, QStringLiteral("Не удалось загрузить фото"));
|
||||
p.drawText(rect(), Qt::AlignCenter, QCoreApplication::translate("ChatGui", "Failed to load photo"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <QCoreApplication>
|
||||
#include "inputbar.h"
|
||||
#include "emojipanel.h"
|
||||
#include "audiorecorder.h"
|
||||
@@ -61,13 +62,13 @@ static QWidget *wrapBottomBtn(QPushButton *btn, int pad) {
|
||||
}
|
||||
|
||||
QString InputBar::formatFileSize(qint64 bytes) {
|
||||
if (bytes < 1024) return QString("%1B").arg(bytes);
|
||||
if (bytes < 1024) return QCoreApplication::translate("ChatGui", "%1B").arg(bytes);
|
||||
double kb = bytes / 1024.0;
|
||||
if (kb < 1024.0) return QString("%1KB").arg(kb, 0, 'f', kb < 10.0 ? 1 : 0);
|
||||
if (kb < 1024.0) return QCoreApplication::translate("ChatGui", "%1KB").arg(kb, 0, 'f', kb < 10.0 ? 1 : 0);
|
||||
double mb = kb / 1024.0;
|
||||
if (mb < 1024.0) return QString("%1MB").arg(mb, 0, 'f', mb < 10.0 ? 1 : 0);
|
||||
if (mb < 1024.0) return QCoreApplication::translate("ChatGui", "%1MB").arg(mb, 0, 'f', mb < 10.0 ? 1 : 0);
|
||||
double gb = mb / 1024.0;
|
||||
return QString("%1GB").arg(gb, 0, 'f', 1);
|
||||
return QCoreApplication::translate("ChatGui", "%1GB").arg(gb, 0, 'f', 1);
|
||||
}
|
||||
|
||||
InputBar::InputBar(EmojiPanel *emojiPanel, AudioRecorder *recorder, QWidget *parent)
|
||||
@@ -108,7 +109,7 @@ InputBar::InputBar(EmojiPanel *emojiPanel, AudioRecorder *recorder, QWidget *par
|
||||
layout->setSpacing(4);
|
||||
|
||||
m_attachBtn = makeIconBtn(QIcon(":/icons/attach.png"),
|
||||
QString::fromUtf8("Прикрепить"), this);
|
||||
QCoreApplication::translate("ChatGui", "Attach"), this);
|
||||
connect(m_attachBtn, &QPushButton::clicked, this, &InputBar::onAttachClicked);
|
||||
layout->addWidget(wrapBottomBtn(m_attachBtn, 3), 0, Qt::AlignBottom);
|
||||
|
||||
@@ -126,15 +127,15 @@ InputBar::InputBar(EmojiPanel *emojiPanel, AudioRecorder *recorder, QWidget *par
|
||||
layout->addWidget(m_input, 1);
|
||||
|
||||
m_emojiBtn = makeIconBtn(QIcon(":/icons/emoji.png"),
|
||||
QString::fromUtf8("Смайлики"), this);
|
||||
QCoreApplication::translate("ChatGui", "Emoji"), this);
|
||||
layout->addWidget(wrapBottomBtn(m_emojiBtn, 3), 0, Qt::AlignBottom);
|
||||
|
||||
m_pttBtn = makeIconBtn(QIcon(":/icons/mic.png"),
|
||||
QString::fromUtf8("Голосовое сообщение (удерживайте)"), this);
|
||||
QCoreApplication::translate("ChatGui", "Voice message (hold)"), this);
|
||||
layout->addWidget(wrapBottomBtn(m_pttBtn, 3), 0, Qt::AlignBottom);
|
||||
|
||||
auto *sendBtn = makeIconBtn(QIcon(":/icons/send.png"),
|
||||
QString::fromUtf8("Отправить"), this);
|
||||
QCoreApplication::translate("ChatGui", "Send"), this);
|
||||
sendBtn->setStyleSheet(sendBtn->styleSheet() +
|
||||
"QPushButton { border: 1px solid palette(highlight); }");
|
||||
layout->addWidget(sendBtn, 0, Qt::AlignBottom);
|
||||
@@ -331,7 +332,7 @@ void InputBar::onPttReleased() {
|
||||
|
||||
void InputBar::onAttachClicked() {
|
||||
if (m_channelIdForRecord.isEmpty()) return;
|
||||
QString srcPath = QFileDialog::getOpenFileName(this, QString::fromUtf8("Прикрепить файл"));
|
||||
QString srcPath = QFileDialog::getOpenFileName(this, QCoreApplication::translate("ChatGui", "Attach file"));
|
||||
if (srcPath.isEmpty()) return;
|
||||
|
||||
QFileInfo fi(srcPath);
|
||||
@@ -360,10 +361,10 @@ void InputBar::onAttachClicked() {
|
||||
bool asVideo = false;
|
||||
if (!asImage && QStringList{"mp4", "mov", "mkv", "avi", "webm", "m4v"}.contains(fi.suffix().toLower())) {
|
||||
QMessageBox box(this);
|
||||
box.setWindowTitle(QString::fromUtf8("Отправить видео"));
|
||||
box.setText(QString::fromUtf8("Файл содержит видео. Как отправить?"));
|
||||
QPushButton *fileBtn = box.addButton(QString::fromUtf8("Как файл"), QMessageBox::AcceptRole);
|
||||
QPushButton *videoBtn = box.addButton(QString::fromUtf8("Как видео"), QMessageBox::DestructiveRole);
|
||||
box.setWindowTitle(QCoreApplication::translate("ChatGui", "Send video"));
|
||||
box.setText(QCoreApplication::translate("ChatGui", "The file contains video. How would you like to send it?"));
|
||||
QPushButton *fileBtn = box.addButton(QCoreApplication::translate("ChatGui", "As a file"), QMessageBox::AcceptRole);
|
||||
QPushButton *videoBtn = box.addButton(QCoreApplication::translate("ChatGui", "As a video"), QMessageBox::DestructiveRole);
|
||||
box.exec();
|
||||
asVideo = (box.clickedButton() == videoBtn);
|
||||
if (!box.clickedButton() || (!asVideo && box.clickedButton() != fileBtn)) {
|
||||
@@ -421,8 +422,8 @@ void InputBar::updatePreparation(const QString& target, const QByteArray& job, i
|
||||
|
||||
void InputBar::showPreparation() {
|
||||
QString status;
|
||||
if (!m_preparations.value(m_channelIdForRecord).isEmpty()) status = QString::fromUtf8("Подготовка медиа…");
|
||||
else if (m_preparationErrors.contains(m_channelIdForRecord)) status = QString::fromUtf8("Не удалось подготовить вложение");
|
||||
if (!m_preparations.value(m_channelIdForRecord).isEmpty()) status = QCoreApplication::translate("ChatGui", "Preparing media…");
|
||||
else if (m_preparationErrors.contains(m_channelIdForRecord)) status = QCoreApplication::translate("ChatGui", "Failed to prepare attachment");
|
||||
m_preparationLabel->setText(status);
|
||||
m_preparationLabel->setVisible(!status.isEmpty());
|
||||
}
|
||||
@@ -442,7 +443,7 @@ void InputBar::showRecordOverlay() {
|
||||
m_recordOverlay->setGeometry(m_input->geometry().adjusted(4, 4, -4, -4));
|
||||
m_recordOverlay->show();
|
||||
m_recordOverlay->raise();
|
||||
m_recordLabel->setText(QString::fromUtf8("\xF0\x9F\x94\xB4 Recording 0:00"));
|
||||
m_recordLabel->setText(QCoreApplication::translate("ChatGui", "🔴 Recording 0:00"));
|
||||
if (m_levelBar) m_levelBar->setValue(0);
|
||||
}
|
||||
}
|
||||
@@ -454,7 +455,7 @@ void InputBar::hideRecordOverlay() {
|
||||
void InputBar::updateRecordingUI(int ms) {
|
||||
if (m_recordLabel) {
|
||||
int secs = ms / 1000;
|
||||
m_recordLabel->setText(QString::fromUtf8("\xF0\x9F\x94\xB4 Recording %1:%2")
|
||||
m_recordLabel->setText(QCoreApplication::translate("ChatGui", "🔴 Recording %1:%2")
|
||||
.arg(secs / 60).arg(secs % 60, 2, 10, QChar('0')));
|
||||
}
|
||||
if (m_levelBar && m_recorder) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <QCoreApplication>
|
||||
#include "invite_link.h"
|
||||
#include "debug_ui.h"
|
||||
#include <QByteArray>
|
||||
@@ -61,13 +62,13 @@ QString encodeInviteLink(uint64_t channelId, const QByteArray& pubkey,
|
||||
InviteData decodeInviteLink(const QString& link) {
|
||||
InviteData d;
|
||||
if (!link.startsWith(QLatin1String("utun://"))) {
|
||||
d.error = QStringLiteral("invalid prefix (expected utun://)");
|
||||
d.error = QCoreApplication::translate("ChatGui", "invalid prefix (expected utun://)");
|
||||
GUI_ERROR("decodeInviteLink: invalid prefix");
|
||||
return d;
|
||||
}
|
||||
QByteArray blob = QByteArray::fromBase64(link.mid(7).toLatin1());
|
||||
if (blob.isEmpty()) {
|
||||
d.error = QStringLiteral("base64 decode failed");
|
||||
d.error = QCoreApplication::translate("ChatGui", "base64 decode failed");
|
||||
GUI_ERROR("decodeInviteLink: base64 decode failed");
|
||||
return d;
|
||||
}
|
||||
@@ -75,7 +76,7 @@ InviteData decodeInviteLink(const QString& link) {
|
||||
int len = blob.size();
|
||||
|
||||
if (len < 11) {
|
||||
d.error = QStringLiteral("blob too short (%1 bytes)").arg(len);
|
||||
d.error = QCoreApplication::translate("ChatGui", "blob too short (%1 bytes)").arg(len);
|
||||
GUI_ERROR("decodeInviteLink: blob too short (%d bytes)", len);
|
||||
return d;
|
||||
}
|
||||
@@ -83,7 +84,7 @@ InviteData decodeInviteLink(const QString& link) {
|
||||
|
||||
uint8_t ver = data[off++];
|
||||
if (ver != INVITE_LINK_VERSION && ver != 0x02 && ver != 0x01) {
|
||||
d.error = QStringLiteral("unsupported version 0x%1").arg(ver, 2, 16, QLatin1Char('0'));
|
||||
d.error = QCoreApplication::translate("ChatGui", "unsupported version 0x%1").arg(ver, 2, 16, QLatin1Char('0'));
|
||||
GUI_ERROR("decodeInviteLink: unsupported version %d", ver);
|
||||
return d;
|
||||
}
|
||||
@@ -91,13 +92,13 @@ InviteData decodeInviteLink(const QString& link) {
|
||||
/* v2/v3: password field before channel_id */
|
||||
if (ver == 0x02 || ver == INVITE_LINK_VERSION) {
|
||||
if (off >= len) {
|
||||
d.error = QStringLiteral("truncated at pass_len");
|
||||
d.error = QCoreApplication::translate("ChatGui", "truncated at pass_len");
|
||||
return d;
|
||||
}
|
||||
uint8_t plen = data[off++];
|
||||
if (plen > 0) {
|
||||
if (off + plen > len) {
|
||||
d.error = QStringLiteral("truncated at password");
|
||||
d.error = QCoreApplication::translate("ChatGui", "truncated at password");
|
||||
return d;
|
||||
}
|
||||
d.password = QString::fromUtf8(reinterpret_cast<const char*>(data + off), plen);
|
||||
@@ -107,7 +108,7 @@ InviteData decodeInviteLink(const QString& link) {
|
||||
|
||||
/* channel_id: uint64 big-endian */
|
||||
if (off + 8 > len) {
|
||||
d.error = QStringLiteral("truncated at channel_id");
|
||||
d.error = QCoreApplication::translate("ChatGui", "truncated at channel_id");
|
||||
GUI_ERROR("decodeInviteLink: truncated at channel_id");
|
||||
return d;
|
||||
}
|
||||
@@ -118,7 +119,7 @@ InviteData decodeInviteLink(const QString& link) {
|
||||
|
||||
/* join_key: uint64 big-endian */
|
||||
if (off + 8 > len) {
|
||||
d.error = QStringLiteral("truncated at join_key");
|
||||
d.error = QCoreApplication::translate("ChatGui", "truncated at join_key");
|
||||
GUI_ERROR("decodeInviteLink: truncated at join_key");
|
||||
return d;
|
||||
}
|
||||
@@ -129,15 +130,15 @@ InviteData decodeInviteLink(const QString& link) {
|
||||
|
||||
/* v3: reality-блок (node-global параметры) */
|
||||
if (ver == INVITE_LINK_VERSION) {
|
||||
if (off >= len) { d.error = QStringLiteral("truncated at reality"); return d; }
|
||||
if (off >= len) { d.error = QCoreApplication::translate("ChatGui", "truncated at reality"); return d; }
|
||||
if (data[off++] != 0) {
|
||||
if (off + 8 + 32 + 3 + 1 > len) { d.error = QStringLiteral("truncated at reality params"); return d; }
|
||||
if (off + 8 + 32 + 3 + 1 > len) { d.error = QCoreApplication::translate("ChatGui", "truncated at reality params"); return d; }
|
||||
d.reality.shortId = QByteArray(reinterpret_cast<const char*>(data + off), 8); off += 8;
|
||||
d.reality.serverPubkey = QByteArray(reinterpret_cast<const char*>(data + off), 32); off += 32;
|
||||
d.reality.version = QByteArray(reinterpret_cast<const char*>(data + off), 3); off += 3;
|
||||
uint8_t snlen = data[off++];
|
||||
if (snlen > INVITE_SNAME_MAX - 1) snlen = INVITE_SNAME_MAX - 1;
|
||||
if (off + snlen > len) { d.error = QStringLiteral("truncated at reality server_name"); return d; }
|
||||
if (off + snlen > len) { d.error = QCoreApplication::translate("ChatGui", "truncated at reality server_name"); return d; }
|
||||
d.reality.serverName = QString::fromUtf8(reinterpret_cast<const char*>(data + off), snlen);
|
||||
off += snlen;
|
||||
d.reality.has = true;
|
||||
@@ -149,12 +150,12 @@ InviteData decodeInviteLink(const QString& link) {
|
||||
uint8_t header = data[off++];
|
||||
int cnt = (header & 0x03) + 1;
|
||||
if (cnt < 1 || cnt > 4) {
|
||||
d.error = QStringLiteral("invalid addr count %1").arg(cnt);
|
||||
d.error = QCoreApplication::translate("ChatGui", "invalid addr count %1").arg(cnt);
|
||||
GUI_ERROR("decodeInviteLink: invalid addr count %d", cnt);
|
||||
return d;
|
||||
}
|
||||
if (off + 32 > len) {
|
||||
d.error = QStringLiteral("truncated at pubkey");
|
||||
d.error = QCoreApplication::translate("ChatGui", "truncated at pubkey");
|
||||
GUI_ERROR("decodeInviteLink: truncated at pubkey");
|
||||
return d;
|
||||
}
|
||||
@@ -168,7 +169,7 @@ InviteData decodeInviteLink(const QString& link) {
|
||||
bool isV6 = header & (1 << (2 + j));
|
||||
int ipLen = isV6 ? 16 : 4;
|
||||
if (off + 2 + ipLen + 2 > len) {
|
||||
d.error = QStringLiteral("truncated at addr %1 of block").arg(j);
|
||||
d.error = QCoreApplication::translate("ChatGui", "truncated at addr %1 of block").arg(j);
|
||||
GUI_ERROR("decodeInviteLink: truncated at addr %d of block", j);
|
||||
return d;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <QCoreApplication>
|
||||
#include "inviteby.h"
|
||||
#include "invite_link.h"
|
||||
#include "debug_ui.h"
|
||||
@@ -21,14 +22,14 @@ extern "C" {
|
||||
InviteByLinkDialog::InviteByLinkDialog(const QString& channelId, UtunNode* node, QWidget* parent)
|
||||
: QDialog(parent), m_channelId(channelId), m_node(node)
|
||||
{
|
||||
setWindowTitle(QString::fromUtf8("Пригласить в канал %1").arg(channelId));
|
||||
setWindowTitle(QCoreApplication::translate("ChatGui", "Invite to channel %1").arg(channelId));
|
||||
setMinimumWidth(420);
|
||||
setModal(true);
|
||||
|
||||
auto *layout = new QVBoxLayout(this);
|
||||
layout->setSpacing(12);
|
||||
|
||||
auto *title = new QLabel(QString::fromUtf8("Вставьте utun:// ссылку приглашаемого узла:"), this);
|
||||
auto *title = new QLabel(QCoreApplication::translate("ChatGui", "Paste the invited node's utun:// link:"), this);
|
||||
title->setStyleSheet("font-weight: bold; font-size: 12px;");
|
||||
layout->addWidget(title);
|
||||
|
||||
@@ -46,7 +47,7 @@ InviteByLinkDialog::InviteByLinkDialog(const QString& channelId, UtunNode* node,
|
||||
|
||||
auto *btnLayout = new QHBoxLayout();
|
||||
btnLayout->addStretch();
|
||||
m_inviteSendBtn = new QPushButton(QString::fromUtf8("Пригласить"), this);
|
||||
m_inviteSendBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Invite"), this);
|
||||
m_inviteSendBtn->setStyleSheet(
|
||||
"QPushButton { border: none; border-radius: 4px;"
|
||||
" padding: 5px 14px; font-size: 12px; font-weight: bold;"
|
||||
@@ -83,14 +84,14 @@ void InviteByLinkDialog::onInviteLinkTextChanged() {
|
||||
}
|
||||
if (!link.startsWith(QLatin1String("utun://"))) {
|
||||
m_inviteStatusLabel->setStyleSheet("color: gray; font-size: 11px; padding: 2px 0;");
|
||||
m_inviteStatusLabel->setText(QString::fromUtf8("Ожидается utun:// ссылка..."));
|
||||
m_inviteStatusLabel->setText(QCoreApplication::translate("ChatGui", "Expected a utun:// link..."));
|
||||
return;
|
||||
}
|
||||
|
||||
InviteData d = decodeInviteLink(link);
|
||||
if (!d.error.isEmpty()) {
|
||||
m_inviteStatusLabel->setStyleSheet("color: gray; font-size: 11px; padding: 2px 0;");
|
||||
m_inviteStatusLabel->setText(QString::fromUtf8("Неверная ссылка: ") + d.error);
|
||||
m_inviteStatusLabel->setText(QCoreApplication::translate("ChatGui", "Invalid link: ") + d.error);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -112,9 +113,9 @@ void InviteByLinkDialog::onInviteLinkTextChanged() {
|
||||
QString::fromLatin1(d.addrs[i].address.left(4).toHex())).arg(d.addrs[i].port);
|
||||
}
|
||||
}
|
||||
if (d.addrs.size() > 3) addrInfo += QString(" +%1 more").arg(d.addrs.size() - 3);
|
||||
if (d.addrs.size() > 3) addrInfo += QCoreApplication::translate("ChatGui", " +%1 more").arg(d.addrs.size() - 3);
|
||||
|
||||
QString diag = QString::fromUtf8("Ссылка: узел 0x%1 ключ %2, %3 адр. (%4)\nПриглашение в канал: %5")
|
||||
QString diag = QCoreApplication::translate("ChatGui", "Link: node 0x%1 key %2, %3 addresses (%4)\nInvitation to channel: %5")
|
||||
.arg(targetNodeId, 16, 16, QChar('0'))
|
||||
.arg(pkShort)
|
||||
.arg(d.addrs.size())
|
||||
@@ -123,7 +124,7 @@ void InviteByLinkDialog::onInviteLinkTextChanged() {
|
||||
|
||||
if (myNodeId != 0 && myNodeId == targetNodeId) {
|
||||
m_inviteStatusLabel->setStyleSheet("color: #E67E22; font-size: 11px; padding: 2px 0;");
|
||||
m_inviteStatusLabel->setText(diag + QString::fromUtf8(" ⚠ Это ваша собственная ссылка"));
|
||||
m_inviteStatusLabel->setText(diag + QCoreApplication::translate("ChatGui", " ⚠ This is your own link"));
|
||||
} else {
|
||||
m_inviteStatusLabel->setStyleSheet("color: gray; font-size: 11px; padding: 2px 0;");
|
||||
m_inviteStatusLabel->setText(diag);
|
||||
@@ -142,21 +143,21 @@ void InviteByLinkDialog::onSendInvite() {
|
||||
if (m_inviteSending) return;
|
||||
QString link = m_inviteLinkEdit->text().trimmed();
|
||||
if (link.isEmpty()) {
|
||||
setInviteStatus(QString::fromUtf8("Вставьте utun:// ссылку узла."), true);
|
||||
setInviteStatus(QCoreApplication::translate("ChatGui", "Paste the node's utun:// link."), true);
|
||||
return;
|
||||
}
|
||||
|
||||
InviteData d = decodeInviteLink(link);
|
||||
if (!d.error.isEmpty()) {
|
||||
setInviteStatus(QString::fromUtf8("Неверная ссылка: ") + d.error, true);
|
||||
setInviteStatus(QCoreApplication::translate("ChatGui", "Invalid link: ") + d.error, true);
|
||||
return;
|
||||
}
|
||||
if (d.pubkey.size() != 32) {
|
||||
setInviteStatus(QString::fromUtf8("Pubkey не 32 байта."), true);
|
||||
setInviteStatus(QCoreApplication::translate("ChatGui", "The public key must be 32 bytes."), true);
|
||||
return;
|
||||
}
|
||||
if (d.addrs.isEmpty()) {
|
||||
setInviteStatus(QString::fromUtf8("Нет адресов."), true);
|
||||
setInviteStatus(QCoreApplication::translate("ChatGui", "No addresses."), true);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -166,7 +167,7 @@ void InviteByLinkDialog::onSendInvite() {
|
||||
uint64_t myNodeId = m_node ? m_node->nodeIdHex().toULongLong(nullptr, 16) : 0;
|
||||
if (myNodeId != 0 && myNodeId == targetNodeId) {
|
||||
GUI_WARN("invite_by_link: self-invite rejected — link contains own pubkey, node=0x%016llx", (unsigned long long)myNodeId);
|
||||
setInviteStatus(QString::fromUtf8("Ошибка: это ваша собственная ссылка (нельзя пригласить себя)"), true);
|
||||
setInviteStatus(QCoreApplication::translate("ChatGui", "Error: this is your own link (you cannot invite yourself)"), true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -186,27 +187,27 @@ void InviteByLinkDialog::onSendInvite() {
|
||||
(const uint8_t*)d.pubkey.constData(),
|
||||
(const uint8_t*)addrsBuf.constData(), d.addrs.size(), addrsBuf.size());
|
||||
|
||||
setInviteStatus(QString::fromUtf8("Приглашение отправлено, ожидание ответа..."));
|
||||
setInviteStatus(QCoreApplication::translate("ChatGui", "Invitation sent, waiting for a response..."));
|
||||
}
|
||||
|
||||
void InviteByLinkDialog::onConnectResult(uint64_t nodeId, uint64_t channelId, int result) {
|
||||
if (!m_inviteSending || nodeId != m_inviteTargetNodeId) return;
|
||||
|
||||
if (result == 0) {
|
||||
setInviteStatus(QString::fromUtf8("Узел присоединился к каналу."), false);
|
||||
setInviteStatus(QCoreApplication::translate("ChatGui", "The node has joined the channel."), false);
|
||||
QTimer::singleShot(400, this, &QDialog::accept);
|
||||
} else {
|
||||
m_inviteSending = false;
|
||||
m_inviteSendBtn->setEnabled(true);
|
||||
m_inviteSendBtn->setFocus();
|
||||
|
||||
const char* errMsg = "Неизвестная ошибка.";
|
||||
QString errMsg = QCoreApplication::translate("ChatGui", "Unknown error.");
|
||||
switch (result) {
|
||||
case -1: errMsg = "Канал не найден."; break;
|
||||
case 2: errMsg = "Таймаут — узел не ответил."; break;
|
||||
case -7: errMsg = "Узел не инициализирован."; break;
|
||||
case -1: errMsg = QCoreApplication::translate("ChatGui", "Channel not found."); break;
|
||||
case 2: errMsg = QCoreApplication::translate("ChatGui", "Timed out — the node did not respond."); break;
|
||||
case -7: errMsg = QCoreApplication::translate("ChatGui", "Node not initialized."); break;
|
||||
default: break;
|
||||
}
|
||||
setInviteStatus(QString::fromUtf8("Ошибка: %1").arg(QString::fromUtf8(errMsg)), true);
|
||||
setInviteStatus(QCoreApplication::translate("ChatGui", "Error: %1").arg(errMsg), true);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <QCoreApplication>
|
||||
#include "invitedialog.h"
|
||||
#include "qrcode_utils.h"
|
||||
#include "../db/db_manager.h"
|
||||
@@ -26,17 +27,18 @@ ShareInviteDialog::ShareInviteDialog(const QString& channelId, DbManager* db, QW
|
||||
, m_channelId(channelId)
|
||||
, m_db(db)
|
||||
{
|
||||
setWindowTitle(QString::fromUtf8("Пригласительная ссылка"));
|
||||
setWindowTitle(QCoreApplication::translate("ChatGui", "Invite link"));
|
||||
setMinimumSize(420, 560);
|
||||
setModal(true);
|
||||
|
||||
auto *layout = new QVBoxLayout(this);
|
||||
layout->setSpacing(12);
|
||||
auto* expiry = new QLabel(QStringLiteral("Ссылка действует %1 минут с момента создания.").arg(JOIN_KEY_TTL_SECONDS / 60), this);
|
||||
auto* expiry = new QLabel(QCoreApplication::translate("ChatGui",
|
||||
"The link expires %1 minutes after creation.").arg(JOIN_KEY_TTL_SECONDS / 60), this);
|
||||
expiry->setWordWrap(true);
|
||||
layout->addWidget(expiry);
|
||||
|
||||
auto *nodeLabel = new QLabel(QString::fromUtf8("Узел входа:"), this);
|
||||
auto *nodeLabel = new QLabel(QCoreApplication::translate("ChatGui", "Entry node:"), this);
|
||||
nodeLabel->setStyleSheet("font-size: 12px; color: palette(window-text);");
|
||||
|
||||
m_nodeCombo = new QComboBox(this);
|
||||
@@ -52,10 +54,10 @@ ShareInviteDialog::ShareInviteDialog(const QString& channelId, DbManager* db, QW
|
||||
|
||||
m_linkEdit = new QLineEdit(this);
|
||||
m_linkEdit->setReadOnly(true);
|
||||
m_linkEdit->setPlaceholderText(QString::fromUtf8("Генерация ссылки..."));
|
||||
m_linkEdit->setPlaceholderText(QCoreApplication::translate("ChatGui", "Generating link..."));
|
||||
m_linkEdit->setStyleSheet("padding: 6px; font-family: monospace;");
|
||||
|
||||
auto *copyBtn = new QPushButton(QString::fromUtf8("Копировать"), this);
|
||||
auto *copyBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Copy"), this);
|
||||
connect(copyBtn, &QPushButton::clicked, this, [this]() {
|
||||
QClipboard* cb = QApplication::clipboard();
|
||||
cb->setText(m_linkEdit->text(), QClipboard::Clipboard);
|
||||
@@ -125,7 +127,7 @@ void ShareInviteDialog::requestLink(uint64_t targetNodeId) {
|
||||
req->request_id = m_requestId = ++nextRequestId;
|
||||
req->target_node_id = targetNodeId;
|
||||
m_linkEdit->setText(QString());
|
||||
m_linkEdit->setPlaceholderText(QString::fromUtf8("Генерация ссылки..."));
|
||||
m_linkEdit->setPlaceholderText(QCoreApplication::translate("ChatGui", "Generating link..."));
|
||||
m_qrLabel->clear();
|
||||
gui_bridge_post_uasync_fn(chat_invite_build_link_trampoline, req);
|
||||
}
|
||||
@@ -151,7 +153,7 @@ void ShareInviteDialog::onCandidates(const char* chId, int chIdLen, const uint8_
|
||||
|
||||
if (autoId == 0) {
|
||||
m_linkEdit->setText(QString());
|
||||
m_linkEdit->setPlaceholderText(QString::fromUtf8("Не удалось определить узел (см. лог)"));
|
||||
m_linkEdit->setPlaceholderText(QCoreApplication::translate("ChatGui", "Failed to identify the node (see log)"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -167,7 +169,7 @@ void ShareInviteDialog::onCandidates(const char* chId, int chIdLen, const uint8_
|
||||
m_nodeCombo->clear();
|
||||
|
||||
/* пункт 0 — узел, найденный авто-выбором */
|
||||
m_nodeCombo->addItem(nodeLabel(autoId) + QString::fromUtf8(" (auto)"),
|
||||
m_nodeCombo->addItem(nodeLabel(autoId) + QCoreApplication::translate("ChatGui", " (auto)"),
|
||||
QVariant::fromValue((qulonglong)autoId));
|
||||
|
||||
/* остальные узлы (пропуская авто), свой помечаем (self) */
|
||||
@@ -179,7 +181,7 @@ void ShareInviteDialog::onCandidates(const char* chId, int chIdLen, const uint8_
|
||||
if (nid == autoId) continue;
|
||||
QString label = nodeLabel(nid);
|
||||
if (m_db && nid == m_db->myNodeId())
|
||||
label += QString::fromUtf8(" (self)");
|
||||
label += QCoreApplication::translate("ChatGui", " (self)");
|
||||
m_nodeCombo->addItem(label, QVariant::fromValue((qulonglong)nid));
|
||||
}
|
||||
|
||||
@@ -193,7 +195,7 @@ void ShareInviteDialog::onLinkReady(const char* chId, int chIdLen, uint64_t requ
|
||||
if (cid != m_channelId) return;
|
||||
if (!link || link[0] == '\0') {
|
||||
m_linkEdit->setText(QString());
|
||||
m_linkEdit->setPlaceholderText(QString::fromUtf8("Не удалось собрать ссылку (см. лог)"));
|
||||
m_linkEdit->setPlaceholderText(QCoreApplication::translate("ChatGui", "Failed to build the link (see log)"));
|
||||
m_qrLabel->clear();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <QCoreApplication>
|
||||
#include "joindialog.h"
|
||||
#include "invite_link.h"
|
||||
#include "debug_ui.h"
|
||||
@@ -23,7 +24,7 @@ JoinDialog::JoinDialog(UtunNode* node, DbManager* db, QWidget* parent,
|
||||
, m_node(node)
|
||||
, m_db(db)
|
||||
{
|
||||
setWindowTitle("Join Channel");
|
||||
setWindowTitle(QCoreApplication::translate("ChatGui", "Join Channel"));
|
||||
setMinimumWidth(420);
|
||||
setModal(true);
|
||||
|
||||
@@ -31,7 +32,7 @@ JoinDialog::JoinDialog(UtunNode* node, DbManager* db, QWidget* parent,
|
||||
layout->setSpacing(12);
|
||||
layout->setContentsMargins(20, 16, 20, 16);
|
||||
|
||||
auto *titleLabel = new QLabel("Paste invite link:", this);
|
||||
auto *titleLabel = new QLabel(QCoreApplication::translate("ChatGui", "Paste invite link:"), this);
|
||||
titleLabel->setStyleSheet("font-weight: bold; font-size: 13px;");
|
||||
layout->addWidget(titleLabel);
|
||||
|
||||
@@ -41,8 +42,9 @@ JoinDialog::JoinDialog(UtunNode* node, DbManager* db, QWidget* parent,
|
||||
"QLineEdit { border: 1px solid palette(mid); border-radius: 6px;"
|
||||
" padding: 8px 12px; font-size: 13px; background: palette(base); }");
|
||||
layout->addWidget(m_linkEdit);
|
||||
auto* expiry = new QLabel(QStringLiteral("Приглашение действует 10 минут с момента создания.\n"
|
||||
"Если срок истёк, попросите отправителя создать новую ссылку."), this);
|
||||
auto* expiry = new QLabel(QCoreApplication::translate("ChatGui",
|
||||
"The invitation expires 10 minutes after creation.\nIf it has expired, ask the sender to "
|
||||
"create a new link."), this);
|
||||
expiry->setWordWrap(true);
|
||||
layout->addWidget(expiry);
|
||||
|
||||
@@ -57,8 +59,8 @@ JoinDialog::JoinDialog(UtunNode* node, DbManager* db, QWidget* parent,
|
||||
btnLayout->setSpacing(8);
|
||||
btnLayout->addStretch();
|
||||
|
||||
m_cancelBtn = new QPushButton("Cancel", this);
|
||||
m_cancelBtn->setFixedWidth(90);
|
||||
m_cancelBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Cancel"), this);
|
||||
m_cancelBtn->setMinimumWidth(90);
|
||||
m_cancelBtn->setStyleSheet(
|
||||
"QPushButton { border: 1px solid palette(mid); border-radius: 4px;"
|
||||
" padding: 6px 16px; font-size: 13px; background: palette(button); }"
|
||||
@@ -66,8 +68,8 @@ JoinDialog::JoinDialog(UtunNode* node, DbManager* db, QWidget* parent,
|
||||
connect(m_cancelBtn, &QPushButton::clicked, this, &QDialog::reject);
|
||||
btnLayout->addWidget(m_cancelBtn);
|
||||
|
||||
m_connectBtn = new QPushButton("Connect", this);
|
||||
m_connectBtn->setFixedWidth(90);
|
||||
m_connectBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Connect"), this);
|
||||
m_connectBtn->setMinimumWidth(90);
|
||||
m_connectBtn->setDefault(true);
|
||||
m_connectBtn->setStyleSheet(
|
||||
"QPushButton { border: none; border-radius: 4px;"
|
||||
@@ -113,7 +115,7 @@ JoinDialog::JoinDialog(UtunNode* node, DbManager* db, QWidget* parent,
|
||||
: QString("?%1").arg(a.proto);
|
||||
addrList.append(QString("%1:%2 (%3)").arg(addrStr).arg(a.port).arg(protoName));
|
||||
}
|
||||
setStatus(QString("Node: 0x%1 | %2")
|
||||
setStatus(QCoreApplication::translate("ChatGui", "Node: 0x%1 %2")
|
||||
.arg(nodeId, 16, 16, QLatin1Char('0'))
|
||||
.arg(addrList.join(", ")));
|
||||
});
|
||||
@@ -149,22 +151,22 @@ void JoinDialog::onConnectClicked() {
|
||||
if (m_connecting) return;
|
||||
QString link = m_linkEdit->text().trimmed();
|
||||
if (link.isEmpty()) {
|
||||
setStatus("Enter an invite link.", true);
|
||||
setStatus(QCoreApplication::translate("ChatGui", "Enter an invite link."), true);
|
||||
return;
|
||||
}
|
||||
|
||||
setStatus("Parsing link...");
|
||||
setStatus(QCoreApplication::translate("ChatGui", "Parsing link..."));
|
||||
InviteData d = decodeInviteLink(link);
|
||||
if (!d.error.isEmpty()) {
|
||||
setStatus("Invalid invite link: " + d.error, true);
|
||||
setStatus(QCoreApplication::translate("ChatGui", "Invalid invite link: ") + d.error, true);
|
||||
return;
|
||||
}
|
||||
if (d.pubkey.size() != 32) {
|
||||
setStatus(QString("Invalid invite link: pubkey is %1 bytes (need 32)").arg(d.pubkey.size()), true);
|
||||
setStatus(QCoreApplication::translate("ChatGui", "Invalid invite link: pubkey is %1 bytes (need 32)").arg(d.pubkey.size()), true);
|
||||
return;
|
||||
}
|
||||
if (d.addrs.isEmpty()) {
|
||||
setStatus("No peer addresses in the invite link.", true);
|
||||
setStatus(QCoreApplication::translate("ChatGui", "No peer addresses in the invite link."), true);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -190,7 +192,7 @@ void JoinDialog::onConnectClicked() {
|
||||
m_connectBtn->setEnabled(false);
|
||||
m_linkEdit->setReadOnly(true);
|
||||
|
||||
setStatus("Connecting to peer...");
|
||||
setStatus(QCoreApplication::translate("ChatGui", "Connecting to peer..."));
|
||||
|
||||
QByteArray addrsBuf = serializeInviteAddrs(d);
|
||||
|
||||
@@ -206,7 +208,7 @@ void JoinDialog::onConnectResult(uint64_t nodeId, uint64_t channelId, int result
|
||||
if (!m_connecting || nodeId != m_pendingNodeId || channelId != m_pendingChannelId) return;
|
||||
|
||||
if (result == 0) {
|
||||
setStatus("Connected!");
|
||||
setStatus(QCoreApplication::translate("ChatGui", "Connected!"));
|
||||
QString cid = QString::number(m_pendingChannelId);
|
||||
emit joined(cid);
|
||||
accept();
|
||||
@@ -216,13 +218,13 @@ void JoinDialog::onConnectResult(uint64_t nodeId, uint64_t channelId, int result
|
||||
m_linkEdit->setReadOnly(false);
|
||||
m_connectBtn->setFocus();
|
||||
|
||||
const char* errMsg = "Unknown error.";
|
||||
QString errMsg = QCoreApplication::translate("ChatGui", "Unknown error.");
|
||||
switch (result) {
|
||||
case -1: errMsg = "Channel not found."; break;
|
||||
case 2: errMsg = "Connection timed out."; break;
|
||||
case -7: errMsg = "Node not initialized."; break;
|
||||
case -1: errMsg = QCoreApplication::translate("ChatGui", "Channel not found."); break;
|
||||
case 2: errMsg = QCoreApplication::translate("ChatGui", "Connection timed out."); break;
|
||||
case -7: errMsg = QCoreApplication::translate("ChatGui", "Node not initialized."); break;
|
||||
default: break;
|
||||
}
|
||||
setStatus(QString("Connection failed: %1").arg(errMsg), true);
|
||||
setStatus(QCoreApplication::translate("ChatGui", "Connection failed: %1").arg(errMsg), true);
|
||||
}
|
||||
}
|
||||
+30
-11
@@ -1,3 +1,4 @@
|
||||
#include <QCoreApplication>
|
||||
#include <QApplication>
|
||||
#include <QIcon>
|
||||
#include <QPalette>
|
||||
@@ -6,6 +7,8 @@
|
||||
#include <QFile>
|
||||
#include <QLockFile>
|
||||
#include <QMessageBox>
|
||||
#include <QTranslator>
|
||||
#include "gui_language.h"
|
||||
#include <cstdio>
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
@@ -75,13 +78,11 @@ int main(int argc, char *argv[]) {
|
||||
app.setDesktopFileName("org.utun.vibechat");
|
||||
app.setWindowIcon(QIcon(":/icons/app_icon.png"));
|
||||
app.setQuitOnLastWindowClosed(false);
|
||||
debug_config_init();
|
||||
|
||||
/* single-instance: лог/БД/сокеты конфликтуют при втором запуске */
|
||||
/* Блокировка до любых изменений конфига/БД; предупреждение после загрузки языка. */
|
||||
QLockFile lock(QCoreApplication::applicationDirPath() + "/vibechat.lock");
|
||||
if (!lock.tryLock()) {
|
||||
QMessageBox::warning(nullptr, "vibechat", "vibechat is already running.");
|
||||
return 1;
|
||||
}
|
||||
const bool locked = lock.tryLock();
|
||||
|
||||
app.setStyle(QStyleFactory::create("Fusion"));
|
||||
QPalette p;
|
||||
@@ -105,10 +106,31 @@ int main(int argc, char *argv[]) {
|
||||
QString cfgDir = QCoreApplication::applicationDirPath();
|
||||
QString cfgPath = cfgDir + "/vibechat.cfg";
|
||||
/* миграция: старый конфиг chatgui.cfg → vibechat.cfg */
|
||||
if (!QFile::exists(cfgPath) && QFile::exists(cfgDir + "/chatgui.cfg"))
|
||||
if (locked && !QFile::exists(cfgPath) && QFile::exists(cfgDir + "/chatgui.cfg"))
|
||||
QFile::rename(cfgDir + "/chatgui.cfg", cfgPath);
|
||||
|
||||
NodeConfig cfg(cfgPath);
|
||||
if (cfg.exists() && !cfg.load()) {
|
||||
qCritical("Cannot open/read config file: %s", qPrintable(cfgPath));
|
||||
return 1;
|
||||
}
|
||||
|
||||
QTranslator guiTranslator, qtTranslator;
|
||||
const QString language = resolveGuiLanguage(cfg.language());
|
||||
if (!guiTranslator.load(":/translations/chatgui_" + language + ".qm") || !app.installTranslator(&guiTranslator)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "GUI language: failed to load embedded catalog for %s", qPrintable(language));
|
||||
return 1;
|
||||
}
|
||||
if (language == "ru") {
|
||||
if (!qtTranslator.load(QStringLiteral(":/translations/qtbase_ru.qm")) || !app.installTranslator(&qtTranslator)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "GUI language: failed to load embedded Qt Russian catalog");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if (!locked) {
|
||||
QMessageBox::warning(nullptr, "vibechat", QCoreApplication::translate("ChatGui", "vibechat is already running."));
|
||||
return 1;
|
||||
}
|
||||
bool needSave = false;
|
||||
|
||||
if (!cfg.exists()) {
|
||||
@@ -117,10 +139,6 @@ int main(int argc, char *argv[]) {
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
if (!cfg.load()) {
|
||||
qCritical("Cannot open/read config file: %s", qPrintable(cfgPath));
|
||||
return 1;
|
||||
}
|
||||
if (cfg.hasErrors()) {
|
||||
for (const auto& err : cfg.errors())
|
||||
fprintf(stderr, "CONFIG ERROR: %s\n", qPrintable(err));
|
||||
@@ -154,12 +172,13 @@ int main(int argc, char *argv[]) {
|
||||
/* ранняя инициализация лога в GUI-потоке: SoundManager::init() вызывается в
|
||||
* конструкторе MainWindow ДО старта utun-потока, поэтому без этого его диагностика
|
||||
* (ma_engine_init и т.п.) в лог-файл не попадёт. */
|
||||
debug_config_init();
|
||||
debug_enable_function_name(0);
|
||||
debug_enable_file_output(debugFile.toUtf8().constData(), 0);
|
||||
QString lvl = cfg.debugLevel().trimmed();
|
||||
if (lvl.isEmpty()) lvl = QStringLiteral("info");
|
||||
debug_apply_global_level(lvl.toUtf8().constData());
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "GUI language: preference=%s system=%s selected=%s",
|
||||
qPrintable(cfg.language()), qPrintable(QLocale::system().uiLanguages().join(',')), qPrintable(language));
|
||||
|
||||
DbManager db;
|
||||
MainWindow w(nullptr, &db, cfgPath, dbPath, debugFile,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <QCoreApplication>
|
||||
#include "mainwindow.h"
|
||||
#include "channellist.h"
|
||||
#include "messagelist.h"
|
||||
@@ -437,7 +438,7 @@ void MainWindow::setupBridgeCallbacks() {
|
||||
uint64_t from; memcpy(&from, data + 1 + chLen, 8);
|
||||
s_mainWindow->m_trayIcon->showMessage(
|
||||
"vibechat",
|
||||
QStringLiteral("You are now a channel co-owner.\nReceived admin key for channel %1 from 0x%2")
|
||||
QCoreApplication::translate("ChatGui", "You are now a channel co-owner.\nReceived admin key for channel %1 from 0x%2")
|
||||
.arg(chId).arg(QString::number(from, 16)),
|
||||
QSystemTrayIcon::Information, 5000);
|
||||
if (s_mainWindow->m_currentChannelId == chId)
|
||||
@@ -604,7 +605,7 @@ void MainWindow::onCallIncoming(const QString& chId, quint64 caller, quint64 cal
|
||||
|
||||
SoundManager::instance()->playLoop("call_incoming");
|
||||
if (!isVisible() || isMinimized()) {
|
||||
m_trayIcon->showMessage("vibechat", QStringLiteral("Incoming call from %1").arg(name),
|
||||
m_trayIcon->showMessage("vibechat", QCoreApplication::translate("ChatGui", "Incoming call from %1").arg(name),
|
||||
QSystemTrayIcon::Information, 5000);
|
||||
}
|
||||
|
||||
@@ -687,7 +688,9 @@ void MainWindow::onDmConvUpdated(const QString& convId) {
|
||||
void MainWindow::onInviteToPmRequested(const QString& source, quint64 peer, const QString& name) {
|
||||
if (!m_db || !m_db->isOpen() || !m_node || !m_node->isRunning()) {
|
||||
GUI_ERROR("invite PM: node unavailable");
|
||||
QMessageBox::warning(this, QStringLiteral("Приглашение"), QStringLiteral("Ядро не запущено.")); return;
|
||||
QMessageBox::warning(this, QCoreApplication::translate("ChatGui",
|
||||
"Invitation"), QCoreApplication::translate("ChatGui",
|
||||
"The core is not running.")); return;
|
||||
}
|
||||
if (!peer || peer == m_db->myNodeId()) { GUI_ERROR("invite PM: invalid recipient"); return; }
|
||||
DmInviteDialog dialog(m_db, peer, name, source, this);
|
||||
@@ -702,11 +705,15 @@ void MainWindow::onMessageLinkActivated(const QString& link) {
|
||||
const QUrl url(link, QUrl::StrictMode);
|
||||
if (!url.isValid() || url.host().isEmpty() || (url.scheme() != "http" && url.scheme() != "https")) {
|
||||
GUI_ERROR("message link: invalid web address bytes=%d", (int)link.size());
|
||||
QMessageBox::warning(this, tr("Ссылка"), tr("Некорректная ссылка.")); return;
|
||||
QMessageBox::warning(this, QCoreApplication::translate("ChatGui",
|
||||
"Link"), QCoreApplication::translate("ChatGui",
|
||||
"Invalid link.")); return;
|
||||
}
|
||||
if (!QDesktopServices::openUrl(url)) {
|
||||
GUI_ERROR("message link: external open failed scheme=%s host=%s", qPrintable(url.scheme()), qPrintable(url.host()));
|
||||
QMessageBox::warning(this, tr("Ссылка"), tr("Не удалось открыть ссылку. Проверьте наличие браузера.")); return;
|
||||
QMessageBox::warning(this, QCoreApplication::translate("ChatGui",
|
||||
"Link"), QCoreApplication::translate("ChatGui",
|
||||
"Failed to open the link. Check that a browser is installed.")); return;
|
||||
}
|
||||
GUI_INFO("message link: external open requested scheme=%s host=%s", qPrintable(url.scheme()), qPrintable(url.host()));
|
||||
return;
|
||||
@@ -714,11 +721,15 @@ void MainWindow::onMessageLinkActivated(const QString& link) {
|
||||
const auto invite = decodeInviteLink(link);
|
||||
if (!invite.error.isEmpty()) {
|
||||
GUI_ERROR("invite link: invalid error=%s", qPrintable(invite.error));
|
||||
QMessageBox::warning(this, QStringLiteral("Приглашение"), QStringLiteral("Некорректная ссылка: ") + invite.error); return;
|
||||
QMessageBox::warning(this, QCoreApplication::translate("ChatGui",
|
||||
"Invitation"), QCoreApplication::translate("ChatGui",
|
||||
"Invalid link: ") + invite.error); return;
|
||||
}
|
||||
if (!m_db || !m_db->isOpen() || !m_node || !m_node->isRunning()) {
|
||||
GUI_ERROR("invite link: node unavailable");
|
||||
QMessageBox::warning(this, QStringLiteral("Приглашение"), QStringLiteral("Ядро не запущено.")); return;
|
||||
QMessageBox::warning(this, QCoreApplication::translate("ChatGui",
|
||||
"Invitation"), QCoreApplication::translate("ChatGui",
|
||||
"The core is not running.")); return;
|
||||
}
|
||||
const QString channelId = QString::number(invite.channelId);
|
||||
for (const auto& channel : m_db->getChannels()) {
|
||||
@@ -751,19 +762,19 @@ void MainWindow::setupTray() {
|
||||
m_trayIcon->setToolTip("vibechat");
|
||||
|
||||
m_trayMenu = new QMenu(this);
|
||||
QAction *settingsAction = m_trayMenu->addAction("Settings");
|
||||
QAction *settingsAction = m_trayMenu->addAction(QCoreApplication::translate("ChatGui", "Settings"));
|
||||
connect(settingsAction, &QAction::triggered, this, &MainWindow::showSettings);
|
||||
|
||||
m_trayMenu->addSeparator();
|
||||
|
||||
QAction *showAction = m_trayMenu->addAction("Show");
|
||||
QAction *showAction = m_trayMenu->addAction(QCoreApplication::translate("ChatGui", "Show"));
|
||||
connect(showAction, &QAction::triggered, this, [this]() {
|
||||
show();
|
||||
raise();
|
||||
activateWindow();
|
||||
});
|
||||
|
||||
QAction *quitAction = m_trayMenu->addAction("Quit");
|
||||
QAction *quitAction = m_trayMenu->addAction(QCoreApplication::translate("ChatGui", "Quit"));
|
||||
connect(quitAction, &QAction::triggered, this, &MainWindow::requestShutdown);
|
||||
|
||||
m_trayIcon->setContextMenu(m_trayMenu);
|
||||
@@ -778,7 +789,7 @@ void MainWindow::closeEvent(QCloseEvent *event) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "MainWindow: closeEvent — hiding to tray");
|
||||
saveWindowGeometry();
|
||||
hide();
|
||||
m_trayIcon->showMessage("vibechat", "Application minimized to tray.",
|
||||
m_trayIcon->showMessage("vibechat", QCoreApplication::translate("ChatGui", "Application minimized to tray."),
|
||||
QSystemTrayIcon::Information, 2000);
|
||||
event->ignore();
|
||||
}
|
||||
@@ -814,7 +825,7 @@ void MainWindow::showShareInviteDialog(const QString& channelId) {
|
||||
|
||||
void MainWindow::showInviteByLinkDialog(const QString& channelId) {
|
||||
if (!m_node || !m_node->isRunning()) {
|
||||
QMessageBox::warning(this, "Error", "Node is not running.");
|
||||
QMessageBox::warning(this, QCoreApplication::translate("ChatGui", "Error"), QCoreApplication::translate("ChatGui", "Node is not running."));
|
||||
return;
|
||||
}
|
||||
InviteByLinkDialog dlg(channelId, m_node, this);
|
||||
@@ -823,7 +834,7 @@ void MainWindow::showInviteByLinkDialog(const QString& channelId) {
|
||||
|
||||
void MainWindow::showJoinDialog() {
|
||||
if (!m_node || !m_node->isRunning()) {
|
||||
QMessageBox::warning(this, "Error", "Node is not running.");
|
||||
QMessageBox::warning(this, QCoreApplication::translate("ChatGui", "Error"), QCoreApplication::translate("ChatGui", "Node is not running."));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -953,11 +964,13 @@ void MainWindow::reloadChannels() {
|
||||
|
||||
void MainWindow::onCreateGroupRequested() {
|
||||
if (!m_node || !m_node->isRunning()) {
|
||||
QMessageBox::warning(this, "Error", "Node is not running.");
|
||||
QMessageBox::warning(this, QCoreApplication::translate("ChatGui", "Error"), QCoreApplication::translate("ChatGui", "Node is not running."));
|
||||
return;
|
||||
}
|
||||
if (!gui_bridge_is_uasync_ready()) {
|
||||
QMessageBox::warning(this, "Error", "uTun thread is not ready yet. Please wait and try again.");
|
||||
QMessageBox::warning(this, QCoreApplication::translate("ChatGui",
|
||||
"Error"), QCoreApplication::translate("ChatGui",
|
||||
"uTun thread is not ready yet. Please wait and try again."));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <QCoreApplication>
|
||||
#include "memberlistmodel.h"
|
||||
#include "../transport/gui_bridge.h"
|
||||
#include <QPainter>
|
||||
@@ -47,9 +48,11 @@ QVariant MemberListModel::data(const QModelIndex& index, int role) const {
|
||||
return sub;
|
||||
}
|
||||
if (role == Qt::ToolTipRole)
|
||||
return QString(it->online ? "online" : "offline") + " | 0x" +
|
||||
return (it->online ? QCoreApplication::translate("ChatGui",
|
||||
"online") : QCoreApplication::translate("ChatGui",
|
||||
"offline")) + " | 0x" +
|
||||
QString("%1").arg(it->nodeId, 16, 16, QChar('0')) +
|
||||
((it->flags & CHAT_MEMBER_FLAG_RADIO) ? tr(" | Radio enabled") : QString());
|
||||
((it->flags & CHAT_MEMBER_FLAG_RADIO) ? QCoreApplication::translate("ChatGui", " · Radio enabled") : QString());
|
||||
if (role == Qt::UserRole + 2) return it->isSelf;
|
||||
if (role == Qt::UserRole + 3) return it->flags;
|
||||
if (role == Qt::UserRole + 4) return (bool)it->storage;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <QCoreApplication>
|
||||
#include "memberpropsdialog.h"
|
||||
#include "../db/db_manager.h"
|
||||
#include "../transport/gui_bridge.h"
|
||||
@@ -25,7 +26,7 @@ MemberPropsDialog::MemberPropsDialog(DbManager* db, const QString& channelId,
|
||||
, m_flags(flags)
|
||||
, m_transferBtn(nullptr)
|
||||
{
|
||||
setWindowTitle("Member Properties");
|
||||
setWindowTitle(QCoreApplication::translate("ChatGui", "Member Properties"));
|
||||
setMinimumWidth(340);
|
||||
setModal(true);
|
||||
|
||||
@@ -80,52 +81,52 @@ MemberPropsDialog::MemberPropsDialog(DbManager* db, const QString& channelId,
|
||||
layout->setContentsMargins(20, 16, 20, 16);
|
||||
|
||||
auto* title = new QLabel(
|
||||
QString("Node: 0x%1").arg(nodeId, 16, 16, QChar('0')) +
|
||||
QCoreApplication::translate("ChatGui", "Node: 0x%1").arg(nodeId, 16, 16, QChar('0')) +
|
||||
(memberName.isEmpty() ? "" : QString(" (%1)").arg(memberName)),
|
||||
this);
|
||||
title->setStyleSheet("font-weight: bold; font-size: 13px;");
|
||||
layout->addWidget(title);
|
||||
|
||||
m_nickEdit = new QLineEdit(this);
|
||||
m_nickEdit->setPlaceholderText("Local nickname...");
|
||||
m_nickEdit->setPlaceholderText(QCoreApplication::translate("ChatGui", "Local nickname..."));
|
||||
m_nickEdit->setText(curNick);
|
||||
m_nickEdit->setEnabled(!isOwn);
|
||||
layout->addWidget(m_nickEdit);
|
||||
|
||||
m_adminCb = new QCheckBox("Admin", this);
|
||||
m_adminCb = new QCheckBox(QCoreApplication::translate("ChatGui", "Admin"), this);
|
||||
m_adminCb->setChecked(isAdmin);
|
||||
m_adminCb->setEnabled(haveKey);
|
||||
layout->addWidget(m_adminCb);
|
||||
|
||||
m_superCb = new QCheckBox("Supernode", this);
|
||||
m_superCb = new QCheckBox(QCoreApplication::translate("ChatGui", "Supernode"), this);
|
||||
m_superCb->setChecked(isSuper);
|
||||
m_superCb->setEnabled(haveKey);
|
||||
layout->addWidget(m_superCb);
|
||||
|
||||
m_storageCb = new QCheckBox("Storage (priority attachment keeper)", this);
|
||||
m_storageCb = new QCheckBox(QCoreApplication::translate("ChatGui", "Storage (priority attachment keeper)"), this);
|
||||
m_storageCb->setChecked(curStorage);
|
||||
m_storageCb->setEnabled(haveKey);
|
||||
layout->addWidget(m_storageCb);
|
||||
|
||||
m_verifiedCb = new QCheckBox("Verified", this);
|
||||
m_verifiedCb = new QCheckBox(QCoreApplication::translate("ChatGui", "Verified"), this);
|
||||
m_verifiedCb->setChecked(curVerified);
|
||||
m_verifiedCb->setEnabled(haveKey);
|
||||
layout->addWidget(m_verifiedCb);
|
||||
|
||||
m_deletedCb = new QCheckBox("Deleted", this);
|
||||
m_deletedCb = new QCheckBox(QCoreApplication::translate("ChatGui", "Deleted"), this);
|
||||
m_deletedCb->setChecked(curDeleted);
|
||||
m_deletedCb->setEnabled(haveKey);
|
||||
layout->addWidget(m_deletedCb);
|
||||
|
||||
if (!haveKey) {
|
||||
auto* hint = new QLabel("(Admin/Super require channel private key)", this);
|
||||
auto* hint = new QLabel(QCoreApplication::translate("ChatGui", "(Admin/Super require channel private key)"), this);
|
||||
hint->setStyleSheet("color: gray; font-size: 11px;");
|
||||
layout->addWidget(hint);
|
||||
}
|
||||
|
||||
m_transferBtn = new QPushButton("Transfer admin rights", this);
|
||||
m_transferBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Transfer admin rights"), this);
|
||||
m_transferBtn->setEnabled(haveKey && !isOwn);
|
||||
m_transferBtn->setToolTip(QStringLiteral("Send the channel private key to this member (encrypted + signed)"));
|
||||
m_transferBtn->setToolTip(QCoreApplication::translate("ChatGui", "Send the channel private key to this member (encrypted + signed)"));
|
||||
m_transferBtn->setStyleSheet(
|
||||
"QPushButton { border: 1px solid palette(mid); border-radius: 4px;"
|
||||
" padding: 6px 16px; font-size: 13px; background: palette(button); }"
|
||||
@@ -139,8 +140,8 @@ MemberPropsDialog::MemberPropsDialog(DbManager* db, const QString& channelId,
|
||||
auto* btnLayout = new QHBoxLayout();
|
||||
btnLayout->addStretch();
|
||||
|
||||
auto* cancelBtn = new QPushButton("Cancel", this);
|
||||
cancelBtn->setFixedWidth(90);
|
||||
auto* cancelBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Cancel"), this);
|
||||
cancelBtn->setMinimumWidth(90);
|
||||
cancelBtn->setStyleSheet(
|
||||
"QPushButton { border: 1px solid palette(mid); border-radius: 4px;"
|
||||
" padding: 6px 16px; font-size: 13px; background: palette(button); }"
|
||||
@@ -148,8 +149,8 @@ MemberPropsDialog::MemberPropsDialog(DbManager* db, const QString& channelId,
|
||||
connect(cancelBtn, &QPushButton::clicked, this, &QDialog::reject);
|
||||
btnLayout->addWidget(cancelBtn);
|
||||
|
||||
auto* saveBtn = new QPushButton("Save", this);
|
||||
saveBtn->setFixedWidth(90);
|
||||
auto* saveBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Save"), this);
|
||||
saveBtn->setMinimumWidth(90);
|
||||
saveBtn->setDefault(true);
|
||||
saveBtn->setStyleSheet(
|
||||
"QPushButton { border: none; border-radius: 4px;"
|
||||
@@ -214,10 +215,10 @@ void MemberPropsDialog::onTransferAdmin() {
|
||||
QString name = m_memberName.isEmpty() ? QString("0x%1").arg(m_nodeId, 16, 16, QChar('0')) : m_memberName;
|
||||
QMessageBox::StandardButton answer = QMessageBox::question(
|
||||
this,
|
||||
QStringLiteral("Transfer admin rights"),
|
||||
QStringLiteral("Send the channel private key to \"%1\"?\n\n"
|
||||
"The key is transferred encrypted and signed. The recipient will become a co-owner "
|
||||
"and will be able to manage members.").arg(name),
|
||||
QCoreApplication::translate("ChatGui", "Transfer admin rights"),
|
||||
QCoreApplication::translate("ChatGui",
|
||||
"Send the channel private key to \"%1\"?\n\nThe key is transferred encrypted and signed. The "
|
||||
"recipient will become a co-owner and will be able to manage members.").arg(name),
|
||||
QMessageBox::Yes | QMessageBox::No,
|
||||
QMessageBox::No);
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <QCoreApplication>
|
||||
#include "messagedelegate.h"
|
||||
#include "message_links.h"
|
||||
#include "chatview.h"
|
||||
@@ -334,20 +335,20 @@ static int measureTextHeight(const QFont &font, const QString &text, int width,
|
||||
}
|
||||
|
||||
static QString formatFileSize(qint64 bytes) {
|
||||
if (bytes < 1024) return QString("%1B").arg(bytes);
|
||||
if (bytes < 1024) return QCoreApplication::translate("ChatGui", "%1B").arg(bytes);
|
||||
double kb = bytes / 1024.0;
|
||||
if (kb < 1024.0) return QString("%1KB").arg(kb, 0, 'f', kb < 10.0 ? 1 : 0);
|
||||
if (kb < 1024.0) return QCoreApplication::translate("ChatGui", "%1KB").arg(kb, 0, 'f', kb < 10.0 ? 1 : 0);
|
||||
double mb = kb / 1024.0;
|
||||
if (mb < 1024.0) return QString("%1MB").arg(mb, 0, 'f', mb < 10.0 ? 1 : 0);
|
||||
if (mb < 1024.0) return QCoreApplication::translate("ChatGui", "%1MB").arg(mb, 0, 'f', mb < 10.0 ? 1 : 0);
|
||||
double gb = mb / 1024.0;
|
||||
return QString("%1GB").arg(gb, 0, 'f', 1);
|
||||
return QCoreApplication::translate("ChatGui", "%1GB").arg(gb, 0, 'f', 1);
|
||||
}
|
||||
|
||||
static QString formatBitrate(qint64 bytes, qint64 durMs) {
|
||||
if (bytes <= 0 || durMs <= 0) return QString();
|
||||
qint64 kbps = bytes * 8 / durMs;
|
||||
if (kbps >= 1000) return QString("%1Mbps").arg(kbps / 1000.0, 0, 'f', 1);
|
||||
return QString("%1kbps").arg(kbps);
|
||||
if (kbps >= 1000) return QCoreApplication::translate("ChatGui", "%1Mbps").arg(kbps / 1000.0, 0, 'f', 1);
|
||||
return QCoreApplication::translate("ChatGui", "%1kbps").arg(kbps);
|
||||
}
|
||||
|
||||
static int calcMaxLineWidth(const QFont &font, const QString &text, int availW) {
|
||||
@@ -1308,7 +1309,9 @@ void MessageDelegate::paint(QPainter *painter, const QStyleOptionViewItem &optio
|
||||
painter->setPen(QColor(0x80, 0x80, 0x80));
|
||||
qint64 fsz = index.data(MsgFileSizeRole).toLongLong();
|
||||
QString sizeStr = formatFileSize(fsz);
|
||||
QString stateStr = (dlState == 2) ? QString::fromUtf8("\xE2\x9C\x85") : (dlState == 1) ? "Downloading..." : "Download";
|
||||
QString stateStr = (dlState == 2) ? QString::fromUtf8("\xE2\x9C\x85") : (dlState == 1) ? QCoreApplication::translate("ChatGui",
|
||||
"Downloading...") : QCoreApplication::translate("ChatGui",
|
||||
"Download");
|
||||
QString deliveryState = index.data(MsgDeliveryStateRole).toString();
|
||||
if (!deliveryState.isEmpty()) stateStr = deliveryState;
|
||||
QString info = sizeStr + " \xC2\xB7 " + stateStr;
|
||||
@@ -1582,7 +1585,7 @@ void MessageDelegate::drawStatusBar(QPainter *p, const Layout &L,
|
||||
|
||||
QString statusText;
|
||||
if (index.data(MsgEditedRole).toBool()) {
|
||||
statusText = QString::fromUtf8("изменено ");
|
||||
statusText = QCoreApplication::translate("ChatGui", "edited ");
|
||||
}
|
||||
statusText += index.data(MsgTimeRole).toString();
|
||||
p->drawText(QRect(r.left(), r.top(), r.width(), r.height()),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <QCoreApplication>
|
||||
#include "messagelist.h"
|
||||
#include "debug_ui.h"
|
||||
#include "chatview.h"
|
||||
@@ -353,7 +354,7 @@ MessageList::MessageList(DbManager* db, AudioRecorder* recorder, QWidget *parent
|
||||
hl->addWidget(m_peerStatus);
|
||||
m_channelTitle = new QLabel(header);
|
||||
m_channelTitle->setStyleSheet("font-weight: bold; font-size: 14px;");
|
||||
m_channelTitle->setText(QStringLiteral("Канал"));
|
||||
m_channelTitle->setText(QCoreApplication::translate("ChatGui", "Channel"));
|
||||
hl->addWidget(m_channelTitle, 1);
|
||||
|
||||
m_talkingLabel = new QLabel(header);
|
||||
@@ -362,8 +363,8 @@ MessageList::MessageList(DbManager* db, AudioRecorder* recorder, QWidget *parent
|
||||
hl->addWidget(m_talkingLabel);
|
||||
|
||||
m_inviteBtn = new QPushButton(QStringLiteral("+"), header);
|
||||
m_inviteBtn->setAccessibleName(QStringLiteral("Пригласить в группу"));
|
||||
m_inviteBtn->setToolTip(QStringLiteral("Пригласить в группу"));
|
||||
m_inviteBtn->setAccessibleName(QCoreApplication::translate("ChatGui", "Invite to group"));
|
||||
m_inviteBtn->setToolTip(QCoreApplication::translate("ChatGui", "Invite to group"));
|
||||
m_inviteBtn->setFixedSize(34, 30);
|
||||
m_inviteBtn->setCursor(Qt::PointingHandCursor);
|
||||
m_inviteBtn->hide();
|
||||
@@ -380,7 +381,7 @@ MessageList::MessageList(DbManager* db, AudioRecorder* recorder, QWidget *parent
|
||||
});
|
||||
|
||||
m_callBtn = new QPushButton(QStringLiteral("☎"), header);
|
||||
m_callBtn->setAccessibleName(QStringLiteral("Позвонить"));
|
||||
m_callBtn->setAccessibleName(QCoreApplication::translate("ChatGui", "Call peer"));
|
||||
m_callBtn->setFixedSize(34, 30);
|
||||
m_callBtn->setCursor(Qt::PointingHandCursor);
|
||||
m_callBtn->hide();
|
||||
@@ -394,11 +395,11 @@ MessageList::MessageList(DbManager* db, AudioRecorder* recorder, QWidget *parent
|
||||
m_radioBtn = new QPushButton(header);
|
||||
m_radioBtn->setIcon(makeRadioIcon(header->palette().color(QPalette::ButtonText)));
|
||||
m_radioBtn->setIconSize(QSize(24, 24));
|
||||
m_radioBtn->setAccessibleName(QStringLiteral("Рация"));
|
||||
m_radioBtn->setAccessibleName(QCoreApplication::translate("ChatGui", "Radio"));
|
||||
m_radioBtn->setCheckable(true);
|
||||
m_radioBtn->setFixedSize(34, 30);
|
||||
m_radioBtn->setCursor(Qt::PointingHandCursor);
|
||||
m_radioBtn->setToolTip(QStringLiteral("Рация: включить/выключить прослушивание канала"));
|
||||
m_radioBtn->setToolTip(QCoreApplication::translate("ChatGui", "Radio: toggle channel listening"));
|
||||
m_radioBtn->setStyleSheet(
|
||||
"QPushButton { border: 1px solid palette(mid); border-radius: 6px; background: palette(base); font-size: 15px; }"
|
||||
"QPushButton:checked { background: #27ae60; border-color: #27ae60; }");
|
||||
@@ -1023,17 +1024,17 @@ void MessageList::onMessageContextMenu(const QPoint& pos) {
|
||||
bool downloaded = !filePath.isEmpty() && QFile::exists(filePath);
|
||||
|
||||
QMenu menu(m_view);
|
||||
QAction* saveAct = menu.addAction(QString::fromUtf8("Сохранить в файл"));
|
||||
QAction* saveAct = menu.addAction(QCoreApplication::translate("ChatGui", "Save to file"));
|
||||
saveAct->setEnabled(downloaded);
|
||||
QAction* dlAct = nullptr;
|
||||
if (!downloaded && !isDm) dlAct = menu.addAction(QString::fromUtf8("Скачать"));
|
||||
if (!downloaded && !isDm) dlAct = menu.addAction(QCoreApplication::translate("ChatGui", "Download"));
|
||||
|
||||
QAction* chosen = menu.exec(m_view->viewport()->mapToGlobal(pos));
|
||||
|
||||
if (chosen == saveAct) {
|
||||
QString base = displayName;
|
||||
if (base.isEmpty()) base = QFileInfo(filePath).fileName();
|
||||
QString dest = QFileDialog::getSaveFileName(this, QString::fromUtf8("Сохранить файл"), base);
|
||||
QString dest = QFileDialog::getSaveFileName(this, QCoreApplication::translate("ChatGui", "Save file"), base);
|
||||
if (dest.isEmpty()) return;
|
||||
std::thread([filePath, dest]() {
|
||||
if (!QFile::copy(filePath, dest))
|
||||
@@ -1063,9 +1064,9 @@ void MessageList::onMessageContextMenu(const QPoint& pos) {
|
||||
void MessageList::clearDmCallRoute() {
|
||||
m_callPeer = 0; m_callGroup = 0;
|
||||
m_callBtn->setEnabled(false);
|
||||
m_callBtn->setToolTip(QStringLiteral("Собеседник недоступен"));
|
||||
m_callBtn->setToolTip(QCoreApplication::translate("ChatGui", "Peer unavailable"));
|
||||
m_peerStatus->setStyleSheet("background: #E53935; border-radius: 6px;");
|
||||
m_peerStatus->setToolTip(QStringLiteral("Не в сети"));
|
||||
m_peerStatus->setToolTip(QCoreApplication::translate("ChatGui", "Offline"));
|
||||
}
|
||||
|
||||
void MessageList::refreshDmCallRoute() {
|
||||
@@ -1095,8 +1096,8 @@ void MessageList::onDmCallRoute(const uint8_t* data, int len) {
|
||||
m_callPeer = peer; m_callGroup = group;
|
||||
m_callBtn->setEnabled(peer && group);
|
||||
m_peerStatus->setStyleSheet(group ? "background: #4CAF50; border-radius: 6px;" : "background: #E53935; border-radius: 6px;");
|
||||
m_peerStatus->setToolTip(group ? QStringLiteral("В сети") : QStringLiteral("Не в сети"));
|
||||
m_callBtn->setToolTip(group ? QStringLiteral("Позвонить") : QStringLiteral("Собеседник недоступен"));
|
||||
m_peerStatus->setToolTip(group ? QCoreApplication::translate("ChatGui", "Online") : QCoreApplication::translate("ChatGui", "Offline"));
|
||||
m_callBtn->setToolTip(group ? QCoreApplication::translate("ChatGui", "Call peer") : QCoreApplication::translate("ChatGui", "Peer unavailable"));
|
||||
}
|
||||
|
||||
bool MessageList::isDmChannel(const QString& channelId) {
|
||||
@@ -1155,7 +1156,7 @@ void MessageList::onDmMessages(const uint8_t* data, int len) {
|
||||
const QString inviteLink = invite ? text : QString();
|
||||
if (invite) {
|
||||
const QString groupName = message.value("tags").toObject().value("group_name").toString();
|
||||
text = tr("Приглашение в группу «%1»").arg(groupName) + '\n' + inviteLink;
|
||||
text = QCoreApplication::translate("ChatGui", "Invitation to group “%1”").arg(groupName) + '\n' + inviteLink;
|
||||
GUI_DEBUG("invite PM: rendered conv=%s seq=%lld group_name_bytes=%d link_bytes=%d", qPrintable(m_currentChannelId),
|
||||
(long long)seq, (int)groupName.toUtf8().size(), (int)inviteLink.size());
|
||||
}
|
||||
@@ -1163,12 +1164,14 @@ void MessageList::onDmMessages(const uint8_t* data, int len) {
|
||||
QString state = message.value("media_status").toString();
|
||||
QString status;
|
||||
if (file) {
|
||||
if (state == "delivered") status = QString::fromUtf8("Доставлено");
|
||||
else if (state == "expired") status = QString::fromUtf8("Срок хранения истёк");
|
||||
else if (dir == 1) status = QString::fromUtf8("Ожидает доставки");
|
||||
else if (state == "ready") status = QString::fromUtf8("Получено");
|
||||
else status = QString::fromUtf8("Загрузка…");
|
||||
} else if (dir == 1) status = message.value("pending").toBool() ? QString::fromUtf8("Ожидает доставки") : QString::fromUtf8("Доставлено");
|
||||
if (state == "delivered") status = QCoreApplication::translate("ChatGui", "Delivered");
|
||||
else if (state == "expired") status = QCoreApplication::translate("ChatGui", "Storage period expired");
|
||||
else if (dir == 1) status = QCoreApplication::translate("ChatGui", "Awaiting delivery");
|
||||
else if (state == "ready") status = QCoreApplication::translate("ChatGui", "Received");
|
||||
else status = QCoreApplication::translate("ChatGui", "Downloading…");
|
||||
} else if (dir == 1) status = message.value("pending").toBool() ? QCoreApplication::translate("ChatGui",
|
||||
"Awaiting delivery") : QCoreApplication::translate("ChatGui",
|
||||
"Delivered");
|
||||
|
||||
AccountRow acc = m_db ? m_db->getAccount(author) : AccountRow();
|
||||
QString name = authorName(author);
|
||||
@@ -1228,7 +1231,7 @@ void MessageList::onRadioTalk(quint64 groupId, quint64 srcNodeId, bool on) {
|
||||
if (on) {
|
||||
QString name = authorName(srcNodeId);
|
||||
if (name.isEmpty()) name = QStringLiteral("0x%1").arg(srcNodeId, 0, 16);
|
||||
m_talkingLabel->setText(QString::fromUtf8("\xF0\x9F\x8E\x99 %1 говорит").arg(name));
|
||||
m_talkingLabel->setText(QCoreApplication::translate("ChatGui", "🎙 %1 is speaking").arg(name));
|
||||
m_talkingLabel->show();
|
||||
} else {
|
||||
m_talkingLabel->hide();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <QCoreApplication>
|
||||
// networksettingspage.cpp — Network configuration form (список listen-сокетов)
|
||||
#include "networksettingspage.h"
|
||||
#include "../transport/config_updater.h"
|
||||
@@ -13,7 +14,7 @@
|
||||
#include <QFile>
|
||||
#include <QTextStream>
|
||||
|
||||
static const QString AUTO_IP_LABEL = "Auto (0.0.0.0)";
|
||||
static QString autoIpLabel() { return QCoreApplication::translate("ChatGui", "Auto (0.0.0.0)"); }
|
||||
|
||||
NetworkSettingsPage::NetworkSettingsPage(QWidget* parent)
|
||||
: QWidget(parent)
|
||||
@@ -22,11 +23,11 @@ NetworkSettingsPage::NetworkSettingsPage(QWidget* parent)
|
||||
layout->setSpacing(12);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
auto* title = new QLabel("Listen Sockets", this);
|
||||
auto* title = new QLabel(QCoreApplication::translate("ChatGui", "Listen Sockets"), this);
|
||||
title->setStyleSheet("font-weight: bold; font-size: 13px;");
|
||||
layout->addWidget(title);
|
||||
|
||||
m_autoSocketsCheck = new QCheckBox("Auto sockets (UDP+TCP from interfaces)", this);
|
||||
m_autoSocketsCheck = new QCheckBox(QCoreApplication::translate("ChatGui", "Auto sockets (UDP+TCP from interfaces)"), this);
|
||||
layout->addWidget(m_autoSocketsCheck);
|
||||
|
||||
m_serverList = new QListWidget(this);
|
||||
@@ -34,8 +35,8 @@ NetworkSettingsPage::NetworkSettingsPage(QWidget* parent)
|
||||
layout->addWidget(m_serverList);
|
||||
|
||||
auto* btnRow = new QHBoxLayout();
|
||||
m_addBtn = new QPushButton("Add", this);
|
||||
m_removeBtn = new QPushButton("Remove", this);
|
||||
m_addBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Add"), this);
|
||||
m_removeBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Remove"), this);
|
||||
btnRow->addWidget(m_addBtn);
|
||||
btnRow->addWidget(m_removeBtn);
|
||||
btnRow->addStretch();
|
||||
@@ -44,41 +45,44 @@ NetworkSettingsPage::NetworkSettingsPage(QWidget* parent)
|
||||
// --- форма правки выбранного сокета ---
|
||||
auto* formLayout = new QGridLayout();
|
||||
|
||||
formLayout->addWidget(new QLabel("Name:", this), 0, 0);
|
||||
formLayout->addWidget(new QLabel(QCoreApplication::translate("ChatGui", "Name:"), this), 0, 0);
|
||||
m_nameEdit = new QLineEdit(this);
|
||||
m_nameEdit->setMinimumWidth(180);
|
||||
formLayout->addWidget(m_nameEdit, 0, 1);
|
||||
|
||||
formLayout->addWidget(new QLabel("IP Address:", this), 1, 0);
|
||||
formLayout->addWidget(new QLabel(QCoreApplication::translate("ChatGui", "IP Address:"), this), 1, 0);
|
||||
m_ipCombo = new QComboBox(this);
|
||||
m_ipCombo->setEditable(true);
|
||||
m_ipCombo->setMinimumWidth(180);
|
||||
formLayout->addWidget(m_ipCombo, 1, 1);
|
||||
|
||||
formLayout->addWidget(new QLabel("Port:", this), 2, 0);
|
||||
formLayout->addWidget(new QLabel(QCoreApplication::translate("ChatGui", "Port:"), this), 2, 0);
|
||||
m_portSpin = new QSpinBox(this);
|
||||
m_portSpin->setRange(1, 65535);
|
||||
m_portSpin->setValue(1333);
|
||||
formLayout->addWidget(m_portSpin, 2, 1);
|
||||
|
||||
formLayout->addWidget(new QLabel("Protocol:", this), 3, 0);
|
||||
formLayout->addWidget(new QLabel(QCoreApplication::translate("ChatGui", "Protocol:"), this), 3, 0);
|
||||
m_protoCombo = new QComboBox(this);
|
||||
m_protoCombo->addItems({"UDP", "TCP"});
|
||||
formLayout->addWidget(m_protoCombo, 3, 1);
|
||||
|
||||
formLayout->addWidget(new QLabel("Type:", this), 4, 0);
|
||||
formLayout->addWidget(new QLabel(QCoreApplication::translate("ChatGui", "Type:"), this), 4, 0);
|
||||
m_typeCombo = new QComboBox(this);
|
||||
m_typeCombo->addItems({"public", "nat", "private", "local"});
|
||||
m_typeCombo->addItem(QCoreApplication::translate("ChatGui", "Public"), QStringLiteral("public"));
|
||||
m_typeCombo->addItem(QStringLiteral("NAT"), QStringLiteral("nat"));
|
||||
m_typeCombo->addItem(QCoreApplication::translate("ChatGui", "Private"), QStringLiteral("private"));
|
||||
m_typeCombo->addItem(QCoreApplication::translate("ChatGui", "Local"), QStringLiteral("local"));
|
||||
formLayout->addWidget(m_typeCombo, 4, 1);
|
||||
|
||||
formLayout->addWidget(new QLabel("Interface:", this), 5, 0);
|
||||
formLayout->addWidget(new QLabel(QCoreApplication::translate("ChatGui", "Interface:"), this), 5, 0);
|
||||
m_interfaceCombo = new QComboBox(this);
|
||||
m_interfaceCombo->setMinimumWidth(180);
|
||||
formLayout->addWidget(m_interfaceCombo, 5, 1);
|
||||
|
||||
layout->addLayout(formLayout);
|
||||
|
||||
auto* note = new QLabel("Changes take effect after restart.", this);
|
||||
auto* note = new QLabel(QCoreApplication::translate("ChatGui", "Changes take effect after restart."), this);
|
||||
note->setStyleSheet("color: gray; font-size: 11px;");
|
||||
layout->addWidget(note);
|
||||
layout->addStretch();
|
||||
@@ -111,7 +115,7 @@ static void splitAddr(const QString& addr, QString& ip, int& port) {
|
||||
}
|
||||
|
||||
void NetworkSettingsPage::populateInterfaces() {
|
||||
m_interfaceCombo->addItem("Auto (any)");
|
||||
m_interfaceCombo->addItem(QCoreApplication::translate("ChatGui", "Auto (any)"));
|
||||
for (const QNetworkInterface& iface : QNetworkInterface::allInterfaces()) {
|
||||
if (iface.flags() & QNetworkInterface::IsLoopBack) continue;
|
||||
if (!(iface.flags() & QNetworkInterface::IsUp)) continue;
|
||||
@@ -120,7 +124,7 @@ void NetworkSettingsPage::populateInterfaces() {
|
||||
}
|
||||
|
||||
void NetworkSettingsPage::populateAddresses() {
|
||||
m_ipCombo->addItem(AUTO_IP_LABEL);
|
||||
m_ipCombo->addItem(autoIpLabel());
|
||||
for (const QNetworkInterface& iface : QNetworkInterface::allInterfaces()) {
|
||||
if (iface.flags() & QNetworkInterface::IsLoopBack) continue;
|
||||
if (!(iface.flags() & QNetworkInterface::IsUp)) continue;
|
||||
@@ -166,11 +170,11 @@ void NetworkSettingsPage::loadFormFromSelected() {
|
||||
m_nameEdit->setText(s.name);
|
||||
QString ip; int port = 0;
|
||||
splitAddr(s.addr, ip, port);
|
||||
if (ip == "0.0.0.0") ip = AUTO_IP_LABEL;
|
||||
if (ip == "0.0.0.0") ip = autoIpLabel();
|
||||
m_ipCombo->setCurrentText(ip);
|
||||
m_portSpin->setValue(port > 0 ? port : 1333);
|
||||
m_protoCombo->setCurrentIndex(s.transport == "tcp" ? 1 : 0);
|
||||
int typeIdx = m_typeCombo->findText(s.type);
|
||||
int typeIdx = m_typeCombo->findData(s.type);
|
||||
m_typeCombo->setCurrentIndex(typeIdx >= 0 ? typeIdx : 0);
|
||||
int ifIdx = m_interfaceCombo->findText(s.netif);
|
||||
m_interfaceCombo->setCurrentIndex(ifIdx >= 0 ? ifIdx : 0);
|
||||
@@ -185,13 +189,13 @@ void NetworkSettingsPage::updateSelectedServerFromForm() {
|
||||
s.name = m_nameEdit->text().trimmed();
|
||||
|
||||
QString ip = m_ipCombo->currentText().trimmed();
|
||||
if (ip == AUTO_IP_LABEL) ip = "0.0.0.0";
|
||||
if (ip == autoIpLabel()) ip = "0.0.0.0";
|
||||
s.addr = ip + ":" + QString::number(m_portSpin->value());
|
||||
|
||||
s.transport = (m_protoCombo->currentIndex() == 1) ? "tcp" : "udp";
|
||||
s.type = m_typeCombo->currentText().trimmed();
|
||||
s.type = m_typeCombo->currentData().toString();
|
||||
QString iface = m_interfaceCombo->currentText();
|
||||
s.netif = iface.startsWith("Auto") ? QString() : iface;
|
||||
s.netif = m_interfaceCombo->currentIndex() == 0 ? QString() : iface;
|
||||
}
|
||||
|
||||
void NetworkSettingsPage::updateSelectedListItemText() {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <QCoreApplication>
|
||||
// nodespage.cpp — Nodes table + dump in settings
|
||||
#include "nodespage.h"
|
||||
#include "../db/db_manager.h"
|
||||
@@ -59,12 +60,15 @@ NodesPage::NodesPage(DbManager* db, QWidget* parent)
|
||||
layout->setSpacing(8);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
auto* title = new QLabel("Known Nodes", this);
|
||||
auto* title = new QLabel(QCoreApplication::translate("ChatGui", "Known Nodes"), this);
|
||||
title->setStyleSheet("font-weight: bold; font-size: 13px;");
|
||||
layout->addWidget(title);
|
||||
|
||||
m_table = new QTableWidget(0, 5, this);
|
||||
m_table->setHorizontalHeaderLabels({"Node ID", "Name", "x25519", "ed25519", "Addrs"});
|
||||
m_table->setHorizontalHeaderLabels({QCoreApplication::translate("ChatGui",
|
||||
"Node ID"), QCoreApplication::translate("ChatGui",
|
||||
"Name"), "x25519", "ed25519", QCoreApplication::translate("ChatGui",
|
||||
"Addrs")});
|
||||
m_table->horizontalHeader()->setStretchLastSection(true);
|
||||
m_table->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
m_table->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
@@ -78,7 +82,7 @@ NodesPage::NodesPage(DbManager* db, QWidget* parent)
|
||||
" font-weight: bold; font-size: 9px; }");
|
||||
layout->addWidget(m_table, 2);
|
||||
|
||||
auto* dumpLabel = new QLabel("Details", this);
|
||||
auto* dumpLabel = new QLabel(QCoreApplication::translate("ChatGui", "Details"), this);
|
||||
dumpLabel->setStyleSheet("font-weight: bold; font-size: 12px; margin-top: 8px;");
|
||||
layout->addWidget(dumpLabel);
|
||||
|
||||
@@ -88,13 +92,13 @@ NodesPage::NodesPage(DbManager* db, QWidget* parent)
|
||||
m_dumpText->setStyleSheet(
|
||||
"QPlainTextEdit { border: 1px solid palette(mid); border-radius: 4px;"
|
||||
" padding: 8px; background: palette(base); }");
|
||||
m_dumpText->setPlaceholderText("Click Refresh to load nodes");
|
||||
m_dumpText->setPlaceholderText(QCoreApplication::translate("ChatGui", "Click Refresh to load nodes"));
|
||||
layout->addWidget(m_dumpText, 3);
|
||||
|
||||
auto* btnLayout = new QHBoxLayout();
|
||||
btnLayout->addStretch();
|
||||
m_refreshBtn = new QPushButton("Refresh", this);
|
||||
m_refreshBtn->setFixedWidth(90);
|
||||
m_refreshBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Refresh"), this);
|
||||
m_refreshBtn->setMinimumWidth(90);
|
||||
m_refreshBtn->setStyleSheet(
|
||||
"QPushButton { border: 1px solid palette(mid); border-radius: 4px;"
|
||||
" padding: 6px 16px; font-size: 13px; background: palette(button); }"
|
||||
@@ -109,7 +113,7 @@ void NodesPage::onRefreshClicked() { refreshNodes(); }
|
||||
void NodesPage::refreshNodes() {
|
||||
if (!m_db || !m_db->m_db) {
|
||||
m_table->setRowCount(0);
|
||||
m_dumpText->setPlainText(QString("Database not connected (m_db=%1, m_db->m_db=%2)")
|
||||
m_dumpText->setPlainText(QCoreApplication::translate("ChatGui", "Database not connected (m_db=%1, m_db->m_db=%2)")
|
||||
.arg((quintptr)m_db, 0, 16).arg(m_db ? (quintptr)m_db->m_db : 0, 0, 16));
|
||||
qWarning("NodesPage::refreshNodes: DB not ready m_db=%p m_db->m_db=%p", (void*)m_db, m_db ? (void*)m_db->m_db : nullptr);
|
||||
return;
|
||||
@@ -167,7 +171,7 @@ void NodesPage::refreshNodes() {
|
||||
"SELECT node_id, name, x25519_pubkey, ed25519_pubkey,"
|
||||
" last_seen_at, created_at FROM nodes ORDER BY node_id",
|
||||
-1, &st, nullptr);
|
||||
if (!st) { m_dumpText->setPlainText("Query error"); return; }
|
||||
if (!st) { m_dumpText->setPlainText(QCoreApplication::translate("ChatGui", "Query error")); return; }
|
||||
|
||||
int nodeCount = 0;
|
||||
while (sqlite3_step(st) == SQLITE_ROW) {
|
||||
@@ -181,15 +185,17 @@ void NodesPage::refreshNodes() {
|
||||
qint64 last_seen = sqlite3_column_int64(st, 4);
|
||||
qint64 created = sqlite3_column_int64(st, 5);
|
||||
|
||||
dump += QString("Node: %1").arg(hex16(nid));
|
||||
dump += QCoreApplication::translate("ChatGui", "Node: %1").arg(hex16(nid));
|
||||
if (!name.isEmpty()) dump += QString(" (%1)").arg(name);
|
||||
dump += QString(" id=0x%1\n").arg(nid, 16, 16, QChar('0'));
|
||||
dump += QString(" x25519: %1\n").arg(x25.isEmpty() ? "-" : shortenKey(x25));
|
||||
dump += QString(" ed25519: %1\n").arg(ed.isEmpty() ? "-" : shortenKey(ed));
|
||||
if (last_seen > 0)
|
||||
dump += QString(" last_seen: %1\n").arg(QDateTime::fromSecsSinceEpoch(last_seen).toString("yyyy-MM-dd hh:mm:ss"));
|
||||
dump += QCoreApplication::translate("ChatGui",
|
||||
" last seen: %1\n").arg(QDateTime::fromSecsSinceEpoch(last_seen).toString("yyyy-MM-dd hh:mm:ss"));
|
||||
if (created > 0)
|
||||
dump += QString(" created: %1\n").arg(QDateTime::fromSecsSinceEpoch(created).toString("yyyy-MM-dd hh:mm:ss"));
|
||||
dump += QCoreApplication::translate("ChatGui",
|
||||
" created: %1\n").arg(QDateTime::fromSecsSinceEpoch(created).toString("yyyy-MM-dd hh:mm:ss"));
|
||||
|
||||
sqlite3_stmt* na = nullptr;
|
||||
sqlite3_prepare_v2(d,
|
||||
@@ -200,7 +206,7 @@ void NodesPage::refreshNodes() {
|
||||
sqlite3_bind_int64(na, 1, (sqlite3_int64)nid);
|
||||
int ac = 0;
|
||||
while (sqlite3_step(na) == SQLITE_ROW) {
|
||||
if (ac == 0) dump += " addresses:\n";
|
||||
if (ac == 0) dump += QCoreApplication::translate("ChatGui", " addresses:\n");
|
||||
int family = sqlite3_column_int(na, 0);
|
||||
int proto = sqlite3_column_int(na, 1);
|
||||
const void* addr = sqlite3_column_blob(na, 2);
|
||||
@@ -219,14 +225,14 @@ void NodesPage::refreshNodes() {
|
||||
}
|
||||
ac++;
|
||||
}
|
||||
if (ac == 0) dump += " addresses: none\n";
|
||||
if (ac == 0) dump += QCoreApplication::translate("ChatGui", " addresses: none\n");
|
||||
sqlite3_finalize(na);
|
||||
} else {
|
||||
dump += " addresses: none\n";
|
||||
dump += QCoreApplication::translate("ChatGui", " addresses: none\n");
|
||||
}
|
||||
}
|
||||
sqlite3_finalize(st);
|
||||
|
||||
if (nodeCount == 0) dump = "No nodes found.";
|
||||
if (nodeCount == 0) dump = QCoreApplication::translate("ChatGui", "No nodes found.");
|
||||
m_dumpText->setPlainText(dump);
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <QCoreApplication>
|
||||
#include "ptt_hotkey_manager.h"
|
||||
#include "radio_audio_engine.h"
|
||||
#include "../db/db_manager.h"
|
||||
@@ -196,7 +197,7 @@ QString PttHotkeyManager::keyName(int key) const {
|
||||
if (name) return QString::fromLatin1(name);
|
||||
}
|
||||
#endif
|
||||
return QString("Key %1").arg(key);
|
||||
return QCoreApplication::translate("ChatGui", "Key %1").arg(key);
|
||||
}
|
||||
|
||||
QString PttHotkeyManager::describe(const PttKeyChord& chord) const {
|
||||
@@ -204,7 +205,7 @@ QString PttHotkeyManager::describe(const PttKeyChord& chord) const {
|
||||
std::sort(keys.begin(), keys.end());
|
||||
QStringList names;
|
||||
for (int key : keys) names << keyName(key);
|
||||
return names.isEmpty() ? QStringLiteral("Не задано") : names.join(" + ");
|
||||
return names.isEmpty() ? QCoreApplication::translate("ChatGui", "Not set") : names.join(" + ");
|
||||
}
|
||||
|
||||
bool PttHotkeyManager::usesPortal() {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <QCoreApplication>
|
||||
#include "ptt_portal.h"
|
||||
#include "../../lib/debug_config.h"
|
||||
|
||||
@@ -50,7 +51,7 @@ void PttPortal::setStatus(const QString& text) { m_status = text; emit statusCha
|
||||
void PttPortal::initialize() {
|
||||
if (busy() || m_state == State::Ready || m_state == State::Unbound) return;
|
||||
m_state = State::Checking;
|
||||
setStatus("Подключение глобальных хоткеев…");
|
||||
setStatus(QCoreApplication::translate("ChatGui", "Connecting global shortcuts…"));
|
||||
auto message = QDBusMessage::createMethodCall(kService, kPath, "org.freedesktop.DBus.Properties", "Get");
|
||||
message.setArguments({kInterface, "version"});
|
||||
auto* watcher = new QDBusPendingCallWatcher(QDBusConnection::sessionBus().asyncCall(message), this);
|
||||
@@ -108,7 +109,7 @@ void PttPortal::response(uint code, const QVariantMap& results, const QDBusMessa
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_RADIO, "PTT portal: response state=%d code=%u", int(state), code);
|
||||
if (code != 0) {
|
||||
fail(QString("request %1: response=%2%3").arg(int(state)).arg(code).arg(code == 1 ? " (cancelled)" : ""));
|
||||
if (code == 1) setStatus("Назначение отменено. Нажмите кнопку, чтобы повторить.");
|
||||
if (code == 1) setStatus(QCoreApplication::translate("ChatGui", "Assignment cancelled. Click the button to try again."));
|
||||
return;
|
||||
}
|
||||
if (state == State::Creating) {
|
||||
@@ -127,11 +128,11 @@ void PttPortal::response(uint code, const QVariantMap& results, const QDBusMessa
|
||||
m_configureAfterBind = known.size() == 3 && m_configureRequested;
|
||||
m_state = State::Unbound;
|
||||
if (!known.isEmpty() || m_configureRequested) bind();
|
||||
else setStatus("Назначьте сочетание в системных настройках.");
|
||||
else setStatus(QCoreApplication::translate("ChatGui", "Assign a shortcut in system settings."));
|
||||
} else if (state == State::Binding) {
|
||||
m_state = State::Ready;
|
||||
updateDescriptions(qdbus_cast<PttPortalShortcuts>(results.value("shortcuts")));
|
||||
setStatus("Глобальные хоткеи подключены.");
|
||||
setStatus(QCoreApplication::translate("ChatGui", "Global shortcuts connected."));
|
||||
DEBUG_INFO(DEBUG_CATEGORY_RADIO, "PTT portal: bindings ready session=%s", qPrintable(m_session));
|
||||
// Первое BindShortcuts само открывает окно назначения, повторное Configure — отдельный вызов.
|
||||
m_configureRequested = false;
|
||||
@@ -143,9 +144,10 @@ bool PttPortal::isPttId(const QString& id) { return id == "ptt1" || id == "ptt2"
|
||||
|
||||
void PttPortal::bind() {
|
||||
PttPortalShortcuts shortcuts;
|
||||
for (int i = 1; i <= 3; i++) shortcuts << PttPortalShortcut{QString("ptt%1").arg(i), {{"description", QString("Рация: передача %1").arg(i)}}};
|
||||
for (int i = 1; i <= 3; i++) shortcuts << PttPortalShortcut{QString("ptt%1").arg(i), {{"description", QCoreApplication::translate("ChatGui",
|
||||
"Radio: transmit %1").arg(i)}}};
|
||||
m_state = State::Binding;
|
||||
setStatus("Назначение глобальных хоткеев…");
|
||||
setStatus(QCoreApplication::translate("ChatGui", "Assigning global shortcuts…"));
|
||||
request("BindShortcuts", {QVariant::fromValue(QDBusObjectPath(m_session)), QVariant::fromValue(shortcuts), m_parentWindow}, {});
|
||||
}
|
||||
|
||||
@@ -230,6 +232,6 @@ void PttPortal::fail(const QString& detail) {
|
||||
m_configureRequested = m_configureAfterBind = false;
|
||||
m_state = State::Failed;
|
||||
m_descriptions = {"", "", ""}; emit descriptionsChanged();
|
||||
setStatus("Глобальные хоткеи недоступны. Повторите назначение; подробности в логе radio.");
|
||||
setStatus(QCoreApplication::translate("ChatGui", "Global shortcuts unavailable. Try assigning again; see the radio log for details."));
|
||||
}
|
||||
void PttPortal::sessionClosed() { fail("portal session closed"); }
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <QCoreApplication>
|
||||
#include "radio_audio_engine.h"
|
||||
#include "sound_manager.h"
|
||||
#include "../transport/audio_diagnostics.h"
|
||||
@@ -82,7 +83,7 @@ void RadioAudioEngine::loadSettings(const QString& configPath) {
|
||||
/* Проверяем значения и атомарно сохраняем INI до отправки команды в ядро. */
|
||||
bool RadioAudioEngine::applySettings(const QMap<QString, int>& values) {
|
||||
if (!m_settingsReady || m_settingsBusy || !gui_bridge_is_uasync_ready() || !gui_bridge_get_inst()) {
|
||||
m_settingsError = QStringLiteral("Настройки рации ещё не готовы");
|
||||
m_settingsError = QCoreApplication::translate("ChatGui", "Radio settings are not ready yet");
|
||||
DEBUG_WARN(DEBUG_CATEGORY_RADIO, "RadioAudio: cannot apply settings ready=%d busy=%d", m_settingsReady, m_settingsBusy);
|
||||
emit stateChanged();
|
||||
return false;
|
||||
@@ -92,7 +93,7 @@ bool RadioAudioEngine::applySettings(const QMap<QString, int>& values) {
|
||||
size_t index = 0;
|
||||
while (index < next.size() && it.key() != QLatin1String(settingKeys[index])) ++index;
|
||||
if (index == next.size() || it.value() < settingMin[index] || it.value() > settingMax[index]) {
|
||||
m_settingsError = QStringLiteral("Некорректная настройка рации: %1").arg(it.key());
|
||||
m_settingsError = QCoreApplication::translate("ChatGui", "Invalid radio setting: %1").arg(it.key());
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "RadioAudio: invalid setting %s=%d", qPrintable(it.key()), it.value());
|
||||
emit stateChanged();
|
||||
return false;
|
||||
@@ -103,7 +104,7 @@ bool RadioAudioEngine::applySettings(const QMap<QString, int>& values) {
|
||||
if (next != m_settings && !m_configPath.isEmpty()) {
|
||||
QFile input(m_configPath);
|
||||
if (!input.open(QIODevice::ReadOnly)) {
|
||||
m_settingsError = QStringLiteral("Не удалось прочитать конфигурацию: %1").arg(input.errorString());
|
||||
m_settingsError = QCoreApplication::translate("ChatGui", "Failed to read configuration: %1").arg(input.errorString());
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "RadioAudio: read config failed: %s", qPrintable(input.errorString()));
|
||||
emit stateChanged();
|
||||
return false;
|
||||
@@ -116,7 +117,7 @@ bool RadioAudioEngine::applySettings(const QMap<QString, int>& values) {
|
||||
if (!data.endsWith('\n')) data.append('\n');
|
||||
QSaveFile output(m_configPath);
|
||||
if (!output.open(QIODevice::WriteOnly) || output.write(data) != data.size() || !output.commit()) {
|
||||
m_settingsError = QStringLiteral("Не удалось сохранить конфигурацию: %1").arg(output.errorString());
|
||||
m_settingsError = QCoreApplication::translate("ChatGui", "Failed to save configuration: %1").arg(output.errorString());
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "RadioAudio: save config failed: %s", qPrintable(output.errorString()));
|
||||
emit stateChanged();
|
||||
return false;
|
||||
@@ -131,7 +132,7 @@ void RadioAudioEngine::postSettings(const std::array<int, 6>& values, bool apply
|
||||
auto* inst = gui_bridge_get_inst();
|
||||
void* memory = inst && gui_bridge_is_uasync_ready() ? u_malloc(sizeof(SettingsRequest)) : nullptr;
|
||||
if (!memory) {
|
||||
m_settingsError = QStringLiteral("Не удалось отправить настройки рации в ядро");
|
||||
m_settingsError = QCoreApplication::translate("ChatGui", "Failed to send radio settings to the core");
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "RadioAudio: settings request unavailable inst=%d uasync=%d",
|
||||
inst != nullptr, gui_bridge_is_uasync_ready());
|
||||
emit stateChanged();
|
||||
@@ -164,7 +165,7 @@ void RadioAudioEngine::postSettings(const std::array<int, 6>& values, bool apply
|
||||
engine->m_settings = snapshot;
|
||||
engine->m_settingsReady = true;
|
||||
engine->m_settingsBusy = false;
|
||||
if (!success) engine->m_settingsError = QStringLiteral("Ядро отклонило настройки рации");
|
||||
if (!success) engine->m_settingsError = QCoreApplication::translate("ChatGui", "The core rejected radio settings");
|
||||
DEBUG_INFO(DEBUG_CATEGORY_RADIO, "RadioAudio: settings applied=%d vad=%d threshold=%d hangover=%dms TX_AGC=%d "
|
||||
"TX_gain=%ddB VAD_gain=%ddB", success, snapshot[0], snapshot[1], snapshot[2], snapshot[3], snapshot[4], snapshot[5]);
|
||||
if ((changed || apply) && engine->m_active) engine->reconfigureCapture();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <QCoreApplication>
|
||||
#include "radiopanel.h"
|
||||
#include "radio_audio_engine.h"
|
||||
#include "sound_manager.h"
|
||||
@@ -24,8 +25,8 @@ RadioPanel::RadioPanel(QWidget* parent) : QWidget(parent) {
|
||||
row->setSpacing(8);
|
||||
m_input = new QToolButton(this);
|
||||
m_output = new QToolButton(this);
|
||||
m_input->setText(QStringLiteral("Микрофон"));
|
||||
m_output->setText(QStringLiteral("Выход звука"));
|
||||
m_input->setText(QCoreApplication::translate("ChatGui", "Microphone"));
|
||||
m_output->setText(QCoreApplication::translate("ChatGui", "Audio output"));
|
||||
m_input->setObjectName(QStringLiteral("radioInputButton"));
|
||||
m_output->setObjectName(QStringLiteral("radioOutputButton"));
|
||||
for (auto* button : {m_input, m_output}) {
|
||||
@@ -43,15 +44,15 @@ RadioPanel::RadioPanel(QWidget* parent) : QWidget(parent) {
|
||||
row->addWidget(m_status, 1);
|
||||
m_vad = new QPushButton(QStringLiteral("VAD"), this);
|
||||
m_vad->setObjectName(QStringLiteral("radioVadButton"));
|
||||
m_vad->setAccessibleName(QStringLiteral("Автоматическая передача по голосу"));
|
||||
m_vad->setAccessibleName(QCoreApplication::translate("ChatGui", "Automatic voice transmission"));
|
||||
m_vad->setCheckable(true);
|
||||
m_vad->setFixedSize(52, 34);
|
||||
m_vad->setStyleSheet("QPushButton:checked { background: #3390EC; color: white; }");
|
||||
row->addWidget(m_vad);
|
||||
m_ptt = new QPushButton(QStringLiteral("PTT"), this);
|
||||
m_ptt->setObjectName(QStringLiteral("radioPttButton"));
|
||||
m_ptt->setAccessibleName(QStringLiteral("Держите и говорите"));
|
||||
m_ptt->setToolTip(QStringLiteral("Держите кнопку или глобальный хоткей для передачи"));
|
||||
m_ptt->setAccessibleName(QCoreApplication::translate("ChatGui", "Hold to talk"));
|
||||
m_ptt->setToolTip(QCoreApplication::translate("ChatGui", "Hold the button or a global shortcut to transmit"));
|
||||
m_ptt->setFixedSize(64, 34);
|
||||
m_ptt->setStyleSheet("QPushButton[transmitting=\"true\"] { background: #27ae60; color: white; }");
|
||||
row->addWidget(m_ptt);
|
||||
@@ -82,16 +83,16 @@ void RadioPanel::updateState() {
|
||||
m_ptt->update();
|
||||
}
|
||||
QString state;
|
||||
if (!engine->settingsError().isEmpty()) state = QStringLiteral("Ошибка настройки");
|
||||
else if (engine->settingsBusy()) state = QStringLiteral("Применение настроек…");
|
||||
else if (!engine->captureAvailable()) state = QStringLiteral("Микрофон недоступен");
|
||||
else if (engine->vadEnabled() && !engine->vadAvailable()) state = QStringLiteral("VAD недоступен");
|
||||
else if (transmitting) state = QStringLiteral("Передача");
|
||||
else state = engine->vadEnabled() ? QStringLiteral("VAD слушает") : QStringLiteral("Приём");
|
||||
if (!engine->settingsError().isEmpty()) state = QCoreApplication::translate("ChatGui", "Settings error");
|
||||
else if (engine->settingsBusy()) state = QCoreApplication::translate("ChatGui", "Applying settings…");
|
||||
else if (!engine->captureAvailable()) state = QCoreApplication::translate("ChatGui", "Microphone unavailable");
|
||||
else if (engine->vadEnabled() && !engine->vadAvailable()) state = QCoreApplication::translate("ChatGui", "VAD unavailable");
|
||||
else if (transmitting) state = QCoreApplication::translate("ChatGui", "Transmitting");
|
||||
else state = engine->vadEnabled() ? QCoreApplication::translate("ChatGui", "VAD listening") : QCoreApplication::translate("ChatGui", "Receiving");
|
||||
m_status->setText(state);
|
||||
m_status->setToolTip(engine->settingsError().isEmpty() ? state : engine->settingsError());
|
||||
m_vad->setToolTip(engine->vadEnabled() ? QStringLiteral("Выключить автоматическую передачу по голосу")
|
||||
: QStringLiteral("Включить автоматическую передачу по голосу"));
|
||||
m_vad->setToolTip(engine->vadEnabled() ? QCoreApplication::translate("ChatGui", "Disable automatic voice transmission")
|
||||
: QCoreApplication::translate("ChatGui", "Enable automatic voice transmission"));
|
||||
}
|
||||
|
||||
/* Список перечитывается при открытии меню: подключённые устройства видны без перезапуска. */
|
||||
@@ -107,7 +108,7 @@ void RadioPanel::populateDevices(QToolButton* button, bool capture) {
|
||||
group->addAction(action);
|
||||
connect(action, &QAction::triggered, this, [this, id, capture]() { selectDevice(id, capture); });
|
||||
};
|
||||
add(QStringLiteral("По умолчанию (авто)"), QString());
|
||||
add(QCoreApplication::translate("ChatGui", "Default (auto)"), QString());
|
||||
auto devices = capture ? SoundManager::enumerateCaptureDevices() : SoundManager::enumeratePlaybackDevices();
|
||||
for (const auto& device : devices) add(device.name, device.id);
|
||||
}
|
||||
@@ -116,7 +117,7 @@ void RadioPanel::selectDevice(const QString& id, bool capture) {
|
||||
auto* inst = gui_bridge_get_inst();
|
||||
if (!inst || !gui_bridge_is_uasync_ready()) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "RadioPanel: cannot save device: core unavailable");
|
||||
m_status->setText(QStringLiteral("Ядро недоступно"));
|
||||
m_status->setText(QCoreApplication::translate("ChatGui", "Core unavailable"));
|
||||
return;
|
||||
}
|
||||
const char* key = capture ? "audio_input_id" : "audio_output_id";
|
||||
@@ -124,14 +125,14 @@ void RadioPanel::selectDevice(const QString& id, bool capture) {
|
||||
auto* request = static_cast<save_ui_state_arg*>(u_calloc(1, sizeof(save_ui_state_arg)));
|
||||
if (!request) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "RadioPanel: device settings allocation failed");
|
||||
m_status->setText(QStringLiteral("Не удалось выбрать устройство"));
|
||||
m_status->setText(QCoreApplication::translate("ChatGui", "Failed to select device"));
|
||||
return;
|
||||
}
|
||||
size_t keyLen = strlen(key), valueLen = value.size();
|
||||
if (keyLen + valueLen + 2 > sizeof(request->data)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "RadioPanel: device ID too long bytes=%zu", valueLen);
|
||||
u_free(request);
|
||||
m_status->setText(QStringLiteral("Не удалось выбрать устройство"));
|
||||
m_status->setText(QCoreApplication::translate("ChatGui", "Failed to select device"));
|
||||
return;
|
||||
}
|
||||
request->inst = inst;
|
||||
@@ -148,9 +149,9 @@ void RadioPanel::updateDevices() {
|
||||
for (auto* button : {m_input, m_output}) {
|
||||
bool capture = button == m_input;
|
||||
QString id = capture ? manager->captureDevice() : manager->playbackDevice();
|
||||
QString name = QStringLiteral("По умолчанию (авто)");
|
||||
QString name = QCoreApplication::translate("ChatGui", "Default (auto)");
|
||||
if (!id.isEmpty()) {
|
||||
name = QStringLiteral("Выбранное устройство недоступно; используется доступное");
|
||||
name = QCoreApplication::translate("ChatGui", "Selected device unavailable; using an available device");
|
||||
auto devices = capture ? manager->enumerateCaptureDevices() : manager->enumeratePlaybackDevices();
|
||||
for (const auto& device : devices) if (device.id == id) { name = device.name; break; }
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <QCoreApplication>
|
||||
#include "radiosettingspage.h"
|
||||
#include "ptt_hotkey_manager.h"
|
||||
#include "radio_audio_engine.h"
|
||||
@@ -17,7 +18,7 @@
|
||||
|
||||
RadioSettingsPage::RadioSettingsPage(DbManager* db, QWidget* parent) : QWidget(parent), m_db(db) {
|
||||
auto* layout = new QVBoxLayout(this);
|
||||
auto* title = new QLabel("Рация (PTT)", this);
|
||||
auto* title = new QLabel(QCoreApplication::translate("ChatGui", "Radio (PTT)"), this);
|
||||
title->setStyleSheet("font-weight: bold; font-size: 13px;");
|
||||
layout->addWidget(title);
|
||||
m_audioSettings = new QWidget(this);
|
||||
@@ -30,16 +31,20 @@ RadioSettingsPage::RadioSettingsPage(DbManager* db, QWidget* parent) : QWidget(p
|
||||
form->addRow(label, spin);
|
||||
return spin;
|
||||
};
|
||||
m_threshold = add(QStringLiteral("Порог речи VAD:"), 10, 95, QStringLiteral(" %"));
|
||||
m_threshold->setToolTip(QStringLiteral("Порог вероятности речи: меньше — чувствительнее. Это не порог громкости."));
|
||||
m_hangover = add(QStringLiteral("Тишина до окончания передачи:"), 50, 2000, QStringLiteral(" мс"));
|
||||
m_compressor = new QCheckBox(QStringLiteral("Компрессор передачи и VAD"), m_audioSettings);
|
||||
m_threshold = add(QCoreApplication::translate("ChatGui", "VAD speech threshold:"), 10, 95, QStringLiteral(" %"));
|
||||
m_threshold->setToolTip(QCoreApplication::translate("ChatGui",
|
||||
"Speech probability threshold: lower is more sensitive. This is not a volume threshold."));
|
||||
m_hangover = add(QCoreApplication::translate("ChatGui",
|
||||
"Silence before ending transmission:"), 50, 2000, QCoreApplication::translate("ChatGui",
|
||||
" ms"));
|
||||
m_compressor = new QCheckBox(QCoreApplication::translate("ChatGui", "Transmission and VAD compressor"), m_audioSettings);
|
||||
form->addRow(m_compressor);
|
||||
m_txGain = add(QStringLiteral("Max gain передачи:"), 1, 60, QStringLiteral(" dB"));
|
||||
m_vadGain = add(QStringLiteral("Max gain VAD:"), 1, 60, QStringLiteral(" dB"));
|
||||
auto* explanation = new QLabel(QStringLiteral("VAD включается кнопкой на панели рации. Буфер начала речи — 0,6 с. "
|
||||
"Компрессор VAD имеет отдельный max gain; остальные параметры общие с компрессором передачи. "
|
||||
"Скорость нарастания усиления задаётся в разделе Audio."), m_audioSettings);
|
||||
m_txGain = add(QCoreApplication::translate("ChatGui", "Transmission max gain:"), 1, 60, QCoreApplication::translate("ChatGui", " dB"));
|
||||
m_vadGain = add(QCoreApplication::translate("ChatGui", "VAD max gain:"), 1, 60, QCoreApplication::translate("ChatGui", " dB"));
|
||||
auto* explanation = new QLabel(QCoreApplication::translate("ChatGui",
|
||||
"Enable VAD using the button on the radio panel. Speech prebuffer is 0.6 s. The VAD "
|
||||
"compressor has a separate maximum gain; other parameters are shared with the transmission "
|
||||
"compressor. Set the gain rise rate in Audio."), m_audioSettings);
|
||||
explanation->setWordWrap(true);
|
||||
form->addRow(explanation);
|
||||
layout->addWidget(m_audioSettings);
|
||||
@@ -50,12 +55,13 @@ RadioSettingsPage::RadioSettingsPage(DbManager* db, QWidget* parent) : QWidget(p
|
||||
});
|
||||
loadAudioSettings();
|
||||
if (PttHotkeyManager::usesPortal()) {
|
||||
auto* hint = new QLabel("Глобальные хоткеи работают из любых приложений. "
|
||||
"Назначьте обычную клавишу с Ctrl, Shift, Alt или Win в системном окне. "
|
||||
"Изменения сохраняются системой сразу. Во время настройки передача с клавиатуры отключена.", this);
|
||||
auto* hint = new QLabel(QCoreApplication::translate("ChatGui",
|
||||
"Global shortcuts work from any application. Assign a regular key with Ctrl, Shift, Alt or "
|
||||
"Win in the system dialog. Changes are saved by the system immediately. Keyboard "
|
||||
"transmission is disabled during setup."), this);
|
||||
hint->setWordWrap(true);
|
||||
layout->addWidget(hint);
|
||||
auto* button = new QPushButton("Задать PTT хоткеи…", this);
|
||||
auto* button = new QPushButton(QCoreApplication::translate("ChatGui", "Set PTT shortcuts…"), this);
|
||||
layout->addWidget(button);
|
||||
m_buttons << button;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
@@ -75,23 +81,27 @@ RadioSettingsPage::RadioSettingsPage(DbManager* db, QWidget* parent) : QWidget(p
|
||||
return;
|
||||
}
|
||||
auto* hint = new QLabel(
|
||||
"Удерживайте сочетание для передачи из любого приложения — даже когда ChatGUI не в фокусе. "
|
||||
"Рация канала должна быть включена. Для записи зажмите нужные клавиши вместе и отпустите их. "
|
||||
"Левые и правые Ctrl, Shift, Alt, Win различаются. Fn доступна, только если клавиатура передаёт её ОС.", this);
|
||||
QCoreApplication::translate("ChatGui",
|
||||
"Hold a shortcut to transmit from any application, even when ChatGUI is not focused. "
|
||||
"Channel radio must be enabled. To record a shortcut, hold the desired keys together and "
|
||||
"release them. Left and right Ctrl, Shift, Alt and Win are distinct. Fn is available only "
|
||||
"if the keyboard reports it to the OS."), this);
|
||||
hint->setWordWrap(true);
|
||||
hint->setStyleSheet("color: gray; font-size: 11px;");
|
||||
layout->addWidget(hint);
|
||||
if (!PttHotkeyManager::isSupported()) {
|
||||
auto* warn = new QLabel("Глобальные хоткеи доступны на Windows и Linux/X11. На этой платформе запись недоступна.", this);
|
||||
auto* warn = new QLabel(QCoreApplication::translate("ChatGui",
|
||||
"Global shortcuts are available on Windows and Linux/X11. Shortcut recording is "
|
||||
"unavailable on this platform."), this);
|
||||
warn->setWordWrap(true);
|
||||
warn->setStyleSheet("color: #e74c3c; font-size: 12px;");
|
||||
layout->addWidget(warn);
|
||||
}
|
||||
for (int i = 0; i < 3; i++) {
|
||||
auto* row = new QHBoxLayout();
|
||||
auto* button = new QPushButton(QString("Задать PTT хоткей %1").arg(i + 1), this);
|
||||
auto* button = new QPushButton(QCoreApplication::translate("ChatGui", "Set PTT shortcut %1").arg(i + 1), this);
|
||||
button->setEnabled(PttHotkeyManager::isSupported());
|
||||
auto* clear = new QPushButton("Очистить", this);
|
||||
auto* clear = new QPushButton(QCoreApplication::translate("ChatGui", "Clear"), this);
|
||||
row->addWidget(button); row->addWidget(clear);
|
||||
layout->addLayout(row);
|
||||
auto* label = new QLabel(this);
|
||||
@@ -127,13 +137,13 @@ RadioSettingsPage::~RadioSettingsPage() {
|
||||
void RadioSettingsPage::startCapture(int index) {
|
||||
finishCapture(false);
|
||||
if (!PttHotkeyManager::instance()->beginCapture()) {
|
||||
m_labels[index]->setText("Не удалось начать запись клавиш. Подробности в логе radio.");
|
||||
m_labels[index]->setText(QCoreApplication::translate("ChatGui", "Failed to start key recording. See the radio log for details."));
|
||||
return;
|
||||
}
|
||||
m_captureIndex = index;
|
||||
m_capture.candidate.clear();
|
||||
m_buttons[index]->setText("Отменить запись");
|
||||
m_labels[index]->setText("Нажмите одну или несколько клавиш вместе…");
|
||||
m_buttons[index]->setText(QCoreApplication::translate("ChatGui", "Cancel recording"));
|
||||
m_labels[index]->setText(QCoreApplication::translate("ChatGui", "Press one or more keys together…"));
|
||||
qApp->installEventFilter(this);
|
||||
}
|
||||
|
||||
@@ -147,7 +157,7 @@ void RadioSettingsPage::finishCapture(bool save) {
|
||||
m_captureIndex = -1;
|
||||
qApp->removeEventFilter(this);
|
||||
PttHotkeyManager::instance()->endCapture();
|
||||
m_buttons[index]->setText(QString("Задать PTT хоткей %1").arg(index + 1));
|
||||
m_buttons[index]->setText(QCoreApplication::translate("ChatGui", "Set PTT shortcut %1").arg(index + 1));
|
||||
updateLabel(index);
|
||||
}
|
||||
|
||||
@@ -223,7 +233,9 @@ void RadioSettingsPage::updatePortalLabels() {
|
||||
const auto descriptions = manager->portalDescriptions();
|
||||
for (int i = 0; i < m_labels.size(); i++) {
|
||||
QString text = descriptions.value(i);
|
||||
m_labels[i]->setText(QString("PTT хоткей %1: %2").arg(i + 1).arg(text.isEmpty() ? "Не задано" : text));
|
||||
m_labels[i]->setText(QCoreApplication::translate("ChatGui",
|
||||
"PTT shortcut %1: %2").arg(i + 1).arg(text.isEmpty() ? QCoreApplication::translate("ChatGui",
|
||||
"Not set") : text));
|
||||
}
|
||||
m_portalStatus->setText(manager->portalStatus());
|
||||
m_buttons[0]->setEnabled(!manager->portalBusy());
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <QCoreApplication>
|
||||
#include "renamedialog.h"
|
||||
#include "../db/db_manager.h"
|
||||
#include <QVBoxLayout>
|
||||
@@ -13,7 +14,7 @@ RenameDialog::RenameDialog(DbManager* db, const QString& channelId,
|
||||
, m_channelId(channelId)
|
||||
, m_nodeId(nodeId)
|
||||
{
|
||||
setWindowTitle("Rename");
|
||||
setWindowTitle(QCoreApplication::translate("ChatGui", "Rename"));
|
||||
setMinimumWidth(300);
|
||||
setModal(true);
|
||||
|
||||
@@ -50,8 +51,8 @@ RenameDialog::RenameDialog(DbManager* db, const QString& channelId,
|
||||
auto* btnLayout = new QHBoxLayout();
|
||||
btnLayout->addStretch();
|
||||
|
||||
auto* cancelBtn = new QPushButton("Cancel", this);
|
||||
cancelBtn->setFixedWidth(90);
|
||||
auto* cancelBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Cancel"), this);
|
||||
cancelBtn->setMinimumWidth(90);
|
||||
cancelBtn->setStyleSheet(
|
||||
"QPushButton { border: 1px solid palette(mid); border-radius: 4px;"
|
||||
" padding: 6px 16px; font-size: 13px; background: palette(button); }"
|
||||
@@ -59,8 +60,8 @@ RenameDialog::RenameDialog(DbManager* db, const QString& channelId,
|
||||
connect(cancelBtn, &QPushButton::clicked, this, &QDialog::reject);
|
||||
btnLayout->addWidget(cancelBtn);
|
||||
|
||||
auto* saveBtn = new QPushButton("Save", this);
|
||||
saveBtn->setFixedWidth(90);
|
||||
auto* saveBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Save"), this);
|
||||
saveBtn->setMinimumWidth(90);
|
||||
saveBtn->setDefault(true);
|
||||
saveBtn->setStyleSheet(
|
||||
"QPushButton { border: none; border-radius: 4px;"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <QCoreApplication>
|
||||
// settingsdialog.cpp — Settings dialog with category list
|
||||
#include "settingsdialog.h"
|
||||
#include "networksettingspage.h"
|
||||
@@ -14,6 +15,9 @@
|
||||
#include "../../lib/mem.h"
|
||||
#include "../db/db_manager.h"
|
||||
#include "../transport/utun_node.h"
|
||||
#include "../transport/node_config.h"
|
||||
#include "../transport/config_updater.h"
|
||||
#include "../../lib/debug_config.h"
|
||||
extern "C" {
|
||||
#include "chat/chat_core.h"
|
||||
}
|
||||
@@ -33,7 +37,7 @@ SettingsDialog::SettingsDialog(const QString& configPath, DbManager* db, UtunNod
|
||||
: QDialog(parent)
|
||||
, m_configPath(configPath)
|
||||
{
|
||||
setWindowTitle("Settings");
|
||||
setWindowTitle(QCoreApplication::translate("ChatGui", "Settings"));
|
||||
setMinimumSize(760, 460);
|
||||
setWindowModality(Qt::WindowModal);
|
||||
|
||||
@@ -49,16 +53,16 @@ SettingsDialog::SettingsDialog(const QString& configPath, DbManager* db, UtunNod
|
||||
" background: palette(window); font-size: 13px; }"
|
||||
"QListWidget::item { padding: 10px 14px; }"
|
||||
"QListWidget::item:selected { background: #3390EC; color: white; }");
|
||||
m_categoryList->addItem("Profile");
|
||||
m_categoryList->addItem("Storage");
|
||||
m_categoryList->addItem("Network");
|
||||
m_categoryList->addItem("Connections");
|
||||
m_categoryList->addItem("Database");
|
||||
m_categoryList->addItem("Status");
|
||||
m_categoryList->addItem("Nodes");
|
||||
m_categoryList->addItem("Sound");
|
||||
m_categoryList->addItem("Audio");
|
||||
m_categoryList->addItem("Рация");
|
||||
m_categoryList->addItem(QCoreApplication::translate("ChatGui", "Profile"));
|
||||
m_categoryList->addItem(QCoreApplication::translate("ChatGui", "Storage"));
|
||||
m_categoryList->addItem(QCoreApplication::translate("ChatGui", "Network"));
|
||||
m_categoryList->addItem(QCoreApplication::translate("ChatGui", "Connections"));
|
||||
m_categoryList->addItem(QCoreApplication::translate("ChatGui", "Database"));
|
||||
m_categoryList->addItem(QCoreApplication::translate("ChatGui", "Status"));
|
||||
m_categoryList->addItem(QCoreApplication::translate("ChatGui", "Nodes"));
|
||||
m_categoryList->addItem(QCoreApplication::translate("ChatGui", "Sound"));
|
||||
m_categoryList->addItem(QCoreApplication::translate("ChatGui", "Audio"));
|
||||
m_categoryList->addItem(QCoreApplication::translate("ChatGui", "Radio"));
|
||||
mainLayout->addWidget(m_categoryList);
|
||||
|
||||
// --- Right panel: stacked pages ---
|
||||
@@ -73,35 +77,50 @@ SettingsDialog::SettingsDialog(const QString& configPath, DbManager* db, UtunNod
|
||||
auto* profilePage = new QWidget(m_pages);
|
||||
auto* profLayout = new QFormLayout(profilePage);
|
||||
profLayout->setContentsMargins(0, 12, 0, 0);
|
||||
auto* nameLabel = new QLabel("Display name:", profilePage);
|
||||
auto* nameLabel = new QLabel(QCoreApplication::translate("ChatGui", "Display name:"), profilePage);
|
||||
m_nicknameEdit = new QLineEdit(profilePage);
|
||||
m_nicknameEdit->setPlaceholderText("Your name (shown to others)");
|
||||
m_nicknameEdit->setPlaceholderText(QCoreApplication::translate("ChatGui", "Your name (shown to others)"));
|
||||
m_nicknameEdit->setMaxLength(32);
|
||||
m_nicknameEdit->setStyleSheet(
|
||||
"QLineEdit { border: 1px solid palette(mid); border-radius: 4px;"
|
||||
" padding: 6px 10px; font-size: 13px; background: palette(base); }");
|
||||
profLayout->addRow(nameLabel, m_nicknameEdit);
|
||||
|
||||
auto* sortLabel = new QLabel("Channel sorting:", profilePage);
|
||||
m_languageCombo = new QComboBox(profilePage);
|
||||
m_languageCombo->setObjectName(QStringLiteral("languageCombo"));
|
||||
m_languageCombo->addItem(QCoreApplication::translate("ChatGui", "System language"), QStringLiteral("system"));
|
||||
m_languageCombo->addItem(QStringLiteral("English"), QStringLiteral("en"));
|
||||
m_languageCombo->addItem(QStringLiteral("Русский"), QStringLiteral("ru"));
|
||||
NodeConfig languageConfig(m_configPath);
|
||||
if (!languageConfig.load()) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "GUI language: cannot read preferences from %s", qPrintable(m_configPath));
|
||||
}
|
||||
m_languageCombo->setCurrentIndex(m_languageCombo->findData(languageConfig.language()));
|
||||
profLayout->addRow(QCoreApplication::translate("ChatGui", "Interface language:"), m_languageCombo);
|
||||
auto* languageNote = new QLabel(QCoreApplication::translate("ChatGui", "Language changes take effect after restart."), profilePage);
|
||||
languageNote->setWordWrap(true);
|
||||
profLayout->addRow(languageNote);
|
||||
|
||||
auto* sortLabel = new QLabel(QCoreApplication::translate("ChatGui", "Channel sorting:"), profilePage);
|
||||
m_sortCombo = new QComboBox(profilePage);
|
||||
m_sortCombo->addItem("Alphabetical (A-Z)");
|
||||
m_sortCombo->addItem("Recent Activity");
|
||||
m_sortCombo->addItem(QCoreApplication::translate("ChatGui", "Alphabetical (A-Z)"));
|
||||
m_sortCombo->addItem(QCoreApplication::translate("ChatGui", "Recent Activity"));
|
||||
m_sortCombo->setStyleSheet(
|
||||
"QComboBox { border: 1px solid palette(mid); border-radius: 4px;"
|
||||
" padding: 6px 10px; font-size: 13px; background: palette(base); }");
|
||||
profLayout->addRow(sortLabel, m_sortCombo);
|
||||
|
||||
auto* joinLabel = new QLabel("Join requests:", profilePage);
|
||||
auto* joinLabel = new QLabel(QCoreApplication::translate("ChatGui", "Join requests:"), profilePage);
|
||||
m_joinPolicyCombo = new QComboBox(profilePage);
|
||||
m_joinPolicyCombo->addItem("Auto-join");
|
||||
m_joinPolicyCombo->addItem("Ask before joining");
|
||||
m_joinPolicyCombo->addItem("Deny all");
|
||||
m_joinPolicyCombo->addItem(QCoreApplication::translate("ChatGui", "Auto-join"));
|
||||
m_joinPolicyCombo->addItem(QCoreApplication::translate("ChatGui", "Ask before joining"));
|
||||
m_joinPolicyCombo->addItem(QCoreApplication::translate("ChatGui", "Deny all"));
|
||||
m_joinPolicyCombo->setStyleSheet(
|
||||
"QComboBox { border: 1px solid palette(mid); border-radius: 4px;"
|
||||
" padding: 6px 10px; font-size: 13px; background: palette(base); }");
|
||||
profLayout->addRow(joinLabel, m_joinPolicyCombo);
|
||||
|
||||
m_autoConnectCheck = new QCheckBox("Auto-connect to group nodes", profilePage);
|
||||
m_autoConnectCheck = new QCheckBox(QCoreApplication::translate("ChatGui", "Auto-connect to group nodes"), profilePage);
|
||||
m_autoConnectCheck->setStyleSheet("font-size: 13px;");
|
||||
profLayout->addRow(m_autoConnectCheck);
|
||||
m_pages->addWidget(profilePage);
|
||||
@@ -120,11 +139,11 @@ SettingsDialog::SettingsDialog(const QString& configPath, DbManager* db, UtunNod
|
||||
auto* connPage = new QWidget(m_pages);
|
||||
auto* connLayout = new QVBoxLayout(connPage);
|
||||
connLayout->setAlignment(Qt::AlignCenter);
|
||||
auto* hintLabel = new QLabel("Live ETCP connection monitor\nwith per-link BBR statistics", connPage);
|
||||
auto* hintLabel = new QLabel(QCoreApplication::translate("ChatGui", "Live ETCP connection monitor\nwith per-link BBR statistics"), connPage);
|
||||
hintLabel->setAlignment(Qt::AlignCenter);
|
||||
hintLabel->setStyleSheet("font-size: 12px; color: palette(text); padding: 20px;");
|
||||
connLayout->addWidget(hintLabel);
|
||||
auto* openBtn = new QPushButton("Open Connection Monitor", connPage);
|
||||
auto* openBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Open Connection Monitor"), connPage);
|
||||
openBtn->setStyleSheet("QPushButton { padding: 10px 20px; font-size: 13px; }");
|
||||
connect(openBtn, &QPushButton::clicked, this, &SettingsDialog::onOpenConnMonitor);
|
||||
connLayout->addWidget(openBtn);
|
||||
@@ -160,8 +179,8 @@ SettingsDialog::SettingsDialog(const QString& configPath, DbManager* db, UtunNod
|
||||
btnLayout->setSpacing(8);
|
||||
btnLayout->addStretch();
|
||||
|
||||
auto* cancelBtn = new QPushButton("Cancel", rightPanel);
|
||||
cancelBtn->setFixedWidth(90);
|
||||
auto* cancelBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Cancel"), rightPanel);
|
||||
cancelBtn->setMinimumWidth(90);
|
||||
cancelBtn->setStyleSheet(
|
||||
"QPushButton { border: 1px solid palette(mid); border-radius: 4px;"
|
||||
" padding: 6px 16px; font-size: 13px; background: palette(button); }"
|
||||
@@ -169,8 +188,8 @@ SettingsDialog::SettingsDialog(const QString& configPath, DbManager* db, UtunNod
|
||||
connect(cancelBtn, &QPushButton::clicked, this, &QDialog::reject);
|
||||
btnLayout->addWidget(cancelBtn);
|
||||
|
||||
m_saveBtn = new QPushButton("Save", rightPanel);
|
||||
m_saveBtn->setFixedWidth(90);
|
||||
m_saveBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Save"), rightPanel);
|
||||
m_saveBtn->setMinimumWidth(90);
|
||||
m_saveBtn->setDefault(true);
|
||||
m_saveBtn->setStyleSheet(
|
||||
"QPushButton { border: none; border-radius: 4px;"
|
||||
@@ -299,8 +318,10 @@ void SettingsDialog::onSave() {
|
||||
m_audioDevicePage->applyAndSave();
|
||||
m_storagePage->applyAndSave();
|
||||
if (!m_radioPage->apply()) {
|
||||
QMessageBox::warning(this, QStringLiteral("Настройки рации"), QStringLiteral("Не удалось применить настройки рации. "
|
||||
"Проверьте доступность ядра и файла конфигурации; подробности в логе radio."));
|
||||
QMessageBox::warning(this, QCoreApplication::translate("ChatGui",
|
||||
"Radio settings"), QCoreApplication::translate("ChatGui",
|
||||
"Failed to apply radio settings. Check that the core and configuration file are available; "
|
||||
"see the radio log for details."));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -357,6 +378,15 @@ void SettingsDialog::onSave() {
|
||||
postChatSetting(m_configPath, "group_autoconnect", v);
|
||||
}
|
||||
}
|
||||
const QString language = m_languageCombo->currentData().toString();
|
||||
if (!config_update_option(m_configPath, "gui", "language", language)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "GUI language: failed to save preference=%s path=%s",
|
||||
qPrintable(language), qPrintable(m_configPath));
|
||||
QMessageBox::warning(this, QCoreApplication::translate("ChatGui", "Settings"),
|
||||
QCoreApplication::translate("ChatGui", "Failed to save the interface language. Check the configuration file."));
|
||||
return;
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "GUI language: saved preference=%s; applies after restart", qPrintable(language));
|
||||
accept();
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ private:
|
||||
QPushButton* m_saveBtn;
|
||||
QLineEdit* m_nicknameEdit;
|
||||
QComboBox* m_sortCombo;
|
||||
QComboBox* m_languageCombo;
|
||||
QComboBox* m_joinPolicyCombo;
|
||||
QCheckBox* m_autoConnectCheck;
|
||||
QString m_oldNickname;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <QCoreApplication>
|
||||
#include "soundsettingspage.h"
|
||||
#include "sound_manager.h"
|
||||
#include "../db/db_manager.h"
|
||||
@@ -22,12 +23,12 @@ SoundSettingsPage::SoundSettingsPage(DbManager* db, QWidget* parent)
|
||||
layout->setContentsMargins(0, 12, 0, 0);
|
||||
layout->setSpacing(14);
|
||||
|
||||
m_enabledCheck = new QCheckBox("Enable sounds", this);
|
||||
m_enabledCheck = new QCheckBox(QCoreApplication::translate("ChatGui", "Enable sounds"), this);
|
||||
m_enabledCheck->setStyleSheet("font-size: 13px;");
|
||||
layout->addWidget(m_enabledCheck);
|
||||
|
||||
auto* volLayout = new QHBoxLayout();
|
||||
auto* volLabel = new QLabel("Volume:", this);
|
||||
auto* volLabel = new QLabel(QCoreApplication::translate("ChatGui", "Volume:"), this);
|
||||
volLabel->setStyleSheet("font-size: 13px;");
|
||||
volLayout->addWidget(volLabel);
|
||||
|
||||
@@ -51,25 +52,25 @@ SoundSettingsPage::SoundSettingsPage(DbManager* db, QWidget* parent)
|
||||
sep->setStyleSheet("QFrame { color: palette(mid); }");
|
||||
layout->addWidget(sep);
|
||||
|
||||
auto* header = new QLabel("Events", this);
|
||||
auto* header = new QLabel(QCoreApplication::translate("ChatGui", "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};
|
||||
m_events[4] = {"call_incoming", "Incoming call", nullptr, nullptr, nullptr};
|
||||
m_events[5] = {"call_connecting", "Call: connecting", nullptr, nullptr, nullptr};
|
||||
m_events[6] = {"call_ringing", "Call: ringback", nullptr, nullptr, nullptr};
|
||||
m_events[0] = {"msg_incoming", QT_TRANSLATE_NOOP("ChatGui", "Incoming message"), nullptr, nullptr, nullptr};
|
||||
m_events[1] = {"msg_outgoing", QT_TRANSLATE_NOOP("ChatGui", "Outgoing message"), nullptr, nullptr, nullptr};
|
||||
m_events[2] = {"connect", QT_TRANSLATE_NOOP("ChatGui", "Peer connected"), nullptr, nullptr, nullptr};
|
||||
m_events[3] = {"disconnect", QT_TRANSLATE_NOOP("ChatGui", "Peer disconnected"), nullptr, nullptr, nullptr};
|
||||
m_events[4] = {"call_incoming", QT_TRANSLATE_NOOP("ChatGui", "Incoming call"), nullptr, nullptr, nullptr};
|
||||
m_events[5] = {"call_connecting", QT_TRANSLATE_NOOP("ChatGui", "Call: connecting"), nullptr, nullptr, nullptr};
|
||||
m_events[6] = {"call_ringing", QT_TRANSLATE_NOOP("ChatGui", "Call: ringback"), nullptr, nullptr, nullptr};
|
||||
|
||||
for (int i = 0; i < 7; i++) {
|
||||
auto& e = m_events[i];
|
||||
auto* row = new QHBoxLayout();
|
||||
row->setSpacing(8);
|
||||
|
||||
auto* lbl = new QLabel(e.label, this);
|
||||
lbl->setFixedWidth(140);
|
||||
auto* lbl = new QLabel(QCoreApplication::translate("ChatGui", e.label), this);
|
||||
lbl->setMinimumWidth(140);
|
||||
lbl->setStyleSheet("font-size: 13px;");
|
||||
row->addWidget(lbl);
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <QCoreApplication>
|
||||
// statuspage.cpp — Status page (NTP + connections) in settings
|
||||
#include "statuspage.h"
|
||||
#include "../transport/utun_node.h"
|
||||
@@ -50,7 +51,7 @@ StatusPage::StatusPage(UtunNode* node, QWidget* parent)
|
||||
layout->addWidget(m_clockFrame);
|
||||
|
||||
/* ── System Status section ── */
|
||||
auto* title = new QLabel("System Status", this);
|
||||
auto* title = new QLabel(QCoreApplication::translate("ChatGui", "System Status"), this);
|
||||
title->setStyleSheet("font-weight: bold; font-size: 13px;");
|
||||
layout->addWidget(title);
|
||||
|
||||
@@ -60,13 +61,13 @@ StatusPage::StatusPage(UtunNode* node, QWidget* parent)
|
||||
m_textEdit->setStyleSheet(
|
||||
"QPlainTextEdit { border: 1px solid palette(mid); border-radius: 4px;"
|
||||
" padding: 8px; background: palette(base); }");
|
||||
m_textEdit->setPlaceholderText("Click Refresh to load status");
|
||||
m_textEdit->setPlaceholderText(QCoreApplication::translate("ChatGui", "Click Refresh to load status"));
|
||||
layout->addWidget(m_textEdit, 1);
|
||||
|
||||
auto* btnLayout = new QHBoxLayout();
|
||||
btnLayout->addStretch();
|
||||
m_refreshBtn = new QPushButton("Refresh", this);
|
||||
m_refreshBtn->setFixedWidth(90);
|
||||
m_refreshBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Refresh"), this);
|
||||
m_refreshBtn->setMinimumWidth(90);
|
||||
m_refreshBtn->setStyleSheet(
|
||||
"QPushButton { border: 1px solid palette(mid); border-radius: 4px;"
|
||||
" padding: 6px 16px; font-size: 13px; background: palette(button); }"
|
||||
@@ -104,7 +105,7 @@ void StatusPage::updateClock() {
|
||||
if (synced)
|
||||
m_clockLabel->setText(QString("%1 [NTP ✓]").arg(t));
|
||||
else
|
||||
m_clockLabel->setText(QString("%1 [LOCAL]").arg(t));
|
||||
m_clockLabel->setText(QCoreApplication::translate("ChatGui", "%1 [LOCAL]").arg(t));
|
||||
|
||||
int msToNext = 1000 - (corrected.time().msec());
|
||||
m_clockTimer->start(msToNext);
|
||||
@@ -112,7 +113,7 @@ void StatusPage::updateClock() {
|
||||
|
||||
void StatusPage::refreshStatus() {
|
||||
updateClock();
|
||||
m_textEdit->setPlainText("Loading...");
|
||||
m_textEdit->setPlainText(QCoreApplication::translate("ChatGui", "Loading..."));
|
||||
gui_bridge_post_uasync_fn(chat_core_collect_status_trampoline, gui_bridge_get_inst());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <QCoreApplication>
|
||||
#include "storagesettingspage.h"
|
||||
#include "../db/db_manager.h"
|
||||
#include "../transport/gui_bridge.h"
|
||||
@@ -46,7 +47,7 @@ StorageSettingsPage::StorageSettingsPage(DbManager* db, QWidget* parent)
|
||||
layout->setContentsMargins(0, 12, 0, 0);
|
||||
layout->setSpacing(14);
|
||||
|
||||
auto* title = new QLabel("Media Storage", this);
|
||||
auto* title = new QLabel(QCoreApplication::translate("ChatGui", "Media Storage"), this);
|
||||
title->setStyleSheet("font-weight: bold; font-size: 13px;");
|
||||
layout->addWidget(title);
|
||||
|
||||
@@ -54,27 +55,27 @@ StorageSettingsPage::StorageSettingsPage(DbManager* db, QWidget* parent)
|
||||
form->setSpacing(10);
|
||||
form->setContentsMargins(0, 8, 0, 0);
|
||||
|
||||
m_autoloadCheck = new QCheckBox("Autoload media", this);
|
||||
m_autoloadCheck = new QCheckBox(QCoreApplication::translate("ChatGui", "Autoload media"), this);
|
||||
m_autoloadCheck->setStyleSheet("font-size: 13px;");
|
||||
form->addRow(m_autoloadCheck);
|
||||
|
||||
m_autoloadMaxSize = new QSpinBox(this);
|
||||
m_autoloadMaxSize->setRange(1, 500);
|
||||
m_autoloadMaxSize->setSuffix(" MB");
|
||||
m_autoloadMaxSize->setSuffix(QCoreApplication::translate("ChatGui", " MB"));
|
||||
m_autoloadMaxSize->setFixedWidth(110);
|
||||
m_autoloadMaxSize->setStyleSheet(
|
||||
"QSpinBox { border: 1px solid palette(mid); border-radius: 4px;"
|
||||
" padding: 5px 8px; font-size: 13px; background: palette(base); }");
|
||||
form->addRow("Autoload max size:", m_autoloadMaxSize);
|
||||
form->addRow(QCoreApplication::translate("ChatGui", "Autoload max size:"), m_autoloadMaxSize);
|
||||
|
||||
m_maxStorageSize = new QSpinBox(this);
|
||||
m_maxStorageSize->setRange(1, 100);
|
||||
m_maxStorageSize->setSuffix(" GB");
|
||||
m_maxStorageSize->setSuffix(QCoreApplication::translate("ChatGui", " GB"));
|
||||
m_maxStorageSize->setFixedWidth(110);
|
||||
m_maxStorageSize->setStyleSheet(
|
||||
"QSpinBox { border: 1px solid palette(mid); border-radius: 4px;"
|
||||
" padding: 5px 8px; font-size: 13px; background: palette(base); }");
|
||||
form->addRow("Max storage size:", m_maxStorageSize);
|
||||
form->addRow(QCoreApplication::translate("ChatGui", "Max storage size:"), m_maxStorageSize);
|
||||
|
||||
layout->addLayout(form);
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <QCoreApplication>
|
||||
#include "videoplayer_engine.h"
|
||||
#include "sound_manager.h"
|
||||
#include "../../lib/debug_config.h"
|
||||
@@ -74,10 +75,10 @@ void Worker::run() {
|
||||
};
|
||||
|
||||
if (avformat_open_input(&fmt, path.toUtf8().constData(), nullptr, nullptr) != 0) {
|
||||
worker_fail(this, QString("cannot open: %1").arg(path)); return;
|
||||
worker_fail(this, QCoreApplication::translate("ChatGui", "cannot open: %1").arg(path)); return;
|
||||
}
|
||||
if (avformat_find_stream_info(fmt, nullptr) < 0) {
|
||||
worker_fail(this, QString("no stream info: %1").arg(path)); cleanup(); return;
|
||||
worker_fail(this, QCoreApplication::translate("ChatGui", "no stream info: %1").arg(path)); cleanup(); return;
|
||||
}
|
||||
|
||||
for (unsigned i = 0; i < fmt->nb_streams; i++) {
|
||||
@@ -85,7 +86,7 @@ void Worker::run() {
|
||||
if (p->codec_type == AVMEDIA_TYPE_VIDEO && vidx < 0) vidx = (int)i;
|
||||
else if (p->codec_type == AVMEDIA_TYPE_AUDIO && aidx < 0) aidx = (int)i;
|
||||
}
|
||||
if (vidx < 0) { worker_fail(this, QString("no video stream: %1").arg(path)); cleanup(); return; }
|
||||
if (vidx < 0) { worker_fail(this, QCoreApplication::translate("ChatGui", "no video stream: %1").arg(path)); cleanup(); return; }
|
||||
|
||||
AVStream* vst = fmt->streams[vidx];
|
||||
int vw = vst->codecpar->width, vh = vst->codecpar->height;
|
||||
@@ -131,7 +132,7 @@ void Worker::run() {
|
||||
if (aidx >= 0) {
|
||||
aframe = av_frame_alloc();
|
||||
pkt = av_packet_alloc();
|
||||
if (!aframe || !pkt) { worker_fail(this, "alloc failed"); cleanup(); return; }
|
||||
if (!aframe || !pkt) { worker_fail(this, QCoreApplication::translate("ChatGui", "alloc failed")); cleanup(); return; }
|
||||
|
||||
while (av_read_frame(fmt, pkt) >= 0) {
|
||||
if (abort->loadRelaxed()) { av_packet_unref(pkt); cleanup(); return; }
|
||||
@@ -175,11 +176,11 @@ void Worker::run() {
|
||||
vdec = avcodec_alloc_context3(vc);
|
||||
if (!vc || !vdec || avcodec_parameters_to_context(vdec, vst->codecpar) < 0 ||
|
||||
avcodec_open2(vdec, vc, nullptr) < 0) {
|
||||
worker_fail(this, "video decoder open failed"); cleanup(); return;
|
||||
worker_fail(this, QCoreApplication::translate("ChatGui", "video decoder open failed")); cleanup(); return;
|
||||
}
|
||||
frame = av_frame_alloc();
|
||||
pkt = av_packet_alloc();
|
||||
if (!frame || !pkt) { worker_fail(this, "alloc failed"); cleanup(); return; }
|
||||
if (!frame || !pkt) { worker_fail(this, QCoreApplication::translate("ChatGui", "alloc failed")); cleanup(); return; }
|
||||
/* YUVJ* (deprecated full-range) → обычный формат + явный range, чтобы
|
||||
* не ловить warning swscaler и не искажать цвета */
|
||||
AVPixelFormat srcFmt = vdec->pix_fmt;
|
||||
@@ -192,7 +193,7 @@ void Worker::run() {
|
||||
}
|
||||
sws = sws_getContext(vw, vh, srcFmt, vw, vh, AV_PIX_FMT_RGB24,
|
||||
SWS_BILINEAR, nullptr, nullptr, nullptr);
|
||||
if (!sws) { worker_fail(this, "sws init failed"); cleanup(); return; }
|
||||
if (!sws) { worker_fail(this, QCoreApplication::translate("ChatGui", "sws init failed")); cleanup(); return; }
|
||||
int *invTbl = nullptr, *tbl = nullptr, sr = 0, dr = 0, br = 0, ct = 0, st = 0;
|
||||
if (sws_getColorspaceDetails(sws, &invTbl, &sr, &tbl, &dr, &br, &ct, &st) >= 0)
|
||||
sws_setColorspaceDetails(sws, invTbl, srcRange, tbl, 1 /* RGB full-range */, br, ct, st);
|
||||
@@ -252,7 +253,7 @@ void Worker::run() {
|
||||
|
||||
void Worker::run() {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "VideoEngine: built without FFmpeg");
|
||||
emit error(QStringLiteral("видео недоступно (сборка без FFmpeg)"));
|
||||
emit error(QCoreApplication::translate("ChatGui", "video unavailable (built without FFmpeg)"));
|
||||
}
|
||||
|
||||
#endif /* HAVE_FFMPEG */
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <QCoreApplication>
|
||||
#include "videoplayer_window.h"
|
||||
#include "videoplayer_engine.h"
|
||||
#include "../../lib/debug_config.h"
|
||||
@@ -76,7 +77,7 @@ public:
|
||||
VideoPlayerWindow::VideoPlayerWindow(QWidget* parent)
|
||||
: QWidget(parent, Qt::Window | Qt::FramelessWindowHint)
|
||||
{
|
||||
setWindowTitle(QStringLiteral("Video"));
|
||||
setWindowTitle(QCoreApplication::translate("ChatGui", "Video"));
|
||||
setFocusPolicy(Qt::StrongFocus);
|
||||
setMouseTracking(true);
|
||||
|
||||
@@ -99,7 +100,7 @@ VideoPlayerWindow::VideoPlayerWindow(QWidget* parent)
|
||||
"QSlider::handle:horizontal { width: 12px; margin: -4px 0; background: white; border-radius: 6px; }");
|
||||
|
||||
m_playBtn = new QPushButton(QStringLiteral("⏸"), m_controls);
|
||||
m_playBtn->setToolTip(QStringLiteral("Пауза"));
|
||||
m_playBtn->setToolTip(QCoreApplication::translate("ChatGui", "Pause"));
|
||||
m_playBtn->setFixedWidth(40);
|
||||
m_slider = new QSlider(Qt::Horizontal, m_controls);
|
||||
m_slider->setStyle(new SeekSliderStyle());
|
||||
@@ -115,7 +116,7 @@ VideoPlayerWindow::VideoPlayerWindow(QWidget* parent)
|
||||
|
||||
/* крестик закрытия — отдельно, справа вверху */
|
||||
m_closeBtn = new QPushButton(QStringLiteral("✕"), this);
|
||||
m_closeBtn->setToolTip(QStringLiteral("Закрыть"));
|
||||
m_closeBtn->setToolTip(QCoreApplication::translate("ChatGui", "Close"));
|
||||
m_closeBtn->setCursor(Qt::ArrowCursor);
|
||||
m_closeBtn->setStyleSheet(
|
||||
"QPushButton { background: rgba(0,0,0,0.5); color: white; border: none; "
|
||||
@@ -193,10 +194,10 @@ void VideoPlayerWindow::onStateChanged(int state) {
|
||||
VideoPlayerEngine::State s = (VideoPlayerEngine::State)state;
|
||||
if (s == VideoPlayerEngine::Playing) {
|
||||
m_playBtn->setText(QStringLiteral("⏸"));
|
||||
m_playBtn->setToolTip(QStringLiteral("Пауза"));
|
||||
m_playBtn->setToolTip(QCoreApplication::translate("ChatGui", "Pause"));
|
||||
} else if (s == VideoPlayerEngine::Paused || s == VideoPlayerEngine::Finished) {
|
||||
m_playBtn->setText(QStringLiteral("▶"));
|
||||
m_playBtn->setToolTip(QStringLiteral("Играть"));
|
||||
m_playBtn->setToolTip(QCoreApplication::translate("ChatGui", "Play"));
|
||||
showControls();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
#include "../src/gui_language.h"
|
||||
#include "../src/networksettingspage.h"
|
||||
#include "../transport/node_config.h"
|
||||
#include "../transport/config_updater.h"
|
||||
#include "../../../lib/debug_config.h"
|
||||
|
||||
#include <QtTest>
|
||||
#include <QComboBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QPushButton>
|
||||
#include <QFile>
|
||||
#include <QTemporaryDir>
|
||||
#include <QTranslator>
|
||||
#include <QXmlStreamReader>
|
||||
#include <QRegularExpression>
|
||||
|
||||
class GuiLanguageTest : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void initTestCase() { debug_config_init(); }
|
||||
|
||||
void systemPreference() {
|
||||
QCOMPARE(resolveGuiLanguage("system", {"ru-RU", "en-US"}), QString("ru"));
|
||||
QCOMPARE(resolveGuiLanguage("system", {"en-GB", "ru-RU"}), QString("en"));
|
||||
QCOMPARE(resolveGuiLanguage("system", {"de-DE", "ru_RU"}), QString("ru"));
|
||||
QCOMPARE(resolveGuiLanguage("system", {"fr-FR"}), QString("en"));
|
||||
QCOMPARE(resolveGuiLanguage("system", {}), QString("en"));
|
||||
QCOMPARE(resolveGuiLanguage("ru", {"en-US"}), QString("ru"));
|
||||
QCOMPARE(resolveGuiLanguage("en", {"ru-RU"}), QString("en"));
|
||||
}
|
||||
|
||||
void preferencePersistence() {
|
||||
QTemporaryDir directory;
|
||||
QVERIFY(directory.isValid());
|
||||
const QString path = directory.filePath("vibechat.cfg");
|
||||
NodeConfig config(path);
|
||||
QVERIFY(config.create("Language test", 1333));
|
||||
QCOMPARE(config.language(), QString("system"));
|
||||
for (const QString& language : {QString("ru"), QString("en"), QString("system")}) {
|
||||
QVERIFY(config_update_option(path, "gui", "language", language));
|
||||
QVERIFY(config.load());
|
||||
QVERIFY2(!config.hasErrors(), qPrintable(config.errors().join('\n')));
|
||||
QCOMPARE(config.language(), language);
|
||||
QVERIFY(config.save());
|
||||
NodeConfig reloaded(path);
|
||||
QVERIFY(reloaded.load());
|
||||
QCOMPARE(reloaded.language(), language);
|
||||
QVERIFY(reloaded.saveFull());
|
||||
QVERIFY(reloaded.load());
|
||||
QCOMPARE(reloaded.language(), language);
|
||||
}
|
||||
QVERIFY(config_update_option(path, "gui", "language", "invalid"));
|
||||
QVERIFY(config.load());
|
||||
QCOMPARE(config.language(), QString("system"));
|
||||
QVERIFY(config_update_option(path, "gui", "language", "ru"));
|
||||
QVERIFY(config.load());
|
||||
QFile file(path);
|
||||
QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Truncate));
|
||||
const QByteArray minimalConfig("[gui]\ndb_path=chat_data\n");
|
||||
QCOMPARE(file.write(minimalConfig), qint64(minimalConfig.size()));
|
||||
file.close();
|
||||
QVERIFY(config.load());
|
||||
QCOMPARE(config.language(), QString("system"));
|
||||
}
|
||||
|
||||
void embeddedCatalogs() {
|
||||
for (const QString& language : {QString("en"), QString("ru")}) {
|
||||
QTranslator translator;
|
||||
QVERIFY(translator.load(":/translations/chatgui_" + language + ".qm"));
|
||||
QVERIFY(qApp->installTranslator(&translator));
|
||||
QCOMPARE(QCoreApplication::translate("ChatGui", "Settings"), language == "ru" ? QString("Настройки") : QString("Settings"));
|
||||
QFile file(QStringLiteral(GUI_TS_DIR) + "/chatgui_" + language + ".ts");
|
||||
QVERIFY(file.open(QIODevice::ReadOnly));
|
||||
QXmlStreamReader xml(&file);
|
||||
QString source;
|
||||
int count = 0;
|
||||
const QRegularExpression placeholders("%(?:L?[1-9][0-9]?|n)");
|
||||
auto parameters = [&](const QString& text) {
|
||||
QStringList list;
|
||||
auto matches = placeholders.globalMatch(text);
|
||||
while (matches.hasNext()) list.append(matches.next().captured());
|
||||
list.sort();
|
||||
return list;
|
||||
};
|
||||
while (!xml.atEnd()) {
|
||||
xml.readNext();
|
||||
if (!xml.isStartElement()) continue;
|
||||
if (xml.name() == QLatin1String("source")) source = xml.readElementText();
|
||||
if (xml.name() != QLatin1String("translation")) continue;
|
||||
QVERIFY(!xml.attributes().hasAttribute("type"));
|
||||
const QString translation = xml.readElementText();
|
||||
QVERIFY2(!translation.isEmpty(), qPrintable(source));
|
||||
QCOMPARE(parameters(translation), parameters(source));
|
||||
QCOMPARE(QCoreApplication::translate("ChatGui", source.toUtf8().constData()), translation);
|
||||
if (language == "en") QCOMPARE(translation, source);
|
||||
count++;
|
||||
}
|
||||
QVERIFY2(!xml.hasError(), qPrintable(xml.errorString()));
|
||||
QVERIFY(count > 0);
|
||||
qApp->removeTranslator(&translator);
|
||||
}
|
||||
}
|
||||
|
||||
void standardButtons() {
|
||||
QTranslator translator;
|
||||
QVERIFY(translator.load(":/translations/qtbase_ru.qm"));
|
||||
QVERIFY(qApp->installTranslator(&translator));
|
||||
QDialogButtonBox buttons(QDialogButtonBox::Cancel | QDialogButtonBox::Save);
|
||||
QCOMPARE(buttons.button(QDialogButtonBox::Cancel)->text().remove('&'), QString("Отмена"));
|
||||
QCOMPARE(buttons.button(QDialogButtonBox::Save)->text().remove('&'), QString("Сохранить"));
|
||||
qApp->removeTranslator(&translator);
|
||||
}
|
||||
|
||||
void translatedNetworkValues_data() {
|
||||
QTest::addColumn<QString>("language");
|
||||
QTest::newRow("English") << QString("en");
|
||||
QTest::newRow("Russian") << QString("ru");
|
||||
}
|
||||
|
||||
void translatedNetworkValues() {
|
||||
QFETCH(QString, language);
|
||||
QTranslator translator;
|
||||
QVERIFY(translator.load(":/translations/chatgui_" + language + ".qm"));
|
||||
QVERIFY(qApp->installTranslator(&translator));
|
||||
QTemporaryDir directory;
|
||||
QVERIFY(directory.isValid());
|
||||
const QString path = directory.filePath("vibechat.cfg");
|
||||
QVERIFY(config_rewrite_servers(path, {{"test", "0.0.0.0:1333", "tcp", "private", ""}}));
|
||||
NetworkSettingsPage page;
|
||||
page.loadFromConfig(path);
|
||||
QComboBox* type = nullptr;
|
||||
for (auto* combo : page.findChildren<QComboBox*>()) {
|
||||
if (combo->findData(QString("public")) >= 0) type = combo;
|
||||
}
|
||||
QVERIFY(type);
|
||||
QCOMPARE(type->currentData().toString(), QString("private"));
|
||||
QCOMPARE(type->currentText(), language == "ru" ? QString("Частный") : QString("Private"));
|
||||
type->setCurrentIndex(type->findData(QString("local")));
|
||||
page.saveToConfig(path);
|
||||
QFile file(path);
|
||||
QVERIFY(file.open(QIODevice::ReadOnly));
|
||||
const QByteArray content = file.readAll();
|
||||
QVERIFY(content.contains("type=local\n"));
|
||||
QVERIFY(content.contains("addr=0.0.0.0:1333\n"));
|
||||
QVERIFY(content.contains("transport=tcp\n"));
|
||||
QVERIFY(!content.contains("netif="));
|
||||
qApp->removeTranslator(&translator);
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(GuiLanguageTest)
|
||||
#include "test_gui_language.moc"
|
||||
@@ -159,7 +159,7 @@ int main(int argc, char** argv) {
|
||||
QTimer::singleShot(10000, &app, &QCoreApplication::quit);
|
||||
portal.initialize();
|
||||
app.exec();
|
||||
return done && !portal.status().contains("недоступны") ? 0 : 1;
|
||||
return done && !portal.status().contains("unavailable") ? 0 : 1;
|
||||
}
|
||||
PortalTest test;
|
||||
return QTest::qExec(&test, argc, argv);
|
||||
|
||||
@@ -144,7 +144,7 @@ int main(int argc, char** argv) {
|
||||
detectorAvailable = false;
|
||||
vad->click();
|
||||
await([&]() { return !engine->settingsBusy(); });
|
||||
REQUIRE(vad->isChecked() && engine->captureAvailable() && !engine->vadAvailable() && status->text() == "VAD недоступен");
|
||||
REQUIRE(vad->isChecked() && engine->captureAvailable() && !engine->vadAvailable() && status->text() == "VAD unavailable");
|
||||
QTest::mousePress(ptt, Qt::LeftButton);
|
||||
await([&]() { return engine->isTransmitting(); });
|
||||
QTest::mouseRelease(ptt, Qt::LeftButton);
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,7 @@
|
||||
<RCC>
|
||||
<qresource prefix="/translations">
|
||||
<file alias="chatgui_en.qm">@CMAKE_CURRENT_BINARY_DIR@/translations/chatgui_en.qm</file>
|
||||
<file alias="chatgui_ru.qm">@CMAKE_CURRENT_BINARY_DIR@/translations/chatgui_ru.qm</file>
|
||||
<file alias="qtbase_ru.qm">@QT_RUSSIAN_CATALOG@</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
@@ -1,6 +1,7 @@
|
||||
// node_config.cpp — Simplified INI config manager for embedded uTun node
|
||||
#include "node_config.h"
|
||||
#include "config_updater.h"
|
||||
#include "../../../lib/debug_config.h"
|
||||
#include <QHostInfo>
|
||||
|
||||
extern "C" {
|
||||
@@ -39,7 +40,7 @@ static const QSet<QString> CONTROL_KEYS = {
|
||||
};
|
||||
|
||||
static const QSet<QString> GUI_KEYS = {
|
||||
"db_path", "debug_file", "debug_level",
|
||||
"db_path", "debug_file", "debug_level", "language",
|
||||
"sound_dir", "sound_msg_incoming", "sound_msg_outgoing",
|
||||
"sound_connect", "sound_disconnect"
|
||||
};
|
||||
@@ -225,6 +226,7 @@ bool NodeConfig::load() {
|
||||
m_servers.clear();
|
||||
m_clients.clear();
|
||||
m_errors.clear();
|
||||
m_language = QStringLiteral("system");
|
||||
|
||||
QString section, controlIp, controlPort;
|
||||
QTextStream in(&f);
|
||||
@@ -336,6 +338,10 @@ bool NodeConfig::load() {
|
||||
if (key == "db_path") m_dbPath = val;
|
||||
else if (key == "debug_file") m_debugFile = val;
|
||||
else if (key == "debug_level") m_debugLevel = val;
|
||||
else if (key == "language") {
|
||||
if (val == "system" || val == "en" || val == "ru") m_language = val;
|
||||
else DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "GUI language: invalid preference '%s', using system language", qPrintable(val));
|
||||
}
|
||||
} else if (debugSection) {
|
||||
if (!m_debugCategories.isEmpty()) m_debugCategories += ',';
|
||||
m_debugCategories += key + '=' + val;
|
||||
@@ -386,6 +392,7 @@ bool NodeConfig::saveFull() {
|
||||
out << "[gui]\n";
|
||||
out << "db_path=" << (m_dbPath.isEmpty() ? "chat_data" : m_dbPath) << "\n";
|
||||
out << "debug_file=" << m_debugFile << "\n";
|
||||
out << "language=" << m_language << "\n";
|
||||
out << "debug_level=" << (m_debugLevel.isEmpty() ? "info" : m_debugLevel) << "\n\n";
|
||||
|
||||
out << "[debug]\n";
|
||||
@@ -445,6 +452,7 @@ bool NodeConfig::save() {
|
||||
|
||||
config_update_in_lines(lines, "gui", "db_path", m_dbPath.isEmpty() ? "chat_data" : m_dbPath, true);
|
||||
config_update_in_lines(lines, "gui", "debug_file", m_debugFile, true);
|
||||
config_update_in_lines(lines, "gui", "language", m_language, true);
|
||||
config_update_in_lines(lines, "gui", "debug_level", m_debugLevel.isEmpty() ? "info" : m_debugLevel, true);
|
||||
|
||||
/* [debug] section: each category=level as separate line */
|
||||
|
||||
@@ -54,6 +54,7 @@ public:
|
||||
void setDebugFile(const QString& p) { m_debugFile = p; }
|
||||
QString debugLevel() const { return m_debugLevel; }
|
||||
QString debugCategories() const { return m_debugCategories; }
|
||||
QString language() const { return m_language; }
|
||||
|
||||
/* Config validation errors (collected during load()) */
|
||||
bool hasErrors() const { return !m_errors.isEmpty(); }
|
||||
@@ -77,6 +78,7 @@ private:
|
||||
QString m_debugFile;
|
||||
QString m_debugLevel;
|
||||
QString m_debugCategories;
|
||||
QString m_language = QStringLiteral("system");
|
||||
QStringList m_errors;
|
||||
};
|
||||
#endif // NODE_CONFIG_H
|
||||
Reference in new issue
Block a user