diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 2348241..7edf7ea 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -33,7 +33,8 @@ MainWindow::MainWindow(QWidget *parent) : theme_name = Settings::getInstance()->get_theme_name(); } catch (...) { - theme_name = "default"; + qDebug() << __func__ << ": exception!"; + theme_name = "dark"; } this->slot_change_theme(theme_name); @@ -185,6 +186,119 @@ void MainWindow::doClose() { closeEvent(nullptr); } +// Called every time, when a menu entry of the language menu is called +void MainWindow::slotLanguageChanged(QString lang) +{ + qDebug() << __func__ << ": lang=" << lang; + if(lang != "") { + // load the language + loadLanguage(lang); + + QDialog settingsDialog(this); + qDebug() << __func__ << ": retranslating settingsDialog"; + settings.retranslateUi(&settingsDialog); + } +} + +void switchTranslator(QTranslator& translator, const QString& filename) { + qDebug() << __func__ << ": filename=" << filename; + // remove the old translator + qApp->removeTranslator(&translator); + + // load the new translator + QString path = QApplication::applicationDirPath(); + path.append("/res/"); + qDebug() << __func__ << ": attempting to load " << path + filename; + if(translator.load(path + filename)) { + qApp->installTranslator(&translator); + } else { + qDebug() << __func__ << ": translation path does not exist! " << path + filename; + } +} + +void MainWindow::loadLanguage(QString& rLanguage) { + qDebug() << __func__ << ": currLang=" << m_currLang << " rLanguage=" << rLanguage; + + QString lang = rLanguage; + + // this allows us to call this function with just a locale such as "zh" + if(lang.right(1) == ")") { + lang.chop(1); // remove trailing ) + } + + // remove everything up to and including the first ( + lang = lang.remove(0, lang.indexOf("(") + 1); + + // NOTE: language codes can be 2 or 3 letters + // https://www.loc.gov/standards/iso639-2/php/code_list.php + + QString languageName; + if(m_currLang != lang) { + qDebug() << __func__ << ": changing language to lang=" << lang; + m_currLang = lang; + QLocale locale = QLocale(m_currLang); + + qDebug() << __func__ << ": locale nativeLanguage=" << locale.nativeLanguageName(); + + // an invalid locale such as "zz" will give the C locale which has no native language name + if (locale.nativeLanguageName() == "") { + qDebug() << __func__ << ": detected invalid language in config file, defaulting to en"; + locale = QLocale("en"); + Settings::getInstance()->set_language("en"); + m_currLang = "en"; + lang = "en"; + } + qDebug() << __func__ << ": locale=" << locale; + QLocale::setDefault(locale); + qDebug() << __func__ << ": setDefault locale=" << locale; + languageName = locale.nativeLanguageName(); //locale.language()); + qDebug() << __func__ << ": languageName=" << languageName; + + switchTranslator(m_translator, QString("silentdragon_%1.qm").arg(lang)); + switchTranslator(m_translatorQt, QString("qt_%1.qm").arg(lang)); + + // TODO: this likely wont work for RTL languages like Arabic + auto first = QString(languageName.at(0)).toUpper(); + languageName = first + languageName.right(languageName.size()-1); + if( lang == "en" ) { + languageName.replace("American ",""); + } + ui->statusBar->showMessage(tr("Language changed to") + " " + languageName + " (" + lang + ")"); + } + + // write this language (the locale shortcode) out to config file + if (lang != "") { + // only write valid languages to config file + Settings::getInstance()->set_language(lang); + } +} + +void MainWindow::changeEvent(QEvent* event) { + if(0 != event) { + switch(event->type()) { + // this event is sent if a translator is loaded + case QEvent::LanguageChange: + qDebug() << __func__ << ": QEvent::LanguageChange changeEvent"; + ui->retranslateUi(this); + break; + + // this event is sent, if the system, language changes + case QEvent::LocaleChange: + { + QString locale = QLocale::system().name(); + locale.truncate(locale.lastIndexOf('_')); + qDebug() << __func__ << ": QEvent::LocaleChange changeEvent locale=" << locale; + loadLanguage(locale); + } + break; + default: + qDebug() << __func__ << ": " << event->type(); + } + } + QMainWindow::changeEvent(event); +} + + void MainWindow::closeEvent(QCloseEvent* event) { QSettings s; @@ -265,7 +379,7 @@ void MainWindow::setupSettingsModal() { // Set up File -> Settings action QObject::connect(ui->actionSettings, &QAction::triggered, [=]() { QDialog settingsDialog(this); - Ui_Settings settings; + //Ui_Settings settings; settings.setupUi(&settingsDialog); Settings::saveRestore(&settingsDialog); @@ -300,9 +414,12 @@ void MainWindow::setupSettingsModal() { QObject::connect(settings.comboBoxTheme, &QComboBox::currentTextChanged, [=] (QString theme_name) { this->slot_change_theme(theme_name); - QMessageBox::information(this, tr("Theme Change"), tr("This change can take a few seconds."), QMessageBox::Ok); + // QMessageBox::information(this, tr("Theme Change"), tr("This change can take a few seconds."), QMessageBox::Ok); + // For some reason, changing language also triggers this + //ui->statusBar->showMessage(tr("Theme changed to ") + theme_name); }); + // Set local currency QString ticker = Settings::getInstance()->get_currency_name(); int currency_index = settings.comboBoxCurrency->findText(ticker, Qt::MatchExactly); @@ -310,7 +427,8 @@ void MainWindow::setupSettingsModal() { QObject::connect(settings.comboBoxCurrency, &QComboBox::currentTextChanged, [=] (QString ticker) { this->slot_change_currency(ticker); rpc->refresh(true); - QMessageBox::information(this, tr("Currency Change"), tr("This change can take a few seconds."), QMessageBox::Ok); + ui->statusBar->showMessage(tr("Currency changed to") + " " + ticker); + // QMessageBox::information(this, tr("Currency Change"), tr("This change can take a few seconds."), QMessageBox::Ok); }); // Save sent transactions @@ -343,9 +461,10 @@ void MainWindow::setupSettingsModal() { } //Use Consolidation - bool isUsingConsolidation = false; int size = 0; + qDebug() << __func__ << ": hushDir=" << rpc->getConnection()->config->hushDir; + QDir hushdir(rpc->getConnection()->config->hushDir); QFile WalletSize(hushdir.filePath("wallet.dat")); if (WalletSize.open(QIODevice::ReadOnly)){ @@ -418,8 +537,89 @@ void MainWindow::setupSettingsModal() { settings.testnetTxExplorerUrl->setText(explorer.testnetTxExplorerUrl); settings.testnetAddressExplorerUrl->setText(explorer.testnetAddressExplorerUrl); - // Connection tab by default - settings.tabWidget->setCurrentIndex(0); + // format systems language + QString defaultLocale = QLocale::system().name(); // e.g. "de_DE" + defaultLocale.truncate(defaultLocale.lastIndexOf('_')); // e.g. "de" + + // Set the current language to the default system language + // TODO: this will need to change when we read/write selected language to config on disk + //m_currLang = defaultLocale; + //qDebug() << __func__ << ": changed m_currLang to " << defaultLocale; + + m_currLang = Settings::getInstance()->get_language(); + qDebug() << __func__ << ": got a currLang=" << m_currLang << " from config file"; + + //QString defaultLang = QLocale::languageToString(QLocale("en").language()); + settings.comboBoxLanguage->addItem("English (en)"); + + m_langPath = QApplication::applicationDirPath(); + m_langPath.append("/res"); + + qDebug() << __func__ <<": defaultLocale=" << defaultLocale << " m_langPath=" << m_langPath;; + + QDir dir(m_langPath); + QStringList fileNames = dir.entryList(QStringList("silentdragon_*.qm")); + + qDebug() << __func__ <<": found " << fileNames.size() << " translations"; + + + // create language drop down dynamically + for (int i = 0; i < fileNames.size(); ++i) { + // get locale extracted by filename + QString locale; + locale = fileNames[i]; // "silentdragon_de.qm" + locale.truncate(locale.lastIndexOf('.')); // "silentdragon_de" + locale.remove(0, locale.lastIndexOf('_') + 1); // "de" + + QString lang = QLocale(locale).nativeLanguageName(); //locale.language()); + + // TODO: this likely wont work for RTL languages like Arabic + // uppercase the first letter of all languages + auto first = QString(lang.at(0)).toUpper(); + lang = first + lang.right(lang.size()-1); + + //settings.comboBoxLanguage->addItem(action); + settings.comboBoxLanguage->addItem(lang + " (" + locale + ")"); + qDebug() << __func__ << ": added lang=" << lang << " locale=" << locale << " defaultLocale=" << defaultLocale << " m_currLang=" << m_currLang; + qDebug() << __func__ << ": m_currLang=" << m_currLang << " ?= locale=" << locale; + + //if (defaultLocale == locale) { + if (m_currLang == locale) { + settings.comboBoxLanguage->setCurrentIndex(i+1); + qDebug() << " set defaultLocale=" << locale << " to checked!!!"; + } + } + + settings.comboBoxLanguage->model()->sort(0,Qt::AscendingOrder); + qDebug() << __func__ <<": sorted translations"; + + //QString lang = QLocale::languageToString(QLocale(m_currLang).language()); + QString lang = QLocale(m_currLang).nativeLanguageName(); //locale.language()); + + auto first = QString(lang.at(0)).toUpper(); + lang = first + lang.right(lang.size()-1); + + if (m_currLang == "en") { + // we have just 1 English translation + // en_US will render as "American English", so fix that + lang.replace("American ",""); + } + + qDebug() << __func__ << ": looking for " << lang + " (" + m_currLang + ")"; + //qDebug() << __func__ << ": looking for " << m_currLang; + int lang_index = settings.comboBoxLanguage->findText(lang + " (" + m_currLang + ")", Qt::MatchExactly); + + qDebug() << __func__ << ": setting comboBoxLanguage index to " << lang_index; + settings.comboBoxLanguage->setCurrentIndex(lang_index); + + QObject::connect(settings.comboBoxLanguage, &QComboBox::currentTextChanged, [=] (QString lang) { + qDebug() << "comboBoxLanguage.currentTextChanged lang=" << lang; + this->slotLanguageChanged(lang); + //QMessageBox::information(this, tr("Language Changed"), tr("This change can take a few seconds."), QMessageBox::Ok); + }); + + // Options tab by default + settings.tabWidget->setCurrentIndex(1); // Enable the troubleshooting options only if using embedded hushd if (!rpc->isEmbedded()) { @@ -1747,7 +1947,7 @@ void MainWindow::updateLabels() { void MainWindow::slot_change_currency(const QString& currency_name) { - qDebug() << "slot_change_currency"; //<< ": " << currency_name; + qDebug() << __func__ << ": " << currency_name; Settings::getInstance()->set_currency_name(currency_name); qDebug() << "Refreshing price stats after currency change"; rpc->refreshPrice(); @@ -1762,9 +1962,17 @@ void MainWindow::slot_change_currency(const QString& currency_name) } } -void MainWindow::slot_change_theme(const QString& theme_name) +void MainWindow::slot_change_theme(QString& theme_name) { - Settings::getInstance()->set_theme_name(theme_name); + qDebug() << __func__ << ": theme_name=" << theme_name; + + if (theme_name == "dark" || theme_name == "default" || theme_name == "light" || + theme_name == "midnight" || theme_name == "blue") { + Settings::getInstance()->set_theme_name(theme_name); + } else { + qDebug() << __func__ << ": ignoring invalid theme_name=" << theme_name; + Settings::getInstance()->set_theme_name("dark"); + } // Include css QString saved_theme_name; @@ -1772,10 +1980,12 @@ void MainWindow::slot_change_theme(const QString& theme_name) saved_theme_name = Settings::getInstance()->get_theme_name(); } catch (const std::exception& e) { qDebug() << QString("Ignoring theme change Exception! : "); - saved_theme_name = "default"; + saved_theme_name = "dark"; } - QFile qFile(":/css/res/css/" + saved_theme_name +".css"); + QString filename = ":/css/res/css/" + saved_theme_name +".css"; + QFile qFile(filename); + qDebug() << __func__ << ": attempting to open filename=" << filename; if (qFile.open(QFile::ReadOnly)) { QString styleSheet = QLatin1String(qFile.readAll()); diff --git a/src/mainwindow.h b/src/mainwindow.h index ecf9edd..135233f 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -6,6 +6,7 @@ #include "precompiled.h" #include "logger.h" #include +#include "ui_settings.h" // Forward declare to break circular dependency. class RPC; @@ -64,6 +65,7 @@ public: void updateFromCombo(); Ui::MainWindow* ui; + Ui_Settings settings; QLabel* statusLabel; QLabel* statusIcon; @@ -72,10 +74,21 @@ public: Logger* logger; void doClose(); + // loads a language by the given language shortcode (e.g. de, en) + void loadLanguage(QString& rLanguage); + +protected: + // this event is called, when a new translator is loaded or the system language is changed + void changeEvent(QEvent* event); + +protected slots: + // this slot is called by the language menu actions + void slotLanguageChanged(QString lang); private: void closeEvent(QCloseEvent* event); + void setupSendTab(); void setupPeersTab(); void setupTransactionsTab(); @@ -85,8 +98,9 @@ private: void setupChatTab(); void setupMarketTab(); - void slot_change_theme(const QString& themeName); + void slot_change_theme(QString& themeName); void slot_change_currency(const QString& currencyName); + void setupTurnstileDialog(); void setupSettingsModal(); void setupStatusBar(); @@ -144,6 +158,13 @@ private: QRegExpValidator* feesValidator = nullptr; QMovie* loadingMovie; + // creates the language menu dynamically from the content of m_langPath + void createLanguageMenu(void); + + QTranslator m_translator; // contains the translations for this application + QTranslator m_translatorQt; // contains the translations for qt + QString m_currLang; // contains the currently loaded language + QString m_langPath; // Path of language files }; #endif // MAINWINDOW_H diff --git a/src/rpc.cpp b/src/rpc.cpp index 378c755..cbd7216 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp @@ -63,6 +63,12 @@ RPC::RPC(MainWindow* main) { qDebug() << __func__ << "Done settings up all timers"; usedAddresses = new QMap(); + + auto lang = Settings::getInstance()->get_language(); + qDebug() << __func__ << ": found lang="<< lang << " in config file"; + + main->loadLanguage(lang); + qDebug() << __func__ << ": setting UI to lang="<< lang << " found in config file"; } RPC::~RPC() { diff --git a/src/settings.cpp b/src/settings.cpp index 07eb0b1..95f2fba 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -40,9 +40,20 @@ Explorer Settings::getExplorer() { auto txExplorerUrl = s.value("explorer/txExplorerUrl", explorer + "/tx/").toString(); auto addressExplorerUrl = s.value("explorer/addressExplorerUrl", explorer + "/address/").toString(); + auto testnetTxExplorerUrl = s.value("explorer/testnetTxExplorerUrl").toString(); auto testnetAddressExplorerUrl = s.value("explorer/testnetAddressExplorerUrl").toString(); + // Some users have the old malicious explorer URL saved in their config file, help them out + if (txExplorerUrl == "https://explorer.myhush.org/tx/") { + txExplorerUrl = explorer + "/tx/"; + saveExplorer(txExplorerUrl, addressExplorerUrl, testnetTxExplorerUrl, testnetAddressExplorerUrl); + } + if (addressExplorerUrl == "https://explorer.myhush.org/address/") { + addressExplorerUrl = explorer + "/address/"; + saveExplorer(txExplorerUrl, addressExplorerUrl, testnetTxExplorerUrl, testnetAddressExplorerUrl); + } + return Explorer{txExplorerUrl, addressExplorerUrl, testnetTxExplorerUrl, testnetAddressExplorerUrl}; } @@ -246,10 +257,13 @@ void Settings::setAllowCustomFees(bool allow) { QString Settings::get_theme_name() { // Load from the QT Settings. - return QSettings().value("options/theme_name", false).toString(); + QString theme_name = QSettings().value("options/theme_name", false).toString(); + qDebug() << __func__ << ": theme_name=" << theme_name; + return theme_name; } void Settings::set_theme_name(QString theme_name) { + qDebug() << __func__ << ": settings theme_name=" << theme_name; QSettings().setValue("options/theme_name", theme_name); } @@ -351,6 +365,21 @@ void Settings::set_currency_name(QString currency_name) { QSettings().setValue("options/currency_name", currency_name); } +QString Settings::get_language() { + // use the default system language if none is set + QString locale = QLocale::system().name(); + // remove country data, i.e. en_US => en + locale.truncate( locale.lastIndexOf("_")); + auto lang = QSettings().value("options/language", locale).toString(); + qDebug() << __func__ << ": found lang=" << lang << " in config file"; + return lang; +} + +void Settings::set_language(QString lang) { + qDebug() << __func__ << ": setting lang=" << lang << " in config file"; + QSettings().setValue("options/language", lang); +} + bool Settings::removeFromHushConf(QString confLocation, QString option) { if (confLocation.isEmpty()) diff --git a/src/settings.h b/src/settings.h index d71d26f..32909d5 100644 --- a/src/settings.h +++ b/src/settings.h @@ -89,6 +89,9 @@ public: QString get_currency_name(); void set_currency_name(QString currency_name); + QString get_language(); + void set_language(QString lang); + void setUsingHushConf(QString confLocation); const QString& getHushdConfLocation() { return _confLocation; } @@ -132,6 +135,7 @@ public: static double getZboardAmount(); static QString getZboardAddr(); + //TODO: this could be an advanced setting too static int getMaxMobileAppTxns() { return 30; } static bool isValidAddress(QString addr); @@ -146,6 +150,10 @@ public: static const int quickUpdateSpeed = 3 * 1000; // 3 sec static const int priceRefreshSpeed = 15 * 60 * 1000; // 15 mins +protected: + // this event is called, when a new translator is loaded or the system language is changed + // void changeEvent(QEvent* event); + private: // This class can only be accessed through Settings::getInstance() Settings() = default; @@ -158,12 +166,11 @@ private: bool _isTestnet = false; bool _isSyncing = false; int _blockNumber = 0; - int _hushdVersion = 0; + int _hushdVersion = 0; bool _useEmbedded = false; bool _headless = false; int _peerConnections = 0; - - double hushPrice = 0.0; + double hushPrice = 0.0; double fiat_price = 0.0; unsigned int btcPrice = 0; std::map prices; diff --git a/src/settings.ui b/src/settings.ui index 0850bce..270fdff 100644 --- a/src/settings.ui +++ b/src/settings.ui @@ -26,7 +26,7 @@ - 3 + 1 @@ -177,6 +177,32 @@ + + + + + 0 + 0 + + + + Language + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + @@ -412,27 +438,27 @@ - default + default - blue + blue - light + light - dark + dark - midnight + midnight diff --git a/src/websockets.cpp b/src/websockets.cpp index 743f5c5..8ec377c 100644 --- a/src/websockets.cpp +++ b/src/websockets.cpp @@ -153,6 +153,7 @@ void WormholeClient::connect() { void WormholeClient::retryConnect() { QTimer::singleShot(5 * 1000 * pow(2, retryCount), [=]() { + qDebug() << __func__ << ": retryCount=" << retryCount; if (retryCount < 10) { qDebug() << "Retrying websocket connection, count=" << this->retryCount; this->retryCount++; @@ -210,10 +211,10 @@ void WormholeClient::onConnected() // On connected, we'll also create a timer to ping it every 4 minutes, since the websocket // will timeout after 5 minutes timer = new QTimer(parent); - qDebug() << "Created QTimer"; + qDebug() << __func__ << ": Created QTimer"; QObject::connect(timer, &QTimer::timeout, [=]() { - qDebug() << "Timer timeout!"; try { + qDebug() << __func__ << ": Timer timeout! shuttingDown=" << shuttingDown << " m_webSocket=" << m_webSocket << " isValid=" << m_webSocket->isValid(); if (!shuttingDown && m_webSocket && m_webSocket->isValid()) { auto payload = QJsonDocument(QJsonObject { {"ping", "ping"} }).toJson(); qint64 bytes = m_webSocket->sendTextMessage(payload); @@ -276,6 +277,7 @@ QString AppDataServer::getSecretHex() { } void AppDataServer::saveNewSecret(QString secretHex) { + qDebug() << __func__ << ": saving secretHex to config file"; QSettings().setValue("mobileapp/secret", secretHex); if (secretHex.isEmpty())