Commit 6055cfb9 authored by Eike Hein's avatar Eike Hein
Browse files

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 7b42eae7
......@@ -10,10 +10,6 @@ set_package_properties(Freetype PROPERTIES DESCRIPTION "A font rendering engine"
set(libkxftconfig_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/fonts/kxftconfig.cpp )
if(X11_Xkb_FOUND AND XCB_XKB_FOUND)
add_subdirectory( keyboard )
endif()
if (EVDEV_FOUND AND XORGLIBINPUT_FOUND AND X11_Xinput_FOUND)
add_subdirectory( input )
endif()
......
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
/*
* Copyright 2010, 2014 John Layt <john@layt.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include "kcmtranslations.h"
#include "../formats/writeexports.h"
#include <KAboutData>
#include <KActionSelector>
#include <KLocalizedString>
#include <KMessageBox>
#include <KPluginFactory>
#include <KMessageWidget>
#include <QListWidget>
#include <QListWidgetItem>
#include <QStandardPaths>
#include "ui_kcmtranslationswidget.h"
K_PLUGIN_FACTORY(KCMTranslationsFactory, registerPlugin<KCMTranslations>();)
KCMTranslations::KCMTranslations(QWidget *parent, const QVariantList &args)
: KCModule(parent, args),
m_ui(new Ui::KCMTranslationsWidget),
m_messageWidget(0)
{
KAboutData *about = new KAboutData(QStringLiteral("KCMTranslations"),
i18n("Translations"),
QString(),
i18n("Configure Plasma translations"),
KAboutLicense::GPL);
about->addAuthor(i18n("John Layt"), i18n("Maintainer"), QStringLiteral("jlayt@kde.net"));
setAboutData(about);
m_ui->setupUi(this);
// Get the current config
m_config = KConfigGroup(KSharedConfig::openConfig(configFile), "Translations");
// Set up UI to respond to user changing the translation selection
connect(m_ui->m_selectTranslations, &KActionSelector::added, this, &KCMTranslations::changedTranslationsSelected);
connect(m_ui->m_selectTranslations, &KActionSelector::removed, this, &KCMTranslations::changedTranslationsAvailable);
connect(m_ui->m_selectTranslations, &KActionSelector::movedUp, this, &KCMTranslations::changedTranslationsSelected);
connect(m_ui->m_selectTranslations, &KActionSelector::movedDown, this, &KCMTranslations::changedTranslationsSelected);
// If user clicks the Install button, trigger distro specific install routine
connect(m_ui->m_buttonTranslationsInstall, &QPushButton::clicked, this, &KCMTranslations::installTranslations);
// Hide the Install button, this will be activated by those distros that support this feature.
m_ui->m_buttonTranslationsInstall->setHidden(true);
}
KCMTranslations::~KCMTranslations()
{
delete m_ui;
}
// Reimp
// Load == User has clicked on Reset to restore saved settings
// Also gets called automatically called after constructor
void KCMTranslations::load()
{
// Get the currently installed translations for Plasma
// TODO May want to later add all installed .po files on system?
m_installedTranslations.clear();
m_installedTranslations = KLocalizedString::availableDomainTranslations("systemsettings").toList();
// Load the current user translations
loadTranslations();
// Check if any of the current user translations are no longer installed
// If any missing remove them and save the settings, then tell the user
QStringList missingLanguages;
QStringList availableLanguages;
if (!m_config.isEntryImmutable(lcLanguage)) {
foreach (const QString &languageCode, m_kcmTranslations) {
if (m_installedTranslations.contains(languageCode)) {
availableLanguages.append(languageCode);
} else {
missingLanguages.append(languageCode);
}
}
m_config.writeEntry(lcLanguage, availableLanguages.join(QStringLiteral(":")));
m_config.sync();
m_config.config()->reparseConfiguration();
loadTranslations();
}
// Then update all the widgets to use the new settings
initWidgets();
if (missingLanguages.count()) {
const QString txt = i18ncp("%1 is the language code",
"The translation files for the language with the code '%2' "
"could not be found. The "
"language has been 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 have been removed from your configuration. "
"If you want to add them back, please install the "
"localization files for it and the languages again.",
missingLanguages.count(),
missingLanguages.join("', '"));
m_messageWidget = new KMessageWidget(this);
m_messageWidget->setMessageType(KMessageWidget::Information);
m_messageWidget->setWordWrap(true);
m_messageWidget->setText(txt);
m_ui->verticalLayout->insertWidget(0, m_messageWidget);
}
}
// Reimp
// Save the new LANGUAGE setting
void KCMTranslations::save()
{
m_config.writeEntry(lcLanguage, m_kcmTranslations.join(QStringLiteral(":")), KConfig::Persistent | KConfig::Global);
m_config.sync();
KMessageBox::information(this,
i18n("Your changes will take effect the next time you log in."),
i18n("Applying Language Settings"),
QStringLiteral("LanguageChangesApplyOnlyToNewlyStartedPrograms"));
load();
writeExports();
}
// Reimp
// Defaults == User has clicked on Defaults to load default settings
// We interpret this to mean the default LANG setting
void KCMTranslations::defaults()
{
// Get the users LANG setting, or if empty the system LANG
KConfigGroup formatsConfig = KConfigGroup(KSharedConfig::openConfig(configFile), "Formats");
QString lang = formatsConfig.readEntry("LANG", QString());
if (lang.isEmpty() || !m_installedTranslations.contains(lang)) {
lang = QLocale::system().name();
}
if (!m_installedTranslations.contains(lang)) {
lang = QStringLiteral("en_US");
}
// Use the available lang as the default
m_kcmTranslations.clear();
m_kcmTranslations.append(lang);
// The update all the widgets to use the new language
initWidgets();
}
void KCMTranslations::loadTranslations()
{
// Load the user translations
m_configTranslations = m_config.readEntry(lcLanguage, QString());
m_kcmTranslations.clear();
m_kcmTranslations = m_configTranslations.split(':', QString::SkipEmptyParts);
}
QString KCMTranslations::quickHelp() const
{
return i18n("<h1>Translations</h1>\n"
"<p>Here you can set your preferred language for translating the "
"user interface of your applications. You can choose a single "
"language, or a list of languages to be applied in sequence. Only "
"language translations that are installed on your system will "
"be listed as available. If your language is not listed then "
"you will need to install it first.</p>");
}
void KCMTranslations::initWidgets()
{
initTranslations();
initTranslationsInstall();
// Init the KCM "Apply" button
emit changed(m_kcmTranslations.join(QStringLiteral(":")) != m_configTranslations);
}
void KCMTranslations::initTranslations()
{
m_ui->m_selectTranslations->blockSignals(true);
m_ui->m_selectTranslations->setAvailableLabel(i18n("Available Languages:"));
QString availableHelp = i18n("This is the list of installed KDE Plasma language "
"translations not currently being used. <br />To use a language "
"translation move it to the 'Preferred Languages' list in "
"the order of preference. <br />If no suitable languages are "
"listed, then you may need to install more language packages "
"using your usual installation method.");
m_ui->m_selectTranslations->availableListWidget()->setToolTip(availableHelp);
m_ui->m_selectTranslations->availableListWidget()->setWhatsThis(availableHelp);
m_ui->m_selectTranslations->setSelectedLabel(i18n("Preferred Languages:"));
QString selectedHelp = i18n("This is the list of installed KDE Plasma language "
"translations currently being used, listed in order of "
"preference. <br />If a translation is not available for the "
"first language in the list, the next language will be used. <br /> "
"If no other translations are available then US English will "
"be used.");
m_ui->m_selectTranslations->selectedListWidget()->setToolTip(selectedHelp);
m_ui->m_selectTranslations->selectedListWidget()->setWhatsThis(selectedHelp);
// Clear the selector before reloading
m_ui->m_selectTranslations->availableListWidget()->clear();
m_ui->m_selectTranslations->selectedListWidget()->clear();
// Load each user selected language into the selected list
foreach (const QString &languageCode, m_kcmTranslations) {
QListWidgetItem *listItem = new QListWidgetItem(m_ui->m_selectTranslations->selectedListWidget());
// TODO This gives the name in the language itself, not in current language, need new QLocale api for that
QString label = QLocale(languageCode).nativeLanguageName();
if (label.isEmpty()) {
label = languageCode;
}
listItem->setText(label);
listItem->setData(Qt::UserRole, languageCode);
}
// Load all the available languages the user hasn't selected into the available list
foreach (const QString &languageCode, m_installedTranslations) {
if (!m_kcmTranslations.contains(languageCode)) {
QListWidgetItem *listItem = new QListWidgetItem(m_ui->m_selectTranslations->availableListWidget());
// TODO This gives the name in the language itself, not in current language, need new QLocale api for that
const QLocale l(languageCode);
QString label = l.nativeLanguageName();
if (label.isEmpty()) {
label = languageCode;
} else if (languageCode.contains(QStringLiteral("@"))) {
label = i18nc("%1 is language name, %2 is language code name", "%1 (%2)", label, languageCode);
} else if (l.name() != languageCode) {
if (m_installedTranslations.contains(l.name())) {
// KDE languageCode got translated by QLocale to a locale code we also have on the list
// Currently only this happens with pt that gets trasnated to pt_BR
if (languageCode == QLatin1String("pt")) {
label = QLocale(QStringLiteral("pt_PT")).nativeLanguageName();
} else {
qWarning() << "Language code morphed into another existing language code, please report" << languageCode << l.name();
label = i18nc("%1 is language name, %2 is language code name", "%1 (%2)", label, languageCode);
}
}
}
listItem->setText(label);
listItem->setData(Qt::UserRole, languageCode);
}
}
m_ui->m_selectTranslations->availableListWidget()->sortItems();
// Default to selecting the first Selected language,
// otherwise the first Available language,
// otherwise no languages so disable all buttons
if (m_ui->m_selectTranslations->selectedListWidget()->count() > 0) {
m_ui->m_selectTranslations->selectedListWidget()->setCurrentRow(0);
} else if (m_ui->m_selectTranslations->availableListWidget()->count() > 0) {
m_ui->m_selectTranslations->availableListWidget()->setCurrentRow(0);
}
// If the setting is locked down by Kiosk, then don't let the user make any changes
if (m_config.isEntryImmutable(lcLanguage)) {
m_ui->m_selectTranslations->setEnabled(false);
}
m_ui->m_selectTranslations->blockSignals(false);
}
void KCMTranslations::changedTranslationsAvailable(QListWidgetItem *item)
{
Q_UNUSED(item);
m_ui->m_selectTranslations->availableListWidget()->sortItems();
int row = m_ui->m_selectTranslations->availableListWidget()->currentRow();
changedTranslations();
m_ui->m_selectTranslations->availableListWidget()->setCurrentRow(row);
}
void KCMTranslations::changedTranslationsSelected(QListWidgetItem *item)
{
Q_UNUSED(item);
int row = m_ui->m_selectTranslations->selectedListWidget()->currentRow();
changedTranslations();
m_ui->m_selectTranslations->selectedListWidget()->setCurrentRow(row);
}
void KCMTranslations::changedTranslations()
{
// Read the list of all Selected translations from the selector widget
m_kcmTranslations.clear();
for (int i = 0; i < m_ui->m_selectTranslations->selectedListWidget()->count(); ++i) {
m_kcmTranslations.append(m_ui->m_selectTranslations->selectedListWidget()->item(i)->data(Qt::UserRole).toString());
}
initWidgets();
}
void KCMTranslations::initTranslationsInstall()
{
m_ui->m_buttonTranslationsInstall->blockSignals(true);
m_ui->m_buttonTranslationsInstall->setText(i18n("Install more languages"));
QString helpText = i18n("<p>Click here to install more languages</p>");
m_ui->m_buttonTranslationsInstall->setToolTip(helpText);
m_ui->m_buttonTranslationsInstall->setWhatsThis(helpText);
m_ui->m_buttonTranslationsInstall->blockSignals(false);
}
void KCMTranslations::installTranslations()
{
// User has clicked Install Languages button, trigger distro specific install routine
}
#include "kcmtranslations.moc"
<?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
}