Merge branch 'software-chooser' of https://github.com/calamares/calamares into development
This commit is contained in:
commit
5a433e22ac
@ -198,7 +198,7 @@ if( CMAKE_CXX_COMPILER_ID MATCHES "Clang" )
|
||||
)
|
||||
string( APPEND CMAKE_CXX_FLAGS " ${CLANG_WARNINGS}" )
|
||||
endforeach()
|
||||
set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DNOTREACHED='//'" )
|
||||
set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DNOTREACHED='//' -DFALLTHRU='[[clang::fallthrough]]'")
|
||||
|
||||
# Third-party code where we don't care so much about compiler warnings
|
||||
# (because it's uncomfortable to patch) get different flags; use
|
||||
@ -225,7 +225,7 @@ else()
|
||||
set( SUPPRESS_3RDPARTY_WARNINGS "" )
|
||||
set( SUPPRESS_BOOST_WARNINGS "" )
|
||||
|
||||
set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DNOTREACHED='__builtin_unreachable();'" )
|
||||
set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DNOTREACHED='__builtin_unreachable();' -DFALLTHRU='/* */'" )
|
||||
endif()
|
||||
|
||||
# Use mark_thirdparty_code() to reduce warnings from the compiler
|
||||
|
@ -34,6 +34,7 @@ set( libSources
|
||||
locale/Label.cpp
|
||||
locale/LabelModel.cpp
|
||||
locale/Lookup.cpp
|
||||
locale/TranslatableConfiguration.cpp
|
||||
|
||||
# Partition service
|
||||
partition/PartitionSize.cpp
|
||||
|
@ -58,7 +58,7 @@ public:
|
||||
|
||||
/** @brief Define a sorting order.
|
||||
*
|
||||
* English (@see isEnglish() -- it means en_US) is sorted at the top.
|
||||
* Locales are sorted by their id, which means the ISO 2-letter code + country.
|
||||
*/
|
||||
bool operator<( const Label& other ) const { return m_localeId < other.m_localeId; }
|
||||
|
||||
@ -78,6 +78,7 @@ public:
|
||||
QLocale locale() const { return m_locale; }
|
||||
|
||||
QString name() const { return m_locale.name(); }
|
||||
QString id() const { return m_localeId; }
|
||||
|
||||
/// @brief Convenience accessor to the language part of the locale
|
||||
QLocale::Language language() const { return m_locale.language(); }
|
||||
|
@ -19,6 +19,9 @@
|
||||
#include "Tests.h"
|
||||
|
||||
#include "locale/LabelModel.h"
|
||||
#include "locale/TranslatableConfiguration.h"
|
||||
|
||||
#include "CalamaresVersion.h"
|
||||
#include "utils/Logger.h"
|
||||
|
||||
#include <QtTest/QtTest>
|
||||
@ -84,3 +87,84 @@ LocaleTests::testEsperanto()
|
||||
QCOMPARE( QLocale( "eo" ).language(), QLocale::Esperanto );
|
||||
#endif
|
||||
}
|
||||
|
||||
static const QStringList&
|
||||
someLanguages()
|
||||
{
|
||||
static QStringList languages { "nl", "de", "da", "nb", "sr@latin", "ar", "ru" };
|
||||
return languages;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
LocaleTests::testTranslatableLanguages()
|
||||
{
|
||||
QStringList availableLanguages = QString( CALAMARES_TRANSLATION_LANGUAGES ).split( ';' );
|
||||
cDebug() << "Translation languages:" << availableLanguages;
|
||||
for ( const auto& language : someLanguages() )
|
||||
{
|
||||
// Could be QVERIFY, but then we don't see what language code fails
|
||||
QCOMPARE( availableLanguages.contains( language ) ? language : QString(), language );
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
LocaleTests::testTranslatableConfig1()
|
||||
{
|
||||
QCOMPARE( QLocale().name(), "C" ); // Otherwise plain get() is dubious
|
||||
CalamaresUtils::Locale::TranslatedString ts1( "Hello" );
|
||||
QCOMPARE( ts1.count(), 1 );
|
||||
|
||||
QCOMPARE( ts1.get(), "Hello" );
|
||||
QCOMPARE( ts1.get( QLocale( "nl" ) ), "Hello" );
|
||||
|
||||
QVariantMap map;
|
||||
map.insert( "description", "description (no language)" );
|
||||
CalamaresUtils::Locale::TranslatedString ts2( map, "description" );
|
||||
QCOMPARE( ts2.count(), 1 );
|
||||
|
||||
QCOMPARE( ts2.get(), "description (no language)" );
|
||||
QCOMPARE( ts2.get( QLocale( "nl" ) ), "description (no language)" );
|
||||
}
|
||||
|
||||
void
|
||||
LocaleTests::testTranslatableConfig2()
|
||||
{
|
||||
QCOMPARE( QLocale().name(), "C" ); // Otherwise plain get() is dubious
|
||||
QVariantMap map;
|
||||
|
||||
for ( const auto& language : someLanguages() )
|
||||
{
|
||||
map.insert( QString( "description[%1]" ).arg( language ),
|
||||
QString( "description (language %1)" ).arg( language ) );
|
||||
if ( language != "nl" )
|
||||
{
|
||||
map.insert( QString( "name[%1]" ).arg( language ), QString( "name (language %1)" ).arg( language ) );
|
||||
}
|
||||
}
|
||||
|
||||
CalamaresUtils::Locale::TranslatedString ts1( map, "description" );
|
||||
// The +1 is because "" is always also inserted
|
||||
QCOMPARE( ts1.count(), someLanguages().count() + 1 );
|
||||
|
||||
QCOMPARE( ts1.get(), "description" ); // it wasn't set
|
||||
QCOMPARE( ts1.get( QLocale( "nl" ) ), "description (language nl)" );
|
||||
for ( const auto& language : someLanguages() )
|
||||
{
|
||||
// Skip Serbian (latin) because QLocale() constructed with it
|
||||
// doesn't retain the @latin part.
|
||||
if ( language == "sr@latin" )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// Could be QVERIFY, but then we don't see what language code fails
|
||||
QCOMPARE( ts1.get( language ) == QString( "description (language %1)" ).arg( language ) ? language : QString(),
|
||||
language );
|
||||
}
|
||||
QCOMPARE( ts1.get( QLocale( QLocale::Language::Serbian, QLocale::Script::LatinScript, QLocale::Country::Serbia ) ),
|
||||
"description (language sr@latin)" );
|
||||
|
||||
CalamaresUtils::Locale::TranslatedString ts2( map, "name" );
|
||||
// We skipped dutch this time
|
||||
QCOMPARE( ts2.count(), someLanguages().count() );
|
||||
}
|
||||
|
@ -33,6 +33,9 @@ private Q_SLOTS:
|
||||
|
||||
void testLanguageModelCount();
|
||||
void testEsperanto();
|
||||
void testTranslatableLanguages();
|
||||
void testTranslatableConfig1();
|
||||
void testTranslatableConfig2();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
111
src/libcalamares/locale/TranslatableConfiguration.cpp
Normal file
111
src/libcalamares/locale/TranslatableConfiguration.cpp
Normal file
@ -0,0 +1,111 @@
|
||||
/* === This file is part of Calamares - <https://github.com/calamares> ===
|
||||
*
|
||||
* Copyright 2019, Adriaan de Groot <groot@kde.org>
|
||||
*
|
||||
* Calamares 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Calamares 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 Calamares. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "TranslatableConfiguration.h"
|
||||
|
||||
#include "LabelModel.h"
|
||||
|
||||
#include "utils/Logger.h"
|
||||
#include "utils/Variant.h"
|
||||
|
||||
#include <QRegularExpression>
|
||||
#include <QRegularExpressionMatch>
|
||||
|
||||
namespace CalamaresUtils
|
||||
{
|
||||
namespace Locale
|
||||
{
|
||||
TranslatedString::TranslatedString( const QString& string )
|
||||
{
|
||||
m_strings[ QString() ] = string;
|
||||
}
|
||||
TranslatedString::TranslatedString( const QVariantMap& map, const QString& key )
|
||||
{
|
||||
// Get the un-decorated value for the key
|
||||
QString value = CalamaresUtils::getString( map, key );
|
||||
if ( value.isEmpty() )
|
||||
{
|
||||
value = key;
|
||||
}
|
||||
m_strings[ QString() ] = value;
|
||||
|
||||
for ( auto it = map.constKeyValueBegin(); it != map.constKeyValueEnd(); ++it )
|
||||
{
|
||||
QString subkey = ( *it ).first;
|
||||
if ( subkey == key )
|
||||
{
|
||||
// Already obtained, above
|
||||
}
|
||||
else if ( subkey.startsWith( key ) )
|
||||
{
|
||||
QRegularExpressionMatch match;
|
||||
if ( subkey.indexOf( QRegularExpression( "\\[([a-zA-Z_@]*)\\]" ), 0, &match ) > 0 )
|
||||
{
|
||||
QString language = match.captured( 1 );
|
||||
m_strings[ language ] = ( *it ).second.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString
|
||||
TranslatedString::get() const
|
||||
{
|
||||
return get( QLocale() );
|
||||
}
|
||||
|
||||
QString
|
||||
TranslatedString::get( const QLocale& locale ) const
|
||||
{
|
||||
QString localeName = locale.name();
|
||||
// Special case, sr@latin doesn't have the @latin reflected in the name
|
||||
if ( locale.language() == QLocale::Language::Serbian && locale.script() == QLocale::Script::LatinScript )
|
||||
{
|
||||
localeName = QStringLiteral( "sr@latin" );
|
||||
}
|
||||
|
||||
if ( m_strings.contains( localeName ) )
|
||||
{
|
||||
return m_strings[ localeName ];
|
||||
}
|
||||
int index = localeName.indexOf( '@' );
|
||||
if ( index > 0 )
|
||||
{
|
||||
localeName.truncate( index );
|
||||
if ( m_strings.contains( localeName ) )
|
||||
{
|
||||
return m_strings[ localeName ];
|
||||
}
|
||||
}
|
||||
|
||||
index = localeName.indexOf( '_' );
|
||||
if ( index > 0 )
|
||||
{
|
||||
localeName.truncate( index );
|
||||
if ( m_strings.contains( localeName ) )
|
||||
{
|
||||
return m_strings[ localeName ];
|
||||
}
|
||||
}
|
||||
|
||||
return m_strings[ QString() ];
|
||||
}
|
||||
|
||||
|
||||
} // namespace Locale
|
||||
} // namespace CalamaresUtils
|
66
src/libcalamares/locale/TranslatableConfiguration.h
Normal file
66
src/libcalamares/locale/TranslatableConfiguration.h
Normal file
@ -0,0 +1,66 @@
|
||||
/* === This file is part of Calamares - <https://github.com/calamares> ===
|
||||
*
|
||||
* Copyright 2019, Adriaan de Groot <groot@kde.org>
|
||||
*
|
||||
* Calamares 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Calamares 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 Calamares. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef LOCALE_TRANSLATABLECONFIGURATION_H
|
||||
#define LOCALE_TRANSLATABLECONFIGURATION_H
|
||||
|
||||
#include "DllMacro.h"
|
||||
|
||||
#include <QLocale>
|
||||
#include <QMap>
|
||||
#include <QVariant>
|
||||
|
||||
namespace CalamaresUtils
|
||||
{
|
||||
namespace Locale
|
||||
{
|
||||
/** @brief A human-readable string from a configuration file
|
||||
*
|
||||
* The configuration files can contain human-readable strings,
|
||||
* but those need their own translations and are not supported
|
||||
* by QObject::tr or anything else.
|
||||
*/
|
||||
class DLLEXPORT TranslatedString
|
||||
{
|
||||
public:
|
||||
/** @brief Get all the translations connected to @p key
|
||||
*/
|
||||
TranslatedString( const QVariantMap& map, const QString& key );
|
||||
/** @brief Not-actually-translated string.
|
||||
*/
|
||||
TranslatedString( const QString& string );
|
||||
/// @brief Empty string
|
||||
TranslatedString()
|
||||
: TranslatedString( QString() ) {}
|
||||
|
||||
int count() const { return m_strings.count(); }
|
||||
|
||||
/// @brief Gets the string in the current locale
|
||||
QString get() const;
|
||||
|
||||
/// @brief Gets the string from the given locale
|
||||
QString get( const QLocale& ) const;
|
||||
|
||||
private:
|
||||
// Maps locale name to human-readable string, "" is English
|
||||
QMap< QString, QString > m_strings;
|
||||
};
|
||||
} // namespace Locale
|
||||
} // namespace CalamaresUtils
|
||||
|
||||
#endif
|
@ -20,11 +20,11 @@ if( ECM_FOUND AND BUILD_TESTING )
|
||||
ecm_add_test(
|
||||
Tests.cpp
|
||||
TEST_NAME
|
||||
packagechooosertest
|
||||
packagechoosertest
|
||||
LINK_LIBRARIES
|
||||
${CALAMARES_LIBRARIES}
|
||||
Qt5::Core
|
||||
Qt5::Test
|
||||
)
|
||||
calamares_automoc( packagechooosertest)
|
||||
calamares_automoc( packagechoosertest)
|
||||
endif()
|
||||
|
@ -41,9 +41,12 @@ PackageChooserPage::PackageChooserPage( PackageChooserMode mode, QWidget* parent
|
||||
switch ( mode )
|
||||
{
|
||||
case PackageChooserMode::Optional:
|
||||
FALLTHRU;
|
||||
case PackageChooserMode::Required:
|
||||
ui->products->setSelectionMode( QAbstractItemView::SingleSelection );
|
||||
break;
|
||||
case PackageChooserMode::OptionalMultiple:
|
||||
FALLTHRU;
|
||||
case PackageChooserMode::RequiredMultiple:
|
||||
ui->products->setSelectionMode( QAbstractItemView::ExtendedSelection );
|
||||
}
|
||||
@ -54,9 +57,9 @@ PackageChooserPage::currentChanged( const QModelIndex& index )
|
||||
{
|
||||
if ( !index.isValid() || !ui->products->selectionModel()->hasSelection() )
|
||||
{
|
||||
ui->productName->setText( m_introduction.name );
|
||||
ui->productName->setText( m_introduction.name.get() );
|
||||
ui->productScreenshot->setPixmap( m_introduction.screenshot );
|
||||
ui->productDescription->setText( m_introduction.description );
|
||||
ui->productDescription->setText( m_introduction.description.get() );
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -177,6 +177,12 @@ PackageChooserViewStep::setConfigurationMap( const QVariantMap& configurationMap
|
||||
m_id = moduleInstanceKey().split( '@' ).last();
|
||||
}
|
||||
|
||||
bool first_time = !m_model;
|
||||
if ( configurationMap.contains( "items" ) )
|
||||
{
|
||||
fillModel( configurationMap.value( "items" ).toList() );
|
||||
}
|
||||
|
||||
// TODO: replace this hard-coded model
|
||||
if ( !m_model )
|
||||
{
|
||||
@ -192,12 +198,65 @@ PackageChooserViewStep::setConfigurationMap( const QVariantMap& configurationMap
|
||||
m_model->addPackage( PackageItem { "kde", "kde", "Plasma", "Plasma Desktop", ":/images/kde.png" } );
|
||||
m_model->addPackage( PackageItem {
|
||||
"gnome", "gnome", "GNOME", "GNU Networked Object Modeling Environment Desktop", ":/images/gnome.png" } );
|
||||
}
|
||||
|
||||
if ( first_time && m_widget && m_model )
|
||||
{
|
||||
hookupModel();
|
||||
}
|
||||
}
|
||||
|
||||
if ( m_widget )
|
||||
void
|
||||
PackageChooserViewStep::fillModel( const QVariantList& items )
|
||||
{
|
||||
if ( !m_model )
|
||||
{
|
||||
m_model = new PackageListModel( nullptr );
|
||||
}
|
||||
|
||||
if ( items.isEmpty() )
|
||||
{
|
||||
cWarning() << "No *items* for PackageChooser module.";
|
||||
return;
|
||||
}
|
||||
|
||||
cDebug() << "Loading PackageChooser model items from config";
|
||||
int item_index = 0;
|
||||
for ( const auto& item_it : items )
|
||||
{
|
||||
++item_index;
|
||||
QVariantMap item_map = item_it.toMap();
|
||||
if ( item_map.isEmpty() )
|
||||
{
|
||||
hookupModel();
|
||||
cWarning() << "PackageChooser entry" << item_index << "is not valid.";
|
||||
continue;
|
||||
}
|
||||
|
||||
QString id = CalamaresUtils::getString( item_map, "id" );
|
||||
QString package = CalamaresUtils::getString( item_map, "package" );
|
||||
QString name = CalamaresUtils::getString( item_map, "name" );
|
||||
QString description = CalamaresUtils::getString( item_map, "description" );
|
||||
QString screenshot = CalamaresUtils::getString( item_map, "screenshot" );
|
||||
|
||||
if ( name.isEmpty() && id.isEmpty() )
|
||||
{
|
||||
name = tr( "No product" );
|
||||
}
|
||||
else if ( name.isEmpty() )
|
||||
{
|
||||
cWarning() << "PackageChooser item" << id << "has an empty name.";
|
||||
continue;
|
||||
}
|
||||
if ( description.isEmpty() )
|
||||
{
|
||||
description = tr( "No description provided." );
|
||||
}
|
||||
if ( screenshot.isEmpty() )
|
||||
{
|
||||
screenshot = QStringLiteral( ":/images/no-selection.png" );
|
||||
}
|
||||
|
||||
m_model->addPackage( PackageItem { id, package, name, description, screenshot } );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -56,6 +56,7 @@ public:
|
||||
void setConfigurationMap( const QVariantMap& configurationMap ) override;
|
||||
|
||||
private:
|
||||
void fillModel( const QVariantList& items );
|
||||
void hookupModel();
|
||||
|
||||
PackageChooserPage* m_widget;
|
||||
|
@ -118,11 +118,11 @@ PackageListModel::data( const QModelIndex& index, int role ) const
|
||||
|
||||
if ( role == Qt::DisplayRole /* Also PackageNameRole */ )
|
||||
{
|
||||
return m_packages[ row ].name;
|
||||
return m_packages[ row ].name.get();
|
||||
}
|
||||
else if ( role == DescriptionRole )
|
||||
{
|
||||
return m_packages[ row ].description;
|
||||
return m_packages[ row ].description.get();
|
||||
}
|
||||
else if ( role == ScreenshotRole )
|
||||
{
|
||||
|
@ -19,6 +19,7 @@
|
||||
#ifndef PACKAGEMODEL_H
|
||||
#define PACKAGEMODEL_H
|
||||
|
||||
#include "locale/TranslatableConfiguration.h"
|
||||
#include "utils/NamedEnum.h"
|
||||
|
||||
#include <QAbstractListModel>
|
||||
@ -41,9 +42,8 @@ struct PackageItem
|
||||
QString id;
|
||||
// TODO: may need more than one
|
||||
QString package;
|
||||
// TODO: name and description are localized
|
||||
QString name;
|
||||
QString description;
|
||||
CalamaresUtils::Locale::TranslatedString name;
|
||||
CalamaresUtils::Locale::TranslatedString description;
|
||||
// TODO: may be more than one
|
||||
QPixmap screenshot;
|
||||
|
||||
|
@ -19,3 +19,44 @@
|
||||
# or "optionalmultiple", "requiredmultiple" (for zero-or-more
|
||||
# or one-or-more).
|
||||
mode: required
|
||||
|
||||
# Items to display in the chooser. In general, this should be a
|
||||
# pretty short list to avoid overwhelming the UI. This is a list
|
||||
# of objects, and the items are displayed in list order.
|
||||
#
|
||||
# Each item has an id, which is used in setting # the value of
|
||||
# *packagechooser_<module-id>*). The following fields
|
||||
# are mandatory:
|
||||
#
|
||||
# - *id* ID for the product. The ID "" is special, and is used for
|
||||
# "no package selected". Only include this if the mode allows
|
||||
# selecting none.
|
||||
# - *package* Package name for the product. While mandatory, this is
|
||||
# not actually used anywhere.
|
||||
# - *name* Human-readable, but untranslated, name of the product.
|
||||
# - *description* Human-readable, but untranslated, description.
|
||||
# - *screenshot* Path to a single screenshot of the product. May be
|
||||
# a filesystem path or a QRC path (e.g. ":/images/no-selection.png").
|
||||
#
|
||||
# Use the empty string "" as ID / key for the "no selection" item if
|
||||
# you want to customize the display of that item as well.
|
||||
items:
|
||||
- id: ""
|
||||
package: ""
|
||||
name: "No Desktop"
|
||||
name[nl]: "Geen desktop"
|
||||
description: "Please pick a desktop environment from the list. If you don't want to install a desktop, that's fine, your system will start up in text-only mode and you can install a desktop environment later."
|
||||
description[nl]: "Kies eventueel een desktop-omgeving uit deze lijst. Als u geen desktop-omgeving wenst te gebruiken, kies er dan geen. In dat geval start het systeem straks op in tekst-modus en kunt u later alsnog een desktop-omgeving installeren."
|
||||
screenshot: ":/images/no-selection.png"
|
||||
- id: kde
|
||||
package: kde
|
||||
name: Plasma Desktop
|
||||
description: "KDE Plasma Desktop, simple by default, a clean work area for real-world usage which intends to stay out of your way. Plasma is powerful when needed, enabling the user to create the workflow that makes them more effective to complete their tasks."
|
||||
screenshot: ":/images/kde.png"
|
||||
- id: gnome
|
||||
package: gnome
|
||||
name: GNOME
|
||||
description: GNU Networked Object Modeling Environment Desktop
|
||||
screenshot: ":/images/gnome.png"
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user