diff --git a/kcms/translations/CMakeLists.txt b/kcms/translations/CMakeLists.txt new file mode 100644 index 000000000..45cab1e5a --- /dev/null +++ b/kcms/translations/CMakeLists.txt @@ -0,0 +1,26 @@ +include(ECMQMLModules) +ecm_find_qmlmodule(org.kde.plasma.core 2.0) + +# KI18N Translation Domain for this library. +add_definitions(-DTRANSLATION_DOMAIN=\"kcm_translations\") + +########### next target ############### + +set(kcm_translations_PART_SRCS translations.cpp translationsmodel.cpp) + +add_library(kcm_translations MODULE ${kcm_translations_PART_SRCS}) + +target_link_libraries(kcm_translations + KF5::I18n + KF5::KCMUtils + KF5::QuickAddons +) + +kcoreaddons_desktop_to_json(kcm_translations "kcm_translations.desktop") + +########### install files ############### + +install(TARGETS kcm_translations DESTINATION ${KDE_INSTALL_PLUGINDIR}/kcms) +install(FILES kcm_translations.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) +kpackage_install_package(package kcm_translations kcms) + diff --git a/kcms/translations/Messages.sh b/kcms/translations/Messages.sh new file mode 100644 index 000000000..96d449a97 --- /dev/null +++ b/kcms/translations/Messages.sh @@ -0,0 +1,2 @@ +#! /usr/bin/env bash +$XGETTEXT `find . -name \*.cpp -o -name \*.qml` -o $podir/kcm_translations.pot diff --git a/kcms/translations/kcm_translations.desktop b/kcms/translations/kcm_translations.desktop new file mode 100644 index 000000000..9937b6d3e --- /dev/null +++ b/kcms/translations/kcm_translations.desktop @@ -0,0 +1,55 @@ +[Desktop Entry] +Exec=kcmshell5 translations +Icon=preferences-desktop-locale +Type=Service +X-KDE-ServiceTypes=KCModule +X-DocPath=kcontrol/translations/index.html + +X-KDE-Library=kcm_translations +X-KDE-ParentApp=kcontrol + +X-KDE-System-Settings-Parent-Category=regionalsettings +X-KDE-Weight=40 + +Name=Language +Name[ca]=Idioma +Name[ca@valencia]=Idioma +Name[cs]=Jazyk +Name[da]=Sprog +Name[de]=Sprache +Name[el]=Γλώσσα +Name[en_GB]=Language +Name[es]=Idioma +Name[et]=Keel +Name[eu]=Hizkuntza +Name[fi]=Kieli +Name[fr]=Langage +Name[gl]=Idioma +Name[he]=שפה +Name[hu]=Nyelv +Name[id]=Bahasa +Name[it]=Lingua +Name[ja]=言語 +Name[ko]=언어 +Name[lt]=Kalba +Name[nl]=Taal +Name[nn]=Språk +Name[pa]=ਭਾਸ਼ਾ +Name[pl]=Język +Name[pt]=Língua +Name[pt_BR]=Idioma +Name[ru]=Язык +Name[sk]=Jazyk +Name[sl]=Jezik +Name[sr]=Језик +Name[sr@ijekavian]=Језик +Name[sr@ijekavianlatin]=Jezik +Name[sr@latin]=Jezik +Name[sv]=Språk +Name[tr]=Dil +Name[uk]=Мова +Name[x-test]=xxLanguagexx +Name[zh_CN]=语言 +Name[zh_TW]=語言 + +Categories=Qt;KDE;X-KDE-settings-translations; diff --git a/kcms/translations/package/contents/ui/main.qml b/kcms/translations/package/contents/ui/main.qml new file mode 100644 index 000000000..682e0e616 --- /dev/null +++ b/kcms/translations/package/contents/ui/main.qml @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2015 Marco Martin + * Copyright (C) 2018 Eike Hein + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +import QtQuick 2.1 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 2.3 as QtControls +import org.kde.kirigami 2.5 as Kirigami +import org.kde.plasma.core 2.1 as PlasmaCore +import org.kde.kcm 1.2 + +ScrollViewKCM { + id: root + + ConfigModule.quickHelp: i18n("Language") + ConfigModule.buttons: ConfigModule.Help | ConfigModule.Defaults | ConfigModule.Apply + + Component { + id: addLanguageItemComponent + + Kirigami.BasicListItem { + id: languageItem + + property string languageCode: model.LanguageCode + + reserveSpaceForIcon: false + + label: model.display + + checkable: true + onCheckedChanged: { + if (checked) { + addLanguagesSheet.selectedLanguages.push(index); + + // There's no property change notification for pushing to an array + // in a var prop, so we can't bind selectedLanguages.length to + // addLanguagesButton.enabled. + addLanguagesButton.enabled = true; + } else { + addLanguagesSheet.selectedLanguages = addLanguagesSheet.selectedLanguages.filter(function(item) { return item !== index }); + + // There's no property change notification for pushing to an array + // in a var prop, so we can't bind selectedLanguages.length to + // addLanguagesButton.enabled. + if (!addLanguagesSheet.selectedLanguages.length) { + addLanguagesButton.enabled = false; + } + } + } + + data: [Connections { + target: addLanguagesSheet + + onSheetOpenChanged: languageItem.checked = false + }] + } + } + + Kirigami.OverlaySheet { + id: addLanguagesSheet + + parent: root.parent + + topPadding: 0 + leftPadding: 0 + rightPadding: 0 + bottomPadding: 0 + + header: Kirigami.Heading { text: i18nc("@title:window", "Add Languages") } + + property var selectedLanguages: [] + + onSheetOpenChanged: selectedLanguages = [] + + ListView { + id: availableLanguagesList + + implicitWidth: 18 * Kirigami.Units.gridUnit + + model: kcm.availableTranslationsModel + + delegate: Kirigami.DelegateRecycler { + width: parent.width + + sourceComponent: addLanguageItemComponent + } + } + + footer: RowLayout { + QtControls.Button { + id: addLanguagesButton + + Layout.alignment: Qt.AlignHCenter + + text: i18nc("@action:button", "Add") + + enabled: false + + onClicked: { + var langs = kcm.selectedTranslationsModel.selectedLanguages.slice(); + addLanguagesSheet.selectedLanguages.sort().forEach(function(index) { + langs.push(kcm.availableTranslationsModel.langCodeAt(index)); + }); + + kcm.selectedTranslationsModel.selectedLanguages = langs; + + addLanguagesSheet.sheetOpen = false; + } + } + } + } + + header: ColumnLayout { + id: messagesLayout + + spacing: Kirigami.Units.largeSpacing + + Kirigami.InlineMessage { + Layout.fillWidth: true + + type: Kirigami.MessageType.Information + + text: i18nc("@info", "There are no languages available on this system.") + + visible: !availableLanguagesList.count + } + + Kirigami.InlineMessage { + Layout.fillWidth: true + + type: kcm.everSaved ? Kirigami.MessageType.Positive : Kirigami.MessageType.Information + + text: (kcm.everSaved ? i18nc("@info", "Your changes will take effect the next time you log in.") + : i18nc("@info", "There are currently no preferred languages configured.")) + + visible: !languagesList.count || kcm.everSaved + } + + Kirigami.InlineMessage { + Layout.fillWidth: true + + type: Kirigami.MessageType.Error + + text: i18ncp("@info %2 is the language code", + "The translation files for the language with the code '%2' could not be found. The language will be removed from your configuration. If you want to add it back, please install the localization files for it and add the language again.", + "The translation files for the languages with the codes '%2' could not be found. These languages will be removed from your configuration. If you want to add them back, please install the localization files for it and the languages again.", + kcm.selectedTranslationsModel.missingLanguages.length, + kcm.selectedTranslationsModel.missingLanguages.join("', '")) + + visible: kcm.selectedTranslationsModel.missingLanguages.length + } + + QtControls.Label { + Layout.fillWidth: true + + visible: languagesList.count + + text: i18n("The language at the top of this list is the one you want to see and use most often.") + } + } + + Component { + id: languagesListItemComponent + + Kirigami.SwipeListItem { + id: listItem + + contentItem: RowLayout { + Kirigami.ListItemDragHandle { + listItem: listItem + listView: languagesList + onMoveRequested: kcm.selectedTranslationsModel.move(oldIndex, newIndex) + } + + Kirigami.Icon { + visible: model.IsMissing + + Layout.alignment: Qt.AlignVCenter + + width: Kirigami.Units.iconSizes.smallMedium + height: width + + source: "error" + color: Kirigami.Theme.negativeTextColor + } + + QtControls.Label { + Layout.fillWidth: true + + Layout.alignment: Qt.AlignVCenter + + text: (index == 0) ? i18nc("@item:inlistbox 1 = Language name", "%1 (Default)", model.display) : model.display + + color: (model.IsMissing ? Kirigami.Theme.negativeTextColor + : (listItem.checked || (listItem.pressed && !listItem.checked && !listItem.sectionDelegate) + ? listItem.activeTextColor : listItem.textColor)) + } + } + + actions: [ + Kirigami.Action { + enabled: !model.IsMissing && index > 0 + iconName: "go-top" + tooltip: i18nc("@info:tooltip", "Promote to default") + onTriggered: kcm.selectedTranslationsModel.move(index, 0) + }, + Kirigami.Action { + property bool removing: false + enabled: removing || !model.IsMissing && languagesList.count > 1 + iconName: "list-remove" + tooltip: i18nc("@info:tooltip", "Remove") + onTriggered: { + removing = true; // Don't crash by re-evaluating `enabled` during destruction. + kcm.selectedTranslationsModel.remove(model.LanguageCode); + } + }] + } + } + + view: ListView { + id: languagesList + + model: kcm.selectedTranslationsModel + + delegate: Kirigami.DelegateRecycler { + width: languagesList.width + + sourceComponent: languagesListItemComponent + } + } + + footer: RowLayout { + id: footerLayout + + QtControls.Button { + Layout.alignment: Qt.AlignRight + + enabled: availableLanguagesList.count + + text: i18nc("@action:button", "Add languages...") + + onClicked: addLanguagesSheet.sheetOpen = !addLanguagesSheet.sheetOpen + + checkable: true + checked: addLanguagesSheet.sheetOpen + } + } +} + diff --git a/kcms/translations/package/metadata.desktop b/kcms/translations/package/metadata.desktop new file mode 100644 index 000000000..6e28ec4d5 --- /dev/null +++ b/kcms/translations/package/metadata.desktop @@ -0,0 +1,50 @@ +[Desktop Entry] +Name=Language +Name[ca]=Idioma +Name[ca@valencia]=Idioma +Name[cs]=Jazyk +Name[da]=Sprog +Name[de]=Sprache +Name[el]=Γλώσσα +Name[en_GB]=Language +Name[es]=Idioma +Name[et]=Keel +Name[eu]=Hizkuntza +Name[fi]=Kieli +Name[fr]=Langage +Name[gl]=Idioma +Name[he]=שפה +Name[hu]=Nyelv +Name[id]=Bahasa +Name[it]=Lingua +Name[ja]=言語 +Name[ko]=언어 +Name[lt]=Kalba +Name[nl]=Taal +Name[nn]=Språk +Name[pa]=ਭਾਸ਼ਾ +Name[pl]=Język +Name[pt]=Língua +Name[pt_BR]=Idioma +Name[ru]=Язык +Name[sk]=Jazyk +Name[sl]=Jezik +Name[sr]=Језик +Name[sr@ijekavian]=Језик +Name[sr@ijekavianlatin]=Jezik +Name[sr@latin]=Jezik +Name[sv]=Språk +Name[tr]=Dil +Name[uk]=Мова +Name[x-test]=xxLanguagexx +Name[zh_CN]=语言 +Name[zh_TW]=語言 + +Icon=preferences-desktop-locale +Type=Service +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-Name=kcm_translations +X-KDE-ServiceTypes=Plasma/Generic +X-Plasma-API=declarativeappletscript + +X-Plasma-MainScript=ui/main.qml diff --git a/kcms/translations/translations.cpp b/kcms/translations/translations.cpp new file mode 100644 index 000000000..4d719c351 --- /dev/null +++ b/kcms/translations/translations.cpp @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2014 John Layt + * Copyright (C) 2018 Eike Hein + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "translations.h" +#include "translationsmodel.h" + +#include "../formats/writeexports.h" + +#include +#include +#include +#include + +K_PLUGIN_FACTORY_WITH_JSON(TranslationsFactory, "kcm_translations.json", registerPlugin();) + +Translations::Translations(QObject *parent, const QVariantList &args) + : KQuickAddons::ConfigModule(parent, args) + , m_translationsModel(new TranslationsModel(this)) + , m_selectedTranslationsModel(new SelectedTranslationsModel(this)) + , m_availableTranslationsModel(new AvailableTranslationsModel(this)) + , m_everSaved(false) +{ + KAboutData *about = new KAboutData(QStringLiteral("kcm_translations"), + i18n("Configure Plasma translations"), + QStringLiteral("2.0"), QString(), KAboutLicense::LGPL); + setAboutData(about); + + setButtons(Apply | Default); + + m_config = KConfigGroup(KSharedConfig::openConfig(configFile), "Translations"); + + connect(m_selectedTranslationsModel, &SelectedTranslationsModel::selectedLanguagesChanged, + this, &Translations::selectedLanguagesChanged); + connect(m_selectedTranslationsModel, &SelectedTranslationsModel::missingLanguagesChanged, + this, &Translations::missingLanguagesChanged); + + connect(m_selectedTranslationsModel, &SelectedTranslationsModel::selectedLanguagesChanged, + m_availableTranslationsModel, &AvailableTranslationsModel::setSelectedLanguages); +} + +Translations::~Translations() +{ +} + +QAbstractItemModel* Translations::translationsModel() const +{ + return m_translationsModel; +} + +QAbstractItemModel* Translations::selectedTranslationsModel() const +{ + return m_selectedTranslationsModel; +} + +QAbstractItemModel* Translations::availableTranslationsModel() const +{ + return m_availableTranslationsModel; +} + +bool Translations::everSaved() const +{ + return m_everSaved; +} + +void Translations::load() +{ + m_configuredLanguages = m_config.readEntry(lcLanguage, + QString()).split(QLatin1Char(':'), QString::SkipEmptyParts); + + m_availableTranslationsModel->setSelectedLanguages(m_configuredLanguages); + m_selectedTranslationsModel->setSelectedLanguages(m_configuredLanguages); +} + +void Translations::save() +{ + m_everSaved = true; + emit everSavedChanged(); + + m_configuredLanguages = m_selectedTranslationsModel->selectedLanguages(); + + const auto missingLanguages = m_selectedTranslationsModel->missingLanguages(); + for (const QString& lang : missingLanguages) { + m_configuredLanguages.removeOne(lang); + } + + m_config.writeEntry(lcLanguage, m_configuredLanguages.join(QStringLiteral(":")), KConfig::Persistent); + m_config.sync(); + + writeExports(); + + m_selectedTranslationsModel->setSelectedLanguages(m_configuredLanguages); +} + +void Translations::defaults() +{ + KConfigGroup formatsConfig = KConfigGroup(KSharedConfig::openConfig(configFile), "Formats"); + + QString lang = formatsConfig.readEntry("LANG", QString()); + + if (lang.isEmpty() + || !KLocalizedString::availableDomainTranslations("systemsettings").contains(lang)) { + lang = QLocale::system().name(); + } + + if (!KLocalizedString::availableDomainTranslations("systemsettings").contains(lang)) { + lang = QStringLiteral("en_US"); + } + + QStringList languages; + languages << lang; + + m_selectedTranslationsModel->setSelectedLanguages(languages); +} + +void Translations::selectedLanguagesChanged() +{ + setNeedsSave(m_configuredLanguages != m_selectedTranslationsModel->selectedLanguages()); +} + +void Translations::missingLanguagesChanged() +{ + if (!m_selectedTranslationsModel->missingLanguages().isEmpty()) { + setNeedsSave(true); + } +} + +#include "translations.moc" diff --git a/kcms/translations/translations.h b/kcms/translations/translations.h new file mode 100644 index 000000000..57f7203a6 --- /dev/null +++ b/kcms/translations/translations.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2014 John Layt + * Copyright (C) 2018 Eike Hein + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef TRANSLATIONS_H +#define TRANSLATIONS_H + +#include + +#include + +class AvailableTranslationsModel; +class SelectedTranslationsModel; +class TranslationsModel; + +class QAbstractListModel; + +class Translations : public KQuickAddons::ConfigModule +{ + Q_OBJECT + + Q_PROPERTY(QAbstractItemModel* translationsModel READ translationsModel CONSTANT) + Q_PROPERTY(QAbstractItemModel* selectedTranslationsModel READ selectedTranslationsModel CONSTANT) + Q_PROPERTY(QAbstractItemModel* availableTranslationsModel READ availableTranslationsModel CONSTANT) + Q_PROPERTY(bool everSaved READ everSaved NOTIFY everSavedChanged) + + public: + explicit Translations(QObject* parent = nullptr, const QVariantList &list = QVariantList()); + ~Translations() override; + + QAbstractItemModel* translationsModel() const; + QAbstractItemModel* selectedTranslationsModel() const; + QAbstractItemModel* availableTranslationsModel() const; + + bool everSaved() const; + + public Q_SLOTS: + void load() override; + void save() override; + void defaults() override; + + Q_SIGNALS: + void everSavedChanged() const; + + private Q_SLOTS: + void selectedLanguagesChanged(); + void missingLanguagesChanged(); + + private: + TranslationsModel *m_translationsModel; + SelectedTranslationsModel *m_selectedTranslationsModel; + AvailableTranslationsModel *m_availableTranslationsModel; + + KConfigGroup m_config; + QStringList m_configuredLanguages; + + bool m_everSaved; +}; + +#endif diff --git a/kcms/translations/translationsmodel.cpp b/kcms/translations/translationsmodel.cpp new file mode 100644 index 000000000..fbb5407d1 --- /dev/null +++ b/kcms/translations/translationsmodel.cpp @@ -0,0 +1,288 @@ +/* + * Copyright (C) 2014 John Layt + * Copyright (C) 2018 Eike Hein + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "translationsmodel.h" + +#include + +#include +#include +#include +#include + +QStringList TranslationsModel::m_languages = QStringList(); +QSet TranslationsModel::m_installedLanguages = QSet(); + +TranslationsModel::TranslationsModel(QObject *parent) + : QAbstractListModel(parent) +{ + if (m_installedLanguages.isEmpty()) { + m_installedLanguages = KLocalizedString::availableDomainTranslations("systemsettings"); + m_languages = m_installedLanguages.toList(); + } +} + +TranslationsModel::~TranslationsModel() +{ +} + +QHash TranslationsModel::roleNames() const +{ + QHash roles = QAbstractItemModel::roleNames(); + + QMetaEnum e = metaObject()->enumerator(metaObject()->indexOfEnumerator("AdditionalRoles")); + + for (int i = 0; i < e.keyCount(); ++i) { + roles.insert(e.value(i), e.key(i)); + } + + return roles; +} + +QVariant TranslationsModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() < 0 || index.row() >= m_languages.count()) { + return QVariant(); + } + + if (role == Qt::DisplayRole) { + return languageCodeToName(m_languages.at(index.row())); + } else if (role == LanguageCode) { + return m_languages.at(index.row()); + } else if (role == IsMissing) { + return false; + } + + return QVariant(); +} + +int TranslationsModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) { + return 0; + } + + return m_languages.count(); +} + +QString TranslationsModel::languageCodeToName(const QString& languageCode) const +{ + const QLocale locale(languageCode); + const QString &languageName = locale.nativeLanguageName(); + + if (languageName.isEmpty()) { + return languageCode; + } + + if (languageCode.contains(QStringLiteral("@"))) { + return i18nc("%1 is language name, %2 is language code name", "%1 (%2)", languageName, languageCode); + } + + if (locale.name() != languageCode && m_installedLanguages.contains(locale.name())) { + // KDE languageCode got translated by QLocale to a locale code we also have on + // the list. Currently this only happens with pt that gets translated to pt_BR. + if (languageCode == QLatin1String("pt")) { + return QLocale(QStringLiteral("pt_PT")).nativeLanguageName(); + } + + qWarning() << "Language code morphed into another existing language code, please report!" << languageCode << locale.name(); + return i18nc("%1 is language name, %2 is language code name", "%1 (%2)", languageName, languageCode); + } + + return languageName; +} + +SelectedTranslationsModel::SelectedTranslationsModel(QObject *parent) + : TranslationsModel(parent) +{ +} + +SelectedTranslationsModel::~SelectedTranslationsModel() +{ +} + +QVariant SelectedTranslationsModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() < 0 || index.row() >= m_selectedLanguages.count()) { + return QVariant(); + } + + if (role == Qt::DisplayRole) { + return languageCodeToName(m_selectedLanguages.at(index.row())); + } else if (role == LanguageCode) { + return m_selectedLanguages.at(index.row()); + } else if (role == IsMissing) { + return m_missingLanguages.contains(m_selectedLanguages.at(index.row())); + } + + return QVariant(); +} + +int SelectedTranslationsModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) { + return 0; + } + + return m_selectedLanguages.count(); +} + +QStringList SelectedTranslationsModel::selectedLanguages() const +{ + return m_selectedLanguages; +} + +void SelectedTranslationsModel::setSelectedLanguages(const QStringList &languages) +{ + if (m_selectedLanguages != languages) { + QStringList missingLanguages; + + for (const QString& lang : languages) { + if (!m_installedLanguages.contains(lang)) { + missingLanguages << lang; + } + } + + missingLanguages.sort(); + + if (missingLanguages != m_missingLanguages) { + m_missingLanguages = missingLanguages; + emit missingLanguagesChanged(); + } + + beginResetModel(); + + m_selectedLanguages = languages; + + endResetModel(); + + emit selectedLanguagesChanged(m_selectedLanguages); + } +} + +QStringList SelectedTranslationsModel::missingLanguages() const +{ + return m_missingLanguages; +} + +void SelectedTranslationsModel::move(int from, int to) +{ + if (from >= m_selectedLanguages.count() || to >= m_selectedLanguages.count()) { + return; + } + + if (from == to) { + return; + } + + const int modelTo = to + (to > from ? 1 : 0); + + const bool ok = beginMoveRows(QModelIndex(), from, from, QModelIndex(), modelTo); + + if (ok) { + m_selectedLanguages.move(from, to); + + endMoveRows(); + + emit selectedLanguagesChanged(m_selectedLanguages); + } +} + +void SelectedTranslationsModel::remove(const QString &languageCode) +{ + if (languageCode.isEmpty()) { + return; + } + + int index = m_selectedLanguages.indexOf(languageCode); + + if (index < 0 || m_selectedLanguages.count() < 2) { + return; + } + + beginRemoveRows(QModelIndex(), index, index); + + m_selectedLanguages.removeAt(index); + + endRemoveRows(); + + emit selectedLanguagesChanged(m_selectedLanguages); +} + +AvailableTranslationsModel::AvailableTranslationsModel(QObject *parent) + : TranslationsModel(parent) +{ +} + +AvailableTranslationsModel::~AvailableTranslationsModel() +{ +} + +QVariant AvailableTranslationsModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() < 0 || index.row() >= m_availableLanguages.count()) { + return QVariant(); + } + + if (role == Qt::DisplayRole) { + return languageCodeToName(m_availableLanguages.at(index.row())); + } else if (role == LanguageCode) { + return m_availableLanguages.at(index.row()); + } else if (role == IsMissing) { + return false; + } + + return QVariant(); +} + +int AvailableTranslationsModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) { + return 0; + } + + return m_availableLanguages.count(); +} + +QString AvailableTranslationsModel::langCodeAt(int row) +{ + if (row < 0 || row >= m_availableLanguages.count()) { + return QString(); + } + + return m_availableLanguages.at(row); +} + +void AvailableTranslationsModel::setSelectedLanguages(const QStringList &languages) +{ + beginResetModel(); + + m_availableLanguages = (m_installedLanguages - QSet::fromList(languages)).toList(); + + QCollator c; + c.setCaseSensitivity(Qt::CaseInsensitive); + + std::sort(m_availableLanguages.begin(), m_availableLanguages.end(), + [this, &c](const QString &a, const QString &b) { + return c.compare(languageCodeToName(a), languageCodeToName(b)) < 0; + }); + + endResetModel(); +} diff --git a/kcms/translations/translationsmodel.h b/kcms/translations/translationsmodel.h new file mode 100644 index 000000000..867a12c5c --- /dev/null +++ b/kcms/translations/translationsmodel.h @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2014 John Layt + * Copyright (C) 2018 Eike Hein + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef TRANSLATIONSMODEL_H +#define TRANSLATIONSMODEL_H + +#include + +#include + +#include + +class TranslationsModel : public QAbstractListModel +{ + Q_OBJECT + + public: + enum AdditionalRoles { + LanguageCode = Qt::UserRole + 1, + IsMissing + }; + Q_ENUM(AdditionalRoles) + + explicit TranslationsModel(QObject *parent = nullptr); + ~TranslationsModel() override; + + QHash roleNames() const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + protected: + QString languageCodeToName(const QString& languageCode) const; + + static QStringList m_languages; + + static QSet m_installedLanguages; +}; + +class SelectedTranslationsModel : public TranslationsModel +{ + Q_OBJECT + + Q_PROPERTY(QStringList selectedLanguages READ selectedLanguages WRITE setSelectedLanguages NOTIFY selectedLanguagesChanged) + Q_PROPERTY(QStringList missingLanguages READ missingLanguages NOTIFY missingLanguagesChanged) + + public: + explicit SelectedTranslationsModel(QObject *parent = nullptr); + ~SelectedTranslationsModel() override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + QStringList selectedLanguages() const; + void setSelectedLanguages(const QStringList &languages); + + QStringList missingLanguages() const; + + Q_INVOKABLE void move(int from, int to); + Q_INVOKABLE void remove(const QString &languageCode); + + Q_SIGNALS: + void selectedLanguagesChanged(const QStringList &languages) const; + void missingLanguagesChanged() const; + + private: + QStringList m_selectedLanguages; + QStringList m_missingLanguages; +}; + +class AvailableTranslationsModel : public TranslationsModel +{ + Q_OBJECT + + public: + explicit AvailableTranslationsModel(QObject *parent = nullptr); + ~AvailableTranslationsModel() override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + Q_INVOKABLE QString langCodeAt(int row); + + public Q_SLOTS: + void setSelectedLanguages(const QStringList &languages); + + private: + QStringList m_availableLanguages; +}; + +#endif