You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
462 lines
14 KiB
462 lines
14 KiB
/* |
|
* Copyright (C) 2014 John Layt <john@layt.net> |
|
* Copyright (C) 2018 Eike Hein <hein@kde.org> |
|
* Copyright (C) 2021 Harald Sitter <sitter@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 "translationsmodel.h" |
|
|
|
#include <KLocalizedString> |
|
#include <KOSRelease> |
|
|
|
#include <QCollator> |
|
#include <QDebug> |
|
#include <QLocale> |
|
#include <QMetaEnum> |
|
#include <QMetaObject> |
|
#include <QProcess> |
|
|
|
#include "config-workspace.h" |
|
#include "debug.h" |
|
|
|
#ifdef HAVE_PACKAGEKIT |
|
#include <PackageKit/Daemon> |
|
class LanguageCompleter : public QObject |
|
{ |
|
Q_OBJECT |
|
public: |
|
explicit LanguageCompleter(const QStringList &packages, QObject *parent = nullptr) |
|
: QObject(parent) |
|
, m_packages(packages) |
|
{ |
|
} |
|
|
|
void start() |
|
{ |
|
auto transaction = PackageKit::Daemon::resolve(m_packages, PackageKit::Transaction::FilterNotInstalled | PackageKit::Transaction::FilterArch); |
|
connect(transaction, |
|
&PackageKit::Transaction::package, |
|
this, |
|
[this](PackageKit::Transaction::Info info, const QString &packageID, const QString &summary) { |
|
Q_UNUSED(info); |
|
Q_UNUSED(summary); |
|
m_packageIDs << packageID; |
|
}); |
|
connect(transaction, &PackageKit::Transaction::errorCode, this, [](PackageKit::Transaction::Error error, const QString &details) { |
|
qCDebug(KCM_TRANSLATIONS) << "resolve error" << error << details; |
|
}); |
|
connect(transaction, &PackageKit::Transaction::finished, this, [this](PackageKit::Transaction::Exit status, uint code) { |
|
qCDebug(KCM_TRANSLATIONS) << "resolve finished" << status << code << m_packageIDs; |
|
if (m_packageIDs.size() != m_packages.size()) { |
|
qCWarning(KCM_TRANSLATIONS) << "Not all missing packages managed to resolve!" << m_packages << m_packageIDs; |
|
} |
|
install(); |
|
}); |
|
} |
|
|
|
Q_SIGNALS: |
|
void complete(); |
|
|
|
private: |
|
void install() |
|
{ |
|
auto transaction = PackageKit::Daemon::installPackages(m_packageIDs); |
|
connect(transaction, &PackageKit::Transaction::errorCode, this, [](PackageKit::Transaction::Error error, const QString &details) { |
|
qCDebug(KCM_TRANSLATIONS) << "install error:" << error << details; |
|
}); |
|
connect(transaction, &PackageKit::Transaction::finished, this, [this](PackageKit::Transaction::Exit status, uint code) { |
|
qCDebug(KCM_TRANSLATIONS) << "install finished:" << status << code; |
|
Q_EMIT complete(); |
|
}); |
|
} |
|
|
|
const QStringList m_packages; |
|
QStringList m_packageIDs; |
|
}; |
|
#endif |
|
|
|
class CompletionCheck : public QObject |
|
{ |
|
Q_OBJECT |
|
public: |
|
enum class Result { Error, Incomplete, Complete }; |
|
|
|
template<typename... Args> |
|
static CompletionCheck *create(Args &&..._args); |
|
~CompletionCheck() override = default; |
|
|
|
virtual void start() = 0; |
|
|
|
Q_SIGNALS: |
|
void finished(Result result, QStringList missingPackages); |
|
|
|
protected: |
|
explicit CompletionCheck(const QString &languageCode, QObject *parent = nullptr) |
|
: QObject(parent) |
|
, m_languageCode(languageCode) |
|
{ |
|
} |
|
|
|
const QString m_languageCode; |
|
|
|
private: |
|
Q_DISABLE_COPY_MOVE(CompletionCheck); |
|
}; |
|
|
|
class UbuntuCompletionCheck : public CompletionCheck |
|
{ |
|
public: |
|
using CompletionCheck::CompletionCheck; |
|
void start() override |
|
{ |
|
proc.setProgram("/usr/bin/check-language-support"); |
|
proc.setArguments({"--language", m_languageCode.left(m_languageCode.indexOf(QLatin1Char('@')))}); |
|
connect(&proc, qOverload<int, QProcess::ExitStatus>(&QProcess::finished), this, [this] { |
|
const QString output = QString::fromUtf8(proc.readAllStandardOutput().simplified()); |
|
// Whenever we don't get packages back simply pretend the language is complete as we can't |
|
// give any useful information on what's wrong anyway. |
|
Q_EMIT finished(output.isEmpty() ? Result::Complete : Result::Incomplete, output.split(QLatin1Char(' '))); |
|
}); |
|
proc.start(); |
|
} |
|
|
|
private: |
|
QProcess proc; |
|
}; |
|
|
|
template<typename... Args> |
|
CompletionCheck *CompletionCheck::create(Args &&..._args) |
|
{ |
|
#ifdef HAVE_PACKAGEKIT |
|
// Ubuntu completion depends on packagekit. When packagekit is not available there's no point supporting |
|
// completion checking as we'll have no way to complete the language if it is incomplete. |
|
KOSRelease os; |
|
if (os.id() == QLatin1String("ubuntu") || os.idLike().contains(QLatin1String("ubuntu"))) { |
|
return new UbuntuCompletionCheck(std::forward<Args>(_args)...); |
|
} |
|
#endif |
|
return nullptr; |
|
} |
|
|
|
QStringList TranslationsModel::m_languages = QStringList(); |
|
QSet<QString> TranslationsModel::m_installedLanguages = QSet<QString>(); |
|
|
|
TranslationsModel::TranslationsModel(QObject *parent) |
|
: QAbstractListModel(parent) |
|
{ |
|
if (m_installedLanguages.isEmpty()) { |
|
m_installedLanguages = KLocalizedString::availableDomainTranslations("plasmashell"); |
|
m_languages = m_installedLanguages.values(); |
|
} |
|
} |
|
|
|
QHash<int, QByteArray> TranslationsModel::roleNames() const |
|
{ |
|
QHash<int, QByteArray> roles = QAbstractItemModel::roleNames(); |
|
|
|
const auto e = QMetaEnum::fromType<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(QLatin1Char('@'))) { |
|
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(); |
|
} |
|
|
|
qCWarning(KCM_TRANSLATIONS) << "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; |
|
} |
|
|
|
QVariant SelectedTranslationsModel::data(const QModelIndex &index, int role) const |
|
{ |
|
if (!index.isValid() || index.row() < 0 || index.row() >= m_selectedLanguages.count()) { |
|
return QVariant(); |
|
} |
|
|
|
const QString code = m_selectedLanguages.at(index.row()); |
|
if (role == Qt::DisplayRole) { |
|
return languageCodeToName(code); |
|
} else if (role == LanguageCode) { |
|
return code; |
|
} else if (role == IsMissing) { |
|
return m_missingLanguages.contains(code); |
|
} else if (role == IsIncomplete) { |
|
return m_incompleteLanguagesWithPackages.contains(code); |
|
} else if (role == IsInstalling) { |
|
return m_installingLanguages.contains(code); |
|
} |
|
|
|
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) { |
|
return; |
|
} |
|
|
|
QStringList missingLanguages; |
|
|
|
for (const QString &lang : languages) { |
|
reloadCompleteness(lang); |
|
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::reloadCompleteness(const QString &languageCode) |
|
{ |
|
auto *check = CompletionCheck::create(languageCode, this); |
|
if (!check) { |
|
return; // no checking support - default to assume complete |
|
} |
|
connect(check, &CompletionCheck::finished, this, [this, languageCode, check](CompletionCheck::Result result, const QStringList &missingPackages) { |
|
check->deleteLater(); |
|
|
|
const int index = m_selectedLanguages.indexOf(languageCode); |
|
if (index < 0) { // removed since the check was started |
|
return; |
|
} |
|
|
|
switch (result) { |
|
case CompletionCheck::Result::Error: |
|
qCWarning(KCM_TRANSLATIONS) << "Failed to get completion status for" << languageCode; |
|
return; |
|
case CompletionCheck::Result::Incomplete: { |
|
// Cache this, we need to modify the data before marking the change on the model. |
|
const bool changed = !m_incompleteLanguagesWithPackages.contains(languageCode); |
|
m_incompleteLanguagesWithPackages[languageCode] = missingPackages; |
|
if (changed) { |
|
Q_EMIT dataChanged(createIndex(index, 0), createIndex(index, 0), {IsIncomplete}); |
|
} |
|
return; |
|
} |
|
case CompletionCheck::Result::Complete: |
|
if (m_incompleteLanguagesWithPackages.remove(languageCode) > 0) { |
|
Q_EMIT dataChanged(createIndex(index, 0), createIndex(index, 0), {IsIncomplete}); |
|
} |
|
return; |
|
} |
|
}); |
|
check->start(); |
|
} |
|
|
|
void SelectedTranslationsModel::completeLanguage(int index) |
|
{ |
|
#ifdef HAVE_PACKAGEKIT |
|
const QString code = m_selectedLanguages.at(index); |
|
|
|
auto completer = new LanguageCompleter(m_incompleteLanguagesWithPackages.value(code), this); |
|
connect(completer, &LanguageCompleter::complete, this, [this, code] { |
|
sender()->deleteLater(); |
|
const int index = m_selectedLanguages.indexOf(code); |
|
if (index < 0) { |
|
return; // entry was probably removed since the install was started |
|
} |
|
m_installingLanguages.removeAll(code); |
|
reloadCompleteness(code); |
|
Q_EMIT dataChanged(createIndex(index, 0), createIndex(index, 0), {IsInstalling}); |
|
}); |
|
m_incompleteLanguagesWithPackages.remove(code); |
|
m_installingLanguages << code; |
|
completer->start(); |
|
Q_EMIT dataChanged(createIndex(index, 0), createIndex(index, 0), {IsIncomplete, IsInstalling}); |
|
#else |
|
Q_UNUSED(index); |
|
#endif |
|
} |
|
|
|
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); |
|
} |
|
|
|
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<QString>(languages.cbegin(), languages.cend())).values(); |
|
|
|
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(); |
|
} |
|
|
|
#include "translationsmodel.moc"
|
|
|