Merge branch 'software-chooser' of https://github.com/calamares/calamares into development

This commit is contained in:
Philip Müller 2019-08-06 13:03:34 +02:00
commit 5a433e22ac
14 changed files with 384 additions and 14 deletions

View File

@ -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

View File

@ -34,6 +34,7 @@ set( libSources
locale/Label.cpp
locale/LabelModel.cpp
locale/Lookup.cpp
locale/TranslatableConfiguration.cpp
# Partition service
partition/PartitionSize.cpp

View File

@ -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(); }

View File

@ -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() );
}

View File

@ -33,6 +33,9 @@ private Q_SLOTS:
void testLanguageModelCount();
void testEsperanto();
void testTranslatableLanguages();
void testTranslatableConfig1();
void testTranslatableConfig2();
};
#endif

View 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

View 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

View File

@ -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()

View File

@ -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
{

View File

@ -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 } );
}
}

View File

@ -56,6 +56,7 @@ public:
void setConfigurationMap( const QVariantMap& configurationMap ) override;
private:
void fillModel( const QVariantList& items );
void hookupModel();
PackageChooserPage* m_widget;

View File

@ -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 )
{

View File

@ -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;

View File

@ -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"