diff --git a/OptionsPopup.qml b/OptionsPopup.qml index c706b9b..ec018c2 100644 --- a/OptionsPopup.qml +++ b/OptionsPopup.qml @@ -105,6 +105,7 @@ Popup { } Layout.minimumWidth: 250 Layout.maximumHeight: 40 + enabled: !imageWriter.isEmbeddedMode() } } @@ -333,10 +334,10 @@ Popup { text: qsTr("Keyboard layout:") color: parent.enabled ? "black" : "grey" } - TextField { + ComboBox { id: fieldKeyboardLayout + editable: true Layout.minimumWidth: 200 - text: "us" } CheckBox { id: chkSkipFirstUse @@ -431,6 +432,7 @@ Popup { fieldTimezone.model = imageWriter.getTimezoneList() fieldPublicKey.text = imageWriter.getDefaultPubKey() fieldWifiCountry.model = imageWriter.getCountryList() + fieldKeyboardLayout.model = imageWriter.getKeymapLayoutList() if (Object.keys(settings).length) { comboSaveSettings.currentIndex = 1 @@ -495,13 +497,25 @@ Popup { fieldTimezone.currentIndex = tzidx } if ('keyboardLayout' in settings) { - fieldKeyboardLayout.text = settings.keyboardLayout + fieldKeyboardLayout.currentIndex = fieldKeyboardLayout.find(settings.keyboardLayout) + if (fieldKeyboardLayout.currentIndex == -1) { + fieldKeyboardLayout.editText = settings.keyboardLayout + } } else { - /* Lacking an easy cross-platform to fetch keyboard layout - from host system, just default to "gb" for people in - UK time zone for now, and "us" for everyone else */ - if (tz == "Europe/London") { - fieldKeyboardLayout.text = "gb" + if (imageWriter.isEmbeddedMode()) + { + fieldKeyboardLayout.currentIndex = fieldKeyboardLayout.find(imageWriter.getCurrentKeyboard()) + } + else + { + /* Lacking an easy cross-platform to fetch keyboard layout + from host system, just default to "gb" for people in + UK time zone for now, and "us" for everyone else */ + if (tz == "Europe/London") { + fieldKeyboardLayout.currentIndex = fieldKeyboardLayout.find("gb") + } else { + fieldKeyboardLayout.currentIndex = fieldKeyboardLayout.find("us") + } } } if ('skipFirstUse' in settings) { @@ -662,7 +676,7 @@ Popup { } var kbdconfig = "XKBMODEL=\"pc105\"\n" - kbdconfig += "XKBLAYOUT=\""+fieldKeyboardLayout.text+"\"\n" + kbdconfig += "XKBLAYOUT=\""+fieldKeyboardLayout.editText+"\"\n" kbdconfig += "XKBVARIANT=\"\"\n" kbdconfig += "XKBOPTIONS=\"\"\n" @@ -729,7 +743,7 @@ Popup { } if (chkLocale.checked) { settings.timezone = fieldTimezone.editText - settings.keyboardLayout = fieldKeyboardLayout.text + settings.keyboardLayout = fieldKeyboardLayout.editText if (chkSkipFirstUse.checked) { settings.skipFirstUse = true } diff --git a/imagewriter.cpp b/imagewriter.cpp index 3e97cd7..e2256df 100644 --- a/imagewriter.cpp +++ b/imagewriter.cpp @@ -56,10 +56,14 @@ #include #endif +#ifdef QT_NO_WIDGETS +#include +#endif + ImageWriter::ImageWriter(QObject *parent) : QObject(parent), _repo(QUrl(QString(OSLIST_URL))), _dlnow(0), _verifynow(0), _engine(nullptr), _thread(nullptr), _verifyEnabled(false), _cachingEnabled(false), - _embeddedMode(false), _online(false) + _embeddedMode(false), _online(false), _trans(nullptr) { connect(&_polltimer, SIGNAL(timeout()), SLOT(pollProgress())); @@ -78,6 +82,9 @@ ImageWriter::ImageWriter(QObject *parent) _embeddedMode = true; connect(&_networkchecktimer, SIGNAL(timeout()), SLOT(pollNetwork())); _networkchecktimer.start(100); + changeKeyboard(detectPiKeyboard()); + if (_currentKeyboard.isEmpty()) + _currentKeyboard = "us"; } #ifdef Q_OS_WIN @@ -125,11 +132,41 @@ ImageWriter::ImageWriter(QObject *parent) } } _settings.endGroup(); + + QDir dir(":/i18n", "rpi-imager_*.qm"); + QStringList transFiles = dir.entryList(); + QLocale currentLocale; + QStringList localeComponents = currentLocale.name().split('_'); + QString currentlangcode; + if (!localeComponents.isEmpty()) + currentlangcode = localeComponents.first(); + + for (QString tf : transFiles) + { + QString langcode = tf.mid(11, tf.length()-14); + /* FIXME: we currently lack a font with support for Chinese characters in embedded mode */ + if (isEmbeddedMode() && langcode == "zh") + continue; + + QLocale loc(langcode); + /* Use "English" for "en" and not "American English" */ + QString langname = (langcode == "en" ? "English" : loc.nativeLanguageName() ); + _translations.insert(langname, langcode); + if (langcode == currentlangcode) + { + _currentLang = langname; + } + } + //_currentKeyboard = "us"; } ImageWriter::~ImageWriter() { - + if (_trans) + { + QCoreApplication::removeTranslator(_trans); + delete _trans; + } } void ImageWriter::setEngine(QQmlApplicationEngine *engine) @@ -153,6 +190,9 @@ void ImageWriter::setSrc(const QUrl &url, quint64 downloadLen, quint64 extrLen, { QFileInfo fi(url.toLocalFile()); _downloadLen = fi.size(); + } + if (url.isLocalFile()) + { _initFormat = "auto"; } } @@ -775,13 +815,27 @@ QStringList ImageWriter::getCountryList() QFile f(":/countries.txt"); if ( f.open(f.ReadOnly) ) { - countries = QString(f.readAll()).split('\n'); + countries = QString(f.readAll()).trimmed().split('\n'); f.close(); } return countries; } +QStringList ImageWriter::getKeymapLayoutList() +{ + QStringList keymaps; + QFile f(":/keymap-layouts.txt"); + if ( f.open(f.ReadOnly) ) + { + keymaps = QString(f.readAll()).trimmed().split('\n'); + f.close(); + } + + return keymaps; +} + + QString ImageWriter::getSSID() { /* Qt used to have proper bearer management that was able to provide a list of @@ -1032,6 +1086,133 @@ bool ImageWriter::imageSupportsCustomization() return !_initFormat.isEmpty(); } +QStringList ImageWriter::getTranslations() +{ + QStringList t = _translations.keys(); + t.sort(Qt::CaseInsensitive); + return t; +} + +QString ImageWriter::getCurrentLanguage() +{ + return _currentLang; +} + +QString ImageWriter::getCurrentKeyboard() +{ + return _currentKeyboard; +} + +void ImageWriter::changeLanguage(const QString &newLanguageName) +{ + if (newLanguageName.isEmpty() || newLanguageName == _currentLang || !_translations.contains(newLanguageName)) + return; + + QString langcode = _translations[newLanguageName]; + qDebug() << "Changing language to" << langcode; + + QTranslator *trans = new QTranslator(); + if (trans->load(":/i18n/rpi-imager_"+langcode+".qm")) + { + replaceTranslator(trans); + } + else + { + qDebug() << "Failed to load translation file"; + delete trans; + } +} + +void ImageWriter::changeKeyboard(const QString &newKeymapLayout) +{ + if (newKeymapLayout.isEmpty() || newKeymapLayout == _currentKeyboard) + return; + +#ifdef QT_NO_WIDGETS + QString kmapfile = "/usr/share/qmaps/"+newKeymapLayout+".qmap"; + + if (QFile::exists(kmapfile)) + QEglFSFunctions::loadKeymap(kmapfile); +#endif + + _currentKeyboard = newKeymapLayout; +} + +void ImageWriter::replaceTranslator(QTranslator *trans) +{ + if (_trans) + { + QCoreApplication::removeTranslator(_trans); + delete _trans; + } + + _trans = trans; + QCoreApplication::installTranslator(_trans); + + if (_engine) + { + _engine->retranslate(); + } +} + +QString ImageWriter::detectPiKeyboard() +{ + unsigned int typenr = 0; + QFile f("/proc/device-tree/chosen/rpi-country-code"); + if (f.exists() && f.open(f.ReadOnly)) + { + QByteArray d = f.readAll(); + f.close(); + + if (d.length() == 4) + { + typenr = d.at(2); + } + } + + if (!typenr) + { + QDir dir("/dev/input/by-id"); + QRegExp rx("RPI_Wired_Keyboard_([0-9]+)"); + + for (QString fn : dir.entryList(QDir::Files)) + { + if (rx.indexIn(fn) != -1) + { + typenr = rx.cap(1).toUInt(); + } + } + } + + if (typenr) + { + QStringList kbcountries = { + "", + "gb", + "fr", + "es", + "us", + "de", + "it", + "jp", + "pt", + "no", + "se", + "dk", + "ru", + "tr", + "il" + }; + + if (typenr < kbcountries.count()) + { + return kbcountries.at(typenr); + } + } + + return QString(); +} + void MountUtilsLog(std::string msg) { Q_UNUSED(msg) //qDebug() << "mountutils:" << msg.c_str(); diff --git a/imagewriter.h b/imagewriter.h index 1a3ed83..d7ab931 100644 --- a/imagewriter.h +++ b/imagewriter.h @@ -19,6 +19,7 @@ class QQmlApplicationEngine; class DownloadThread; class QNetworkReply; class QWinTaskbarButton; +class QTranslator; class ImageWriter : public QObject { @@ -97,6 +98,7 @@ public: Q_INVOKABLE QString getTimezone(); Q_INVOKABLE QStringList getTimezoneList(); Q_INVOKABLE QStringList getCountryList(); + Q_INVOKABLE QStringList getKeymapLayoutList(); Q_INVOKABLE QString getSSID(); Q_INVOKABLE QString getPSK(const QString &ssid); @@ -112,6 +114,15 @@ public: Q_INVOKABLE QString crypt(const QByteArray &password); Q_INVOKABLE QString pbkdf2(const QByteArray &psk, const QByteArray &ssid); + Q_INVOKABLE QStringList getTranslations(); + Q_INVOKABLE QString getCurrentLanguage(); + Q_INVOKABLE QString getCurrentKeyboard(); + Q_INVOKABLE void changeLanguage(const QString &newLanguageName); + Q_INVOKABLE void changeKeyboard(const QString &newKeymapLayout); + + void replaceTranslator(QTranslator *trans); + QString detectPiKeyboard(); + signals: /* We are emiting signals with QVariant as parameters because QML likes it that way */ @@ -143,7 +154,7 @@ protected slots: protected: QUrl _src, _repo; - QString _dst, _cacheFileName, _parentCategory, _osName; + QString _dst, _cacheFileName, _parentCategory, _osName, _currentLang, _currentKeyboard; QByteArray _expectedHash, _cachedFileHash, _cmdline, _config, _firstrun, _cloudinit, _cloudinitNetwork, _initFormat; quint64 _downloadLen, _extrLen, _devLen, _dlnow, _verifynow; DriveListModel _drivelist; @@ -153,6 +164,8 @@ protected: DownloadThread *_thread; bool _verifyEnabled, _multipleFilesInZip, _cachingEnabled, _embeddedMode, _online; QSettings _settings; + QMap _translations; + QTranslator *_trans; #ifdef Q_OS_WIN QWinTaskbarButton *_taskbarButton; #endif diff --git a/keymap-layouts.txt b/keymap-layouts.txt new file mode 100644 index 0000000..4a7abf3 --- /dev/null +++ b/keymap-layouts.txt @@ -0,0 +1,98 @@ +af +al +am +ara +at +au +az +ba +bd +be +bg +br +brai +bt +bw +by +ca +cd +ch +cm +cn +cz +de +dk +dz +ee +epo +es +et +fi +fo +fr +gb +ge +gh +gn +gr +hr +hu +id +ie +il +in +iq +ir +is +it +jp +jv +ke +kg +kh +kr +kz +la +latam +lk +lt +lv +ma +mao +md +me +mk +ml +mm +mn +mt +mv +my +ng +nl +no +np +ph +pk +pl +pt +ro +rs +ru +se +si +sk +sn +sy +tg +th +tj +tm +tr +tw +tz +ua +us +uz +vn +za diff --git a/linux/udisks2api.h b/linux/udisks2api.h index b81e071..2450c96 100644 --- a/linux/udisks2api.h +++ b/linux/udisks2api.h @@ -8,7 +8,8 @@ #include #include -#include + +class QDBusInterface; class UDisks2Api : public QObject { diff --git a/main.cpp b/main.cpp index 26923db..5b69bf4 100644 --- a/main.cpp +++ b/main.cpp @@ -20,6 +20,8 @@ #include #include #include +#include +#include #ifndef QT_NO_WIDGETS #include #endif @@ -63,6 +65,14 @@ int main(int argc, char *argv[]) } QGuiApplication app(argc, argv); + + /* Set default font */ + QStringList fontList = QFontDatabase::applicationFontFamilies(QFontDatabase::addApplicationFont(":/fonts/Roboto-Regular.ttf")); + QGuiApplication::setFont(QFont(fontList.first(), 10)); + + QLocale::Language l = QLocale::system().language(); + if (l == QLocale::AnyLanguage || l == QLocale::C) + QLocale::setDefault(QLocale("en")); #else QApplication app(argc, argv); #endif @@ -73,7 +83,7 @@ int main(int argc, char *argv[]) ImageWriter imageWriter; NetworkAccessManagerFactory namf; QQmlApplicationEngine engine; - QTranslator translator; + QTranslator *translator = new QTranslator; QString customQm; QSettings settings; @@ -206,13 +216,17 @@ int main(int argc, char *argv[]) QLocale::setDefault(QLocale(langcode)); #endif - if (translator.load(QLocale(), "rpi-imager", "_", QLatin1String(":/i18n"))) - QCoreApplication::installTranslator(&translator); + if (translator->load(QLocale(), "rpi-imager", "_", QLatin1String(":/i18n"))) + imageWriter.replaceTranslator(translator); + else + delete translator; } else { - if (translator.load(customQm)) - QCoreApplication::installTranslator(&translator); + if (translator->load(customQm)) + imageWriter.replaceTranslator(translator); + else + delete translator; } if (!url.isEmpty()) diff --git a/main.qml b/main.qml index e625ffc..879fb54 100644 --- a/main.qml +++ b/main.qml @@ -83,7 +83,7 @@ ApplicationWindow { anchors.rightMargin: 50 anchors.leftMargin: 50 - rows: 3 + rows: 4 columns: 3 columnSpacing: 25 @@ -259,6 +259,7 @@ ApplicationWindow { id: customizebutton source: "icons/ic_cog_40px.svg" visible: false + MouseArea { anchors.fill: parent onClicked: { @@ -267,6 +268,82 @@ ApplicationWindow { } } } + + RowLayout { + id: langbar + Layout.columnSpan: 3 + Layout.alignment: Qt.AlignCenter | Qt.AlignBottom + /* FIXME: shouldn't use anchors here. But Layout bottom alignment does not + seem to be respected */ + anchors.bottom: parent.bottom + anchors.bottomMargin: 5 + spacing: 10 + + visible: imageWriter.isEmbeddedMode() + + Rectangle { + anchors.fill: langbar + color: "#ffffe3" + radius: 5 + } + + Text { + font.pixelSize: 12 + font.family: roboto.name + text: qsTr("Language: ") + Layout.leftMargin: 30 + Layout.topMargin: 10 + Layout.bottomMargin: 10 + } + ComboBox { + font.pixelSize: 12 + font.family: roboto.name + model: imageWriter.getTranslations() + Layout.preferredWidth: 200 + currentIndex: -1 + Component.onCompleted: { + currentIndex = find(imageWriter.getCurrentLanguage()) + } + onActivated: { + imageWriter.changeLanguage(editText) + } + Layout.topMargin: 10 + Layout.bottomMargin: 10 + } + Text { + font.pixelSize: 12 + font.family: roboto.name + text: qsTr("Keyboard: ") + Layout.topMargin: 10 + Layout.bottomMargin: 10 + } + ComboBox { + enabled: imageWriter.isEmbeddedMode() + font.pixelSize: 12 + font.family: roboto.name + model: imageWriter.getKeymapLayoutList() + currentIndex: -1 + Component.onCompleted: { + currentIndex = find(imageWriter.getCurrentKeyboard()) + } + onActivated: { + imageWriter.changeKeyboard(editText) + } + Layout.topMargin: 10 + Layout.bottomMargin: 10 + Layout.rightMargin: 30 + } + } + + /* Language/keyboard bar is normally only visible in embedded mode. + To test translations also show it when shift+ctrl+L is pressed. */ + Shortcut { + sequences: ["Shift+Ctrl+L", "Shift+Meta+L"] + context: Qt.ApplicationShortcut + onActivated: { + langbar.visible = true + } + } } } } @@ -779,6 +856,7 @@ ApplicationWindow { noButton: true title: qsTr("Warning") onYes: { + langbar.visible = false writebutton.enabled = false customizebutton.visible = false cancelwritebutton.enabled = true diff --git a/qml.qrc b/qml.qrc index d650fa3..89364b9 100644 --- a/qml.qrc +++ b/qml.qrc @@ -29,5 +29,6 @@ icons/ic_info_16px.png icons/ic_info_12px.png icons/ic_cog_40px.svg + keymap-layouts.txt