#include // settingsdialog.cpp — Settings dialog with category list #include "settingsdialog.h" #include "networksettingspage.h" #include "bluetoothsettingspage.h" #include "databasesettingspage.h" #include "statuspage.h" #include "nodespage.h" #include "soundsettingspage.h" #include "audiodevicesettingspage.h" #include "storagesettingspage.h" #include "radiosettingspage.h" #include "avatars/avatarmanager.h" #include "avatars/avatarcrop.h" #include "ptt_hotkey_manager.h" #include "connmonitorwindow.h" #include "../transport/gui_bridge.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 "call/call.h" } #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include SettingsDialog::SettingsDialog(const QString& configPath, DbManager* db, UtunNode* node, AudioRecorder* recorder, QWidget* parent) : QDialog(parent) , m_configPath(configPath) { setWindowTitle(QCoreApplication::translate("ChatGui", "Settings")); setMinimumSize(760, 460); setWindowModality(Qt::WindowModal); auto* mainLayout = new QHBoxLayout(this); mainLayout->setSpacing(0); mainLayout->setContentsMargins(0, 0, 0, 0); // --- Left sidebar: category list --- m_categoryList = new QListWidget(this); m_categoryList->setFixedWidth(150); m_categoryList->setStyleSheet( "QListWidget { border: none; border-right: 1px solid palette(mid);" " background: palette(window); font-size: 13px; }" "QListWidget::item { padding: 10px 14px; }" "QListWidget::item:selected { background: #3390EC; color: white; }"); 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")); m_categoryList->addItem(QCoreApplication::translate("ChatGui", "Broadcast")); m_categoryList->addItem(QCoreApplication::translate("ChatGui", "Bluetooth")); mainLayout->addWidget(m_categoryList); // --- Right panel: stacked pages --- auto* rightPanel = new QWidget(this); auto* rightLayout = new QVBoxLayout(rightPanel); rightLayout->setContentsMargins(20, 16, 20, 16); rightLayout->setSpacing(0); m_pages = new QStackedWidget(rightPanel); // Profile page auto* profilePage = new QWidget(m_pages); auto* profLayout = new QFormLayout(profilePage); profLayout->setContentsMargins(0, 12, 0, 0); auto* nameLabel = new QLabel(QCoreApplication::translate("ChatGui", "Display name:"), profilePage); m_nicknameEdit = new QLineEdit(profilePage); 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); if (auto* avatars = AvatarManager::instance()) { auto* controls = new QWidget(profilePage); auto* box = new QVBoxLayout(controls); box->setContentsMargins(0, 0, 0, 0); auto* preview = new QLabel(controls); preview->setFixedSize(64, 64); preview->setAlignment(Qt::AlignCenter); auto* server = new QComboBox(controls); server->setObjectName(QStringLiteral("avatarServer")); auto* buttons = new QHBoxLayout; auto* upload = new QPushButton(QCoreApplication::translate("ChatGui", "Upload avatar"), controls); auto* clear = new QPushButton(QCoreApplication::translate("ChatGui", "Remove avatar"), controls); upload->setObjectName(QStringLiteral("uploadAvatar")); clear->setObjectName(QStringLiteral("removeAvatar")); auto* status = new QLabel(controls); status->setWordWrap(true); status->setObjectName(QStringLiteral("avatarStatus")); auto* note = new QLabel(QCoreApplication::translate("ChatGui", "Avatar changes apply immediately in channels with the selected storage server."), controls); note->setWordWrap(true); box->addWidget(preview); box->addWidget(server); buttons->addWidget(upload); buttons->addWidget(clear); box->addLayout(buttons); box->addWidget(status); box->addWidget(note); profLayout->addRow(QCoreApplication::translate("ChatGui", "Avatar:"), controls); auto update = [avatars, server, preview, upload, clear] { const quint64 selected = server->count() ? server->currentData().toULongLong() : avatars->defaultServer(); server->clear(); const auto servers = avatars->servers(); for (auto it = servers.begin(); it != servers.end(); ++it) server->addItem(QString("%1 · %2").arg(it.value(), QString::number(it.key(), 16)), QVariant::fromValue(it.key())); int index = server->findData(QVariant::fromValue(selected)); if (index >= 0) server->setCurrentIndex(index); upload->setEnabled(avatars->ready() && server->count() && gui_bridge_is_uasync_ready()); clear->setEnabled(avatars->ready() && gui_bridge_is_uasync_ready()); const QImage image = avatars->defaultImage(); preview->setPixmap(image.isNull() ? QPixmap() : QPixmap::fromImage(image).scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation)); if (image.isNull()) preview->setText(QCoreApplication::translate("ChatGui", "No avatar")); }; connect(avatars, &AvatarManager::serversChanged, controls, update); connect(avatars, &AvatarManager::statusChanged, status, &QLabel::setText); connect(upload, &QPushButton::clicked, controls, [manager = QPointer(avatars), owner = QPointer(controls), selection = QPointer(server)] { if (!manager || !owner || !selection) return; QPointer chooser = new QFileDialog(owner, QCoreApplication::translate("ChatGui", "Choose avatar")); auto release = qScopeGuard([&] { delete chooser.data(); }); chooser->setNameFilter(QCoreApplication::translate("ChatGui", "Images (*.png *.jpg *.jpeg *.webp)")); chooser->setFileMode(QFileDialog::ExistingFile); QObject::connect(manager, &QObject::destroyed, chooser, &QDialog::reject); if (chooser->exec() != QDialog::Accepted || !chooser || !manager || !owner || !selection) return; const auto files = chooser->selectedFiles(); if (files.isEmpty()) return; const QString path = files.first(); const quint64 serverId = selection->currentData().toULongLong(); QRectF crop; if (selectAvatarCrop(path, crop, owner) && manager) manager->uploadDefault(path, serverId, crop); }); connect(clear, &QPushButton::clicked, avatars, &AvatarManager::clearDefault); update(); } 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(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(QCoreApplication::translate("ChatGui", "Join requests:"), profilePage); m_joinPolicyCombo = new QComboBox(profilePage); 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(QCoreApplication::translate("ChatGui", "Auto-connect to group nodes"), profilePage); m_autoConnectCheck->setStyleSheet("font-size: 13px;"); profLayout->addRow(m_autoConnectCheck); m_publishCallStatus = new QCheckBox(QCoreApplication::translate("ChatGui", "Share my call status"), profilePage); m_publishCallStatus->setObjectName(QStringLiteral("publishCallStatus")); QSettings callConfig(m_configPath, QSettings::IniFormat); const int callPolicy = callConfig.value("chatserver/incoming_call_policy", CALL_POLICY_PUBLISH_PHONE).toInt(); m_publishCallStatus->setChecked(callPolicy & CALL_POLICY_PUBLISH_PHONE); profLayout->addRow(m_publishCallStatus); auto* callStatusNote = new QLabel(QCoreApplication::translate("ChatGui", "Other participants can see whether you are busy with a call in this app."), profilePage); callStatusNote->setWordWrap(true); profLayout->addRow(callStatusNote); m_pages->addWidget(profilePage); // Storage page m_storagePage = new StorageSettingsPage(db, m_pages); m_storagePage->setConfigPath(m_configPath); m_pages->addWidget(m_storagePage); // Network page m_networkPage = new NetworkSettingsPage(m_pages); m_pages->addWidget(m_networkPage); // Connections page — button to open standalone window { auto* connPage = new QWidget(m_pages); auto* connLayout = new QVBoxLayout(connPage); connLayout->setAlignment(Qt::AlignCenter); 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(QCoreApplication::translate("ChatGui", "Open Connection Monitor"), connPage); openBtn->setStyleSheet("QPushButton { padding: 10px 20px; font-size: 13px; }"); connect(openBtn, &QPushButton::clicked, this, [] { ConnMonitorWindow::showMonitor(); }); connLayout->addWidget(openBtn); m_pages->addWidget(connPage); } // Database page m_databasePage = new DatabaseSettingsPage(db, m_pages); m_pages->addWidget(m_databasePage); // Status page m_statusPage = new StatusPage(node, m_pages); m_pages->addWidget(m_statusPage); // Nodes page m_nodesPage = new NodesPage(db, m_pages); m_pages->addWidget(m_nodesPage); m_soundPage = new SoundSettingsPage(db, m_pages); m_pages->addWidget(m_soundPage); m_audioDevicePage = new AudioDeviceSettingsPage(db, recorder, m_pages); m_audioDevicePage->setConfigPath(m_configPath); m_audioDevicePage->loadFromDb(); m_pages->addWidget(m_audioDevicePage); auto* radioScroll = new QScrollArea(m_pages); radioScroll->setWidgetResizable(true); radioScroll->setFrameShape(QFrame::NoFrame); m_radioPage = new RadioSettingsPage(db, radioScroll); radioScroll->setWidget(m_radioPage); m_pages->addWidget(radioScroll); auto* streamPage = new QWidget(m_pages); auto* streamForm = new QFormLayout(streamPage); m_streamSubblocks = new QSpinBox(streamPage); m_streamSubblocks->setObjectName(QStringLiteral("streamSubblocks")); m_streamSubblocks->setRange(1, 255); m_streamRedundant = new QSpinBox(streamPage); m_streamRedundant->setObjectName(QStringLiteral("streamRedundant")); QSettings streamConfig(m_configPath, QSettings::IniFormat); m_streamSubblocks->setValue(streamConfig.value("chatserver/stream_subblocks", 8).toInt()); m_streamRedundant->setRange(0, m_streamSubblocks->value() - 1); m_streamRedundant->setValue(streamConfig.value("chatserver/stream_rs_redundant", 0).toInt()); connect(m_streamSubblocks, QOverload::of(&QSpinBox::valueChanged), this, [this](int n) { m_streamRedundant->setMaximum(n - 1); }); streamForm->addRow(QCoreApplication::translate("ChatGui", "Total subblocks:"), m_streamSubblocks); streamForm->addRow(QCoreApplication::translate("ChatGui", "Redundant RS subblocks:"), m_streamRedundant); auto* streamNote = new QLabel(QCoreApplication::translate("ChatGui", "The total includes redundant subblocks. Zero redundancy is allowed. Changes apply to the next broadcast."), streamPage); streamNote->setWordWrap(true); streamForm->addRow(streamNote); m_pages->addWidget(streamPage); m_bluetoothPage = new BluetoothSettingsPage(m_configPath, node, m_pages); m_pages->addWidget(m_bluetoothPage); rightLayout->addWidget(m_pages, 1); // --- Bottom buttons --- auto* btnLayout = new QHBoxLayout(); btnLayout->setSpacing(8); auto* editConfigBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Edit config"), rightPanel); editConfigBtn->setObjectName(QStringLiteral("editConfig")); connect(editConfigBtn, &QPushButton::clicked, this, &SettingsDialog::onEditConfig); btnLayout->addWidget(editConfigBtn); btnLayout->addStretch(); 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); }" "QPushButton:hover { background: palette(light); }"); connect(cancelBtn, &QPushButton::clicked, this, &QDialog::reject); btnLayout->addWidget(cancelBtn); 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;" " padding: 6px 16px; font-size: 13px; font-weight: bold;" " background: #3390EC; color: white; }" "QPushButton:hover { background: #2B7ED4; }"); connect(m_saveBtn, &QPushButton::clicked, this, &SettingsDialog::onSave); btnLayout->addWidget(m_saveBtn); rightLayout->addLayout(btnLayout); mainLayout->addWidget(rightPanel, 1); connect(m_categoryList, &QListWidget::currentRowChanged, this, &SettingsDialog::onCategoryChanged); // Черновик всех редактируемых страниц загружаем один раз на время жизни диалога. loadProfileFromConfig(m_configPath); if (db) { int sortMode = db->getUiStateInt("channel_sort", 0); m_sortCombo->setCurrentIndex(sortMode == 1 ? 1 : 0); int joinPolicy = db->getUiStateInt("join_policy", 1); m_joinPolicyCombo->setCurrentIndex(joinPolicy >= 0 && joinPolicy <= 2 ? joinPolicy : 1); m_autoConnectCheck->setChecked(db->getUiStateInt("group_autoconnect", 1) != 0); } m_networkPage->loadFromConfig(m_configPath); m_storagePage->loadFromDb(); m_soundPage->loadFromDb(); DEBUG_DEBUG(DEBUG_CATEGORY_CONFIG, "Settings: draft initialized; editable pages retain values until Save or Cancel"); m_categoryList->setCurrentRow(0); } void SettingsDialog::loadProfileFromConfig(const QString& path) { QFile f(path); if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) return; QTextStream in(&f); while (!in.atEnd()) { QString line = in.readLine().trimmed(); if (line.startsWith("my_node_name=")) { QString nm = line.mid(QString("my_node_name=").size()).trimmed(); m_nicknameEdit->setText(nm); m_oldNickname = nm; break; } } } void SettingsDialog::onCategoryChanged(int row) { m_pages->setCurrentIndex(row); DEBUG_DEBUG(DEBUG_CATEGORY_CONFIG, "Settings: selected page=%d; draft retained", row); // При переключении обновляем только диагностические страницы. if (row == 4) m_databasePage->refreshStatus(); if (row == 5) m_statusPage->refreshStatus(); if (row == 6) m_nodesPage->refreshNodes(); } void SettingsDialog::onSave() { if (!m_bluetoothPage->save()) { QMessageBox::warning(this, QCoreApplication::translate("ChatGui", "Settings"), QCoreApplication::translate("ChatGui", "Failed to save Bluetooth settings. Check the configuration file.")); return; } // Меняем только разрешение публикации, сохраняя остальные настройки входящих вызовов. QSettings callConfig(m_configPath, QSettings::IniFormat); int callPolicy = callConfig.value("chatserver/incoming_call_policy", CALL_POLICY_PUBLISH_PHONE).toInt(); callPolicy = (callPolicy & ~CALL_POLICY_PUBLISH_PHONE) | (m_publishCallStatus->isChecked() ? CALL_POLICY_PUBLISH_PHONE : 0); if (!config_update_option(m_configPath, "chatserver", "incoming_call_policy", QString::number(callPolicy))) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Call status: failed to save policy=%d path=%s", callPolicy, qPrintable(m_configPath)); QMessageBox::warning(this, QCoreApplication::translate("ChatGui", "Settings"), QCoreApplication::translate("ChatGui", "Failed to save call status settings. Check the configuration file.")); return; } if (gui_bridge_is_uasync_ready() && gui_bridge_get_inst()) { gui_bridge_post_setting(GUI_SETTING_CHAT, "incoming_call_policy", QByteArray::number(callPolicy).constData()); } else DEBUG_WARN(DEBUG_CATEGORY_CALL, "Call status: core unavailable; saved policy=%d applies at startup", callPolicy); DEBUG_INFO(DEBUG_CATEGORY_CALL, "Call status: sharing=%d policy=%d saved", m_publishCallStatus->isChecked(), callPolicy); QString newName = m_nicknameEdit->text().trimmed(); bool nameChanged = (newName != m_oldNickname); /* update name in uTun if changed */ if (nameChanged) { gui_bridge_update_name(newName.toUtf8().constData()); m_oldNickname = newName; } /* Save nickname to config — только при изменении имени, иначе перезапись плодит дубли */ if (nameChanged) { QFile f(m_configPath); if (f.open(QIODevice::ReadOnly | QIODevice::Text)) { QString content = f.readAll(); f.close(); QTextStream ts(&content); QStringList lines; while (!ts.atEnd()) lines << ts.readLine(); bool found = false; for (int i = 0; i < lines.size(); i++) { if (lines[i].trimmed().startsWith("my_node_name=")) { if (!found) { lines[i] = "my_node_name=" + newName; found = true; } else lines.removeAt(i--); /* дубль — удалить */ } } if (!found) { for (int i = 0; i < lines.size(); i++) { if (lines[i].trimmed() == "[global]") { lines.insert(i + 1, "my_node_name=" + newName); found = true; break; } } if (!found) lines << "my_node_name=" + newName; } if (f.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { f.write(lines.join('\n').toUtf8()); f.close(); } } } for (const auto& setting : {QPair{"stream_subblocks", m_streamSubblocks->value()}, QPair{"stream_rs_redundant", m_streamRedundant->value()}}) { if (!config_update_option(m_configPath, "chatserver", setting.first, QString::number(setting.second))) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Broadcast settings: failed to save %s", setting.first); QMessageBox::warning(this, QCoreApplication::translate("ChatGui", "Broadcast"), QCoreApplication::translate("ChatGui", "Failed to save broadcast settings. Check the configuration file.")); return; } gui_bridge_post_setting(GUI_SETTING_CHAT, setting.first, QByteArray::number(setting.second).constData()); } DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Broadcast settings saved n=%d r=%d; apply on next start", m_streamSubblocks->value(), m_streamRedundant->value()); if (!m_networkPage->saveToConfig(m_configPath)) { QMessageBox::warning(this, QCoreApplication::translate("ChatGui", "Settings"), QCoreApplication::translate("ChatGui", "Failed to save network settings. Check the configuration file.")); return; } for (const auto& setting : {QPair{"local_invites_enabled", m_networkPage->localInvitesEnabled()}, QPair{"local_broadcasts_enabled", m_networkPage->localBroadcastsEnabled()}}) { if (!gui_bridge_is_uasync_ready() || !gui_bridge_get_inst()) continue; gui_bridge_post_setting(GUI_SETTING_CHAT, setting.first, setting.second ? "1" : "0"); } /* save channel sort preference to ui_state */ gui_bridge_post_setting(GUI_SETTING_UI_STATE, "channel_sort", QByteArray::number(m_sortCombo->currentIndex()).constData()); m_soundPage->applyAndSave(); m_audioDevicePage->applyAndSave(); m_storagePage->applyAndSave(); if (!m_radioPage->apply()) { 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; } /* Физические сочетания сохраняем в БД; Wayland-хоткеями владеет системный портал. */ if (!PttHotkeyManager::usesPortal()) { gui_bridge_post_setting(GUI_SETTING_UI_STATE, "radio_ptt_hotkeys", m_radioPage->hotkeys().join('|').toUtf8().constData()); } const QPair policies[] = {{"join_policy", m_joinPolicyCombo->currentIndex()}, {"group_autoconnect", m_autoConnectCheck->isChecked() ? 1 : 0}}; for (const auto& setting : policies) { const QByteArray value = QByteArray::number(setting.second); gui_bridge_post_setting(GUI_SETTING_UI_STATE, setting.first, value.constData()); if (m_configPath.isEmpty()) continue; if (!config_update_option(m_configPath, "chatserver", setting.first, QString::fromLatin1(value))) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Settings: config save failed key=%s", setting.first); continue; } gui_bridge_post_setting(GUI_SETTING_CHAT, setting.first, value.constData()); } 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(); } void SettingsDialog::onEditConfig() { const QString path = QFileInfo(m_configPath).absoluteFilePath(); #ifdef Q_OS_WIN QProcess editor; editor.setProgram(QStringLiteral("notepad.exe")); editor.setArguments({path}); const bool opened = editor.startDetached(); if (!opened) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Settings: cannot start Notepad path=%s error=%s", qPrintable(path), qPrintable(editor.errorString())); } #else const bool opened = QDesktopServices::openUrl(QUrl::fromLocalFile(path)); if (!opened) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Settings: desktop service could not open config path=%s", qPrintable(path)); } #endif if (!opened) { QMessageBox::warning(this, QCoreApplication::translate("ChatGui", "Edit config"), QCoreApplication::translate("ChatGui", "Could not open the configuration file in a text editor:\n%1").arg(path)); return; } DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Settings: config editor launched path=%s", qPrintable(path)); }