Commit 68b2a755 authored by Eike Hein's avatar Eike Hein

Port Language KCM to Qt Quick

Summary:
* Changed the overall design from two lists to one list with a modal
  sheet to add more languages.
* Replaced a modal "You need to relogin for changes" dialog with a
  MessageType.Positive InlineMessage.
* Reworked the way missing languages are handled: The old KCM silently
  rewrote config and showed a warning. The new design shows an
  informative warning and removes the missing languages on the next
  save. Until then they're flagged as missing in the list.
* Manages Apply button state correctly (or rather at all ...).

This depends on D12097.

This implements T7247.

This is currently not final code. It's a WIP upload to give Marco
something to work with to fix various Kirigami and SimpleKCM problems.

Currently known issues:
* Does not save (code is from old KCM, might have been broken there)
* Disabled SwipeListItem actions do not show disabled
* Placement of actions button in SwipeListItem is wonky if the
  contentItem is a RowLayout
* SwipeListItem spews errors about positionAnimation after using an
  action
* SwipeListItem is awkward to use, we need a drag-reorderable list
  delegate
* OverlaySheet spews numerous warnings about not being able to find
  applicationWindow and activeFocusItem
* The sheet is parented to the SimpleKCM's parent since there's no
  app window to be modal too
* The footer inside an OverlaySheet sometimes moves up above the
  content instead of staying down
* Even though SimpleKCM is just a Kirigami.ScrollablePage like
  Kirigami Gallery pages, an InlineMessage that fills the page width
  gets cut off on the left and right, so wonky code to insert margins
  next to them
* List has window bg color as background instead of view background
  color

Reviewers: #kirigami, mart

Subscribers: rkflx, aspotashev, davidedmundson, safaalfulaij, abetts, ngraham, plasma-devel

Tags: #plasma, #kirigami

Differential Revision: https://phabricator.kde.org/D12102
parent 68021da4
include(ECMQMLModules)
ecm_find_qmlmodule(org.kde.plasma.core 2.0)
# KI18N Translation Domain for this library.
add_definitions(-DTRANSLATION_DOMAIN=\"kcmtranslations\")
set(kcm_translations_PART_SRCS kcmtranslations.cpp)
########### next target ###############
ki18n_wrap_ui(kcm_translations_PART_SRCS kcmtranslationswidget.ui)
set(kcm_translations_PART_SRCS translations.cpp translationsmodel.cpp)
add_library(kcm_translations ${kcm_translations_PART_SRCS})
add_library(kcm_translations MODULE ${kcm_translations_PART_SRCS})
target_link_libraries(kcm_translations
Qt5::Widgets
KF5::WidgetsAddons
KF5::KCMUtils
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})
install(FILES translations.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR})
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)
#! /usr/bin/env bash
$EXTRACTRC *.ui >> rc.cpp
$XGETTEXT -ktranslate:1,1t -ktranslate:1c,2,2t *.cpp -o $podir/kcmtranslations.pot
rm -f rc.cpp
$XGETTEXT `find . -name \*.cpp -o -name \*.qml` -o $podir/kcmtranslations.pot
This diff is collapsed.
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>KCMTranslationsWidget</class>
<widget class="QWidget" name="KCMTranslationsWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>687</width>
<height>333</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="KActionSelector" name="m_selectTranslations">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="availableLabel">
<string>Available &amp;Translations:</string>
</property>
<property name="selectedLabel">
<string>Preferred Trans&amp;lations:</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="m_buttonTranslationsInstall">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Install more translations</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>KActionSelector</class>
<extends>QWidget</extends>
<header>kactionselector.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
/*
* Copyright (C) 2015 Marco Martin <mart@kde.org>
* Copyright (C) 2018 Eike Hein <hein@kde.org>
*
* 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.4 as Kirigami
import org.kde.plasma.core 2.1 as PlasmaCore
import org.kde.kcm 1.2
ScrollViewKCM {
id: root
ConfigModule.quickHelp: i18n("Language")
PlasmaCore.SortFilterModel {
id: availableLanguagesModel
sourceModel: kcm.translationsModel
filterRole: "IsSelected"
filterCallback: function(source_row, value) { return !value; }
sortRole: "display"
}
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 {
implicitWidth: 18 * Kirigami.Units.gridUnit
model: availableLanguagesModel
delegate: Kirigami.BasicListItem {
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;
}
}
}
}
}
footer: RowLayout {
QtControls.Button {
id: addLanguagesButton
Layout.alignment: Qt.AlignHCenter
text: i18nc("@action:button", "Add")
enabled: false
onClicked: {
var langs = [];
addLanguagesSheet.selectedLanguages.sort().forEach(function(index) {
langs.push(availableLanguagesModel.get(index).LanguageCode);
});
kcm.translationsModel.selectedLanguages = kcm.translationsModel.selectedLanguages.concat(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: !availableLanguagesModel.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.translationsModel.missingLanguages.length,
kcm.translationsModel.missingLanguages.join("', '"))
visible: kcm.translationsModel.missingLanguages.length
}
QtControls.Label {
Layout.fillWidth: true
visible: languagesList.count
text: i18n("The language at the top of this list is is the one you want to see and use most often.")
}
}
view: ListView {
id: languagesList
model: PlasmaCore.SortFilterModel {
sourceModel: kcm.translationsModel
filterRole: "IsSelected"
filterCallback: function(source_row, value) { return value; }
sortRole: "SelectedPriority"
}
delegate: Kirigami.SwipeListItem {
id: listItem
width: ListView.view.width
contentItem: RowLayout {
width: implicitWidth
height: Math.max(implicitHeight, Kirigami.Units.iconSizes.smallMedium)
anchors.verticalCenter: parent.verticalCenter
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.translationsModel.moveSelectedLanguage(index, 0)
},
Kirigami.Action {
enabled: !model.IsMissing && index > 0
iconName: "go-up"
tooltip: i18nc("@info:tooltip", "Move up")
onTriggered: kcm.translationsModel.moveSelectedLanguage(index, index - 1)
},
Kirigami.Action {
enabled: !model.IsMissing && index < (languagesList.count - 1)
iconName: "go-down"
tooltip: i18nc("@info:tooltip", "Move down")
onTriggered: kcm.translationsModel.moveSelectedLanguage(index, index + 1)
},
Kirigami.Action {
enabled: !model.IsMissing
iconName: "list-remove"
tooltip: i18nc("@info:tooltip", "Remove")
onTriggered: kcm.translationsModel.removeSelectedLanguage(model.LanguageCode)
}]
}
}
footer: RowLayout {
id: footerLayout
QtControls.Button {
Layout.alignment: Qt.AlignRight
enabled: availableLanguagesModel.count
text: i18nc("@action:button", "Add languages...")
onClicked: addLanguagesSheet.sheetOpen = !addLanguagesSheet.sheetOpen
checkable: true
checked: addLanguagesSheet.sheetOpen
}
}
}
[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
/*
* Copyright (C) 2014 John Layt <john@layt.net>
* Copyright (C) 2018 Eike Hein <hein@kde.org>
*
* 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 <KAboutData>
#include <KLocalizedString>
#include <KPluginFactory>
#include <KSharedConfig>
K_PLUGIN_FACTORY_WITH_JSON(TranslationsFactory, "kcm_translations.json", registerPlugin<Translations>();)
Translations::Translations(QObject *parent, const QVariantList &args)
: KQuickAddons::ConfigModule(parent, args)
, m_translationsModel(new TranslationsModel(this))
, m_everSaved(false)
{
KAboutData *about = new KAboutData("kcm_translations",
i18n("Configure Plasma translations"),
"2.0", QString(), KAboutLicense::LGPL);
setAboutData(about);
setButtons(Apply | Default);
m_config = KConfigGroup(KSharedConfig::openConfig(configFile), "Translations");
connect(m_translationsModel, &TranslationsModel::selectedLanguagesChanged,
this, &Translations::selectedLanguagesChanged);
connect(m_translationsModel, &TranslationsModel::missingLanguagesChanged,
this, &Translations::missingLanguagesChanged);
}
Translations::~Translations()
{
}
QAbstractItemModel* Translations::translationsModel() const
{
return m_translationsModel;
}
bool Translations::everSaved() const
{
return m_everSaved;
}
void Translations::load()
{
m_configuredLanguages = m_config.readEntry(lcLanguage,
QString()).split(':', QString::SkipEmptyParts);
m_translationsModel->setSelectedLanguages(m_configuredLanguages);
}
void Translations::save()
{
m_everSaved = true;
emit everSavedChanged();
m_configuredLanguages = m_translationsModel->selectedLanguages();
for (const QString& lang : m_translationsModel->missingLanguages()) {
m_configuredLanguages.removeOne(lang);
}
m_config.writeEntry(lcLanguage, m_configuredLanguages.join(QStringLiteral(":")), KConfig::Persistent);
m_config.sync();
writeExports();
m_translationsModel->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_translationsModel->setSelectedLanguages(languages);
}
void Translations::selectedLanguagesChanged()
{
setNeedsSave(m_configuredLanguages != m_translationsModel->selectedLanguages());
}
void Translations::missingLanguagesChanged()
{
if (m_translationsModel->missingLanguages().count()) {
setNeedsSave(true);
}
}
#include "translations.moc"
/* This file is part of the KDE's Plasma workspace
* Copyright 2014 John Layt <john@layt.net>
/*
* Copyright (C) 2014 John Layt <john@layt.net>
* Copyright (C) 2018 Eike Hein <hein@kde.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
......@@ -17,68 +18,51 @@
* Boston, MA 02110-1301, USA.
*/
#ifndef KCMTRANSLATIONS_H
#define KCMTRANSLATIONS_H
#ifndef TRANSLATIONS_H
#define TRANSLATIONS_H
#include <KQuickAddons/ConfigModule>
#include <KCModule>
#include <KSharedConfig>
#include <KConfigGroup>
class QListWidgetItem;
class KMessageWidget;
class TranslationsModel;
namespace Ui
{
class KCMTranslationsWidget;
}
class QAbstractListModel;
/**
* @short A KCM to configure KDE Gui Translations
*
* This module is for changing the User's Gui Translations settings.
*/
class KCMTranslations : public KCModule
class Translations : public KQuickAddons::ConfigModule
{
Q_OBJECT
public:
KCMTranslations(QWidget *parent, const QVariantList &);
~KCMTranslations() override;
void load() override;
void save() override;
void defaults() override;
QString quickHelp() const override;
Q_PROPERTY(QAbstractItemModel* translationsModel READ translationsModel CONSTANT)
Q_PROPERTY(bool everSaved READ everSaved NOTIFY everSavedChanged)
private Q_SLOTS:
public:
explicit Translations(QObject* parent = 0, const QVariantList &list = QVariantList());
~Translations() override;
void changedTranslationsAvailable(QListWidgetItem *item);
void changedTranslationsSelected(QListWidgetItem *item);
QAbstractItemModel* translationsModel() const;
void installTranslations();
bool everSaved() const;
private:
public Q_SLOTS:
void load();
void save();
void defaults();
void loadTranslations();
void changedTranslations();
Q_SIGNALS:
void everSavedChanged() const;
void initWidgets();
void initTranslations();
void initTranslationsInstall();
private Q_SLOTS:
void selectedLanguagesChanged();
void missingLanguagesChanged();
// The list of translations currently set in the KCM
QStringList m_kcmTranslations;
// The currently saved list of user translations, used to check if value changed
QString m_configTranslations;
// The currently installed translations, used to check if users translations are valid
QStringList m_installedTranslations;
private:
TranslationsModel *m_translationsModel;
KConfigGroup m_config;
KConfigGroup m_config;
QStringList m_configuredLanguages;
Ui::KCMTranslationsWidget *m_ui;
KMessageWidget *m_messageWidget;
bool m_everSaved;
};
#endif //KCMTRANSLATIONS_H
#endif
/*
* Copyright (C) 2014 John Layt <john@layt.net>
* Copyright (C) 2018 Eike Hein <hein@kde.org>
*
* 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.