diff --git a/CHANGES b/CHANGES
index c84f245b0..d41d4b8fa 100644
--- a/CHANGES
+++ b/CHANGES
@@ -18,12 +18,25 @@ This release contains contributions from (alphabetically by first name):
number of jobs. (Thanks to Bill Auger)
- Preliminary work has been added to post the installation log to a
pastebin for bug reporting. (Thanks to Bill Auger)
+ - Support for translated human-readable strings in Calamares
+ config files has been added. This is used only in the *packagechooser*
+ module (see below) but will expand to those modules that need
+ user-visible strings from the configuration file (existing
+ solutions need either gettext or Qt support).
+ - Esperanto is now available when Qt version 5.12.2 or later is used.
## Modules ##
- *fstab* A new configuration key *efiMountOptions* has been added, to
allow setting filesystem options specifically for the EFI partition.
(Thanks to apt-ghetto)
+ - *packagechooser* is a new module for low-density package choices,
+ e.g. for selecting a default desktop environment, or adding some
+ proprietary drivers, or chosing browsers of office suites. It presents
+ **one** collection of items -- at most ten or so, because of the UI --
+ and the user can select zero or more of them. The behavior is
+ configurable, and package information can be set through the Calamares
+ configuration file or by reading AppData files for the packages. #426
# 3.2.11 (2019-07-06) #
diff --git a/io.calamares.calamares.appdata.xml b/io.calamares.calamares.appdata.xml
new file mode 100644
index 000000000..355f485c2
--- /dev/null
+++ b/io.calamares.calamares.appdata.xml
@@ -0,0 +1,29 @@
+
+
+ io.calamares.calamares.desktop
+ CC0-1.0
+ GPL-3.0+
+ Calamares
+ Calamares
+ Calamares
+ Calamares
+ Calamares Linux Installer
+ Calamares Linux Installer
+ Linux Installatieprogramma Calamares
+
+
Calamares is an installer program for Linux distributions.
+
Calamares is een installatieprogramma voor Linux distributies.
+ https://calamares.io/images/cal_640.png
+
+
+
+ calamares
+
+
diff --git a/src/libcalamares/locale/Tests.cpp b/src/libcalamares/locale/Tests.cpp
index 664390511..fa84cd2fb 100644
--- a/src/libcalamares/locale/Tests.cpp
+++ b/src/libcalamares/locale/Tests.cpp
@@ -96,6 +96,10 @@ someLanguages()
}
+/** @brief Check consistency of test data
+ * Check that all the languages used in testing, are actually enabled
+ * in Calamares translations.
+ */
void
LocaleTests::testTranslatableLanguages()
{
@@ -108,12 +112,19 @@ LocaleTests::testTranslatableLanguages()
}
}
+/** @brief Test strings with no translations
+ */
void
LocaleTests::testTranslatableConfig1()
{
+ CalamaresUtils::Locale::TranslatedString ts0;
+ QVERIFY( ts0.isEmpty() );
+ QCOMPARE( ts0.count(), 1 ); // the empty string
+
QCOMPARE( QLocale().name(), "C" ); // Otherwise plain get() is dubious
CalamaresUtils::Locale::TranslatedString ts1( "Hello" );
QCOMPARE( ts1.count(), 1 );
+ QVERIFY( !ts1.isEmpty() );
QCOMPARE( ts1.get(), "Hello" );
QCOMPARE( ts1.get( QLocale( "nl" ) ), "Hello" );
@@ -122,11 +133,14 @@ LocaleTests::testTranslatableConfig1()
map.insert( "description", "description (no language)" );
CalamaresUtils::Locale::TranslatedString ts2( map, "description" );
QCOMPARE( ts2.count(), 1 );
+ QVERIFY( !ts2.isEmpty() );
QCOMPARE( ts2.get(), "description (no language)" );
QCOMPARE( ts2.get( QLocale( "nl" ) ), "description (no language)" );
}
+/** @bref Test strings with translations.
+ */
void
LocaleTests::testTranslatableConfig2()
{
@@ -143,11 +157,22 @@ LocaleTests::testTranslatableConfig2()
}
}
+ // If there's no untranslated string in the map, it is considered empty
+ CalamaresUtils::Locale::TranslatedString ts0( map, "description" );
+ QVERIFY( ts0.isEmpty() ); // Because no untranslated string
+ QCOMPARE( ts0.count(),
+ someLanguages().count() + 1 ); // But there are entries for the translations, plus an empty string
+
+ // expand the map with untranslated entries
+ map.insert( QString( "description" ), "description (no language)" );
+ map.insert( QString( "name" ), "name (no language)" );
+
CalamaresUtils::Locale::TranslatedString ts1( map, "description" );
// The +1 is because "" is always also inserted
QCOMPARE( ts1.count(), someLanguages().count() + 1 );
+ QVERIFY( !ts1.isEmpty() );
- QCOMPARE( ts1.get(), "description" ); // it wasn't set
+ QCOMPARE( ts1.get(), "description (no language)" ); // it wasn't set
QCOMPARE( ts1.get( QLocale( "nl" ) ), "description (language nl)" );
for ( const auto& language : someLanguages() )
{
@@ -167,4 +192,10 @@ LocaleTests::testTranslatableConfig2()
CalamaresUtils::Locale::TranslatedString ts2( map, "name" );
// We skipped dutch this time
QCOMPARE( ts2.count(), someLanguages().count() );
+ QVERIFY( !ts2.isEmpty() );
+
+ // This key doesn't exist
+ CalamaresUtils::Locale::TranslatedString ts3( map, "front" );
+ QVERIFY( ts3.isEmpty() );
+ QCOMPARE( ts3.count(), 1 ); // The empty string
}
diff --git a/src/libcalamares/locale/TranslatableConfiguration.cpp b/src/libcalamares/locale/TranslatableConfiguration.cpp
index b3b5259c9..7493c836c 100644
--- a/src/libcalamares/locale/TranslatableConfiguration.cpp
+++ b/src/libcalamares/locale/TranslatableConfiguration.cpp
@@ -38,10 +38,6 @@ 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 )
diff --git a/src/libcalamares/locale/TranslatableConfiguration.h b/src/libcalamares/locale/TranslatableConfiguration.h
index b2f598069..a055cbfbd 100644
--- a/src/libcalamares/locale/TranslatableConfiguration.h
+++ b/src/libcalamares/locale/TranslatableConfiguration.h
@@ -46,9 +46,23 @@ public:
TranslatedString( const QString& string );
/// @brief Empty string
TranslatedString()
- : TranslatedString( QString() ) {}
+ : TranslatedString( QString() )
+ {
+ }
+ /** @brief How many strings (translations) are there?
+ *
+ * This is always at least 1 (for the untranslated string),
+ * but may be more than 1 even when isEmpty() is true --
+ * if there is no untranslated version, for instance.
+ */
int count() const { return m_strings.count(); }
+ /** @brief Consider this string empty?
+ *
+ * Only the state of the untranslated string is considered,
+ * so count() may be more than 1 even while the string is empty.
+ */
+ bool isEmpty() const { return m_strings[ QString() ].isEmpty(); }
/// @brief Gets the string in the current locale
QString get() const;
diff --git a/src/modules/packagechooser/CMakeLists.txt b/src/modules/packagechooser/CMakeLists.txt
index 4663ccce7..70a86a3bb 100644
--- a/src/modules/packagechooser/CMakeLists.txt
+++ b/src/modules/packagechooser/CMakeLists.txt
@@ -1,4 +1,15 @@
find_package( Qt5 COMPONENTS Core Gui Widgets REQUIRED )
+set( _extra_libraries "" )
+
+### OPTIONAL AppData XML support in PackageModel
+#
+#
+find_package(Qt5 COMPONENTS Xml)
+if ( Qt5Xml_FOUND )
+ add_definitions( -DHAVE_XML )
+ list( APPEND _extra_libraries Qt5::Xml )
+endif()
+
calamares_add_plugin( packagechooser
TYPE viewmodule
@@ -13,6 +24,7 @@ calamares_add_plugin( packagechooser
page_package.ui
LINK_PRIVATE_LIBRARIES
calamaresui
+ ${_extra_libraries}
SHARED_LIB
)
@@ -23,8 +35,11 @@ if( ECM_FOUND AND BUILD_TESTING )
packagechoosertest
LINK_LIBRARIES
${CALAMARES_LIBRARIES}
+ calamares_viewmodule_packagechooser
Qt5::Core
Qt5::Test
+ Qt5::Gui
+ ${_extra_libraries}
)
calamares_automoc( packagechoosertest)
endif()
diff --git a/src/modules/packagechooser/PackageChooserViewStep.cpp b/src/modules/packagechooser/PackageChooserViewStep.cpp
index 4476eb9e6..6df785a06 100644
--- a/src/modules/packagechooser/PackageChooserViewStep.cpp
+++ b/src/modules/packagechooser/PackageChooserViewStep.cpp
@@ -232,31 +232,14 @@ PackageChooserViewStep::fillModel( const QVariantList& items )
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() )
+ if ( item_map.contains( "appdata" ) )
{
- name = tr( "No product" );
+ m_model->addPackage( PackageItem::fromAppData( item_map ) );
}
- else if ( name.isEmpty() )
+ else
{
- cWarning() << "PackageChooser item" << id << "has an empty name.";
- continue;
+ m_model->addPackage( PackageItem( item_map ) );
}
- 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 } );
}
}
diff --git a/src/modules/packagechooser/PackageModel.cpp b/src/modules/packagechooser/PackageModel.cpp
index f133f4fbd..59c6973ba 100644
--- a/src/modules/packagechooser/PackageModel.cpp
+++ b/src/modules/packagechooser/PackageModel.cpp
@@ -19,6 +19,13 @@
#include "PackageModel.h"
#include "utils/Logger.h"
+#include "utils/Variant.h"
+
+#ifdef HAVE_XML
+#include
+#include
+#include
+#endif
const NamedEnumTable< PackageChooserMode >&
roleNames()
@@ -40,13 +47,6 @@ roleNames()
return names;
}
-PackageItem
-PackageItem::fromAppStream( const QString& filename )
-{
- // TODO: implement this
- return PackageItem {};
-}
-
PackageItem::PackageItem() {}
PackageItem::PackageItem( const QString& a_id,
@@ -73,6 +73,239 @@ PackageItem::PackageItem( const QString& a_id,
{
}
+PackageItem::PackageItem::PackageItem( const QVariantMap& item_map )
+ : id( CalamaresUtils::getString( item_map, "id" ) )
+ , package( CalamaresUtils::getString( item_map, "package" ) )
+ , name( CalamaresUtils::Locale::TranslatedString( item_map, "name" ) )
+ , description( CalamaresUtils::Locale::TranslatedString( item_map, "description" ) )
+ , screenshot( CalamaresUtils::getString( item_map, "screenshot" ) )
+{
+ if ( name.isEmpty() && id.isEmpty() )
+ {
+ name = QObject::tr( "No product" );
+ }
+ else if ( name.isEmpty() )
+ {
+ cWarning() << "PackageChooser item" << id << "has an empty name.";
+ }
+ if ( description.isEmpty() )
+ {
+ description = QObject::tr( "No description provided." );
+ }
+}
+
+#ifdef HAVE_XML
+/** @brief try to load the given @p fileName XML document
+ *
+ * Returns a QDomDocument, which will be valid iff the file can
+ * be read and contains valid XML data.
+ */
+static inline QDomDocument
+loadAppData( const QString& fileName )
+{
+ QFile file( fileName );
+ if ( !file.open( QIODevice::ReadOnly ) )
+ {
+ return QDomDocument();
+ }
+ QDomDocument doc( "AppData" );
+ if ( !doc.setContent( &file ) )
+ {
+ file.close();
+ return QDomDocument();
+ }
+ file.close();
+ return doc;
+}
+
+/** @brief gets the text of child element @p tagName
+ */
+static inline QString
+getChildText( const QDomNode& n, const QString& tagName )
+{
+ QDomElement e = n.firstChildElement( tagName );
+ return e.isNull() ? QString() : e.text();
+}
+
+/** @brief Gets a suitable screenshot path
+ *
+ * The element contains zero or more
+ * elements, which can have a *type* associated with them.
+ * Scan the screenshot elements, return the path
+ * for the one labeled with type=default or, if there is no
+ * default, the first element.
+ */
+static inline QString
+getScreenshotPath( const QDomNode& n )
+{
+ QDomElement shotsNode = n.firstChildElement( "screenshots" );
+ if ( shotsNode.isNull() )
+ {
+ return QString();
+ }
+
+ const QDomNodeList shotList = shotsNode.childNodes();
+ int firstScreenshot = -1; // Use which screenshot node?
+ for ( int i = 0; i < shotList.count(); ++i )
+ {
+ if ( !shotList.at( i ).isElement() )
+ {
+ continue;
+ }
+ QDomElement e = shotList.at( i ).toElement();
+ if ( e.tagName() != "screenshot" )
+ {
+ continue;
+ }
+ // If none has the "type=default" attribute, use the first one
+ if ( firstScreenshot < 0 )
+ {
+ firstScreenshot = i;
+ }
+ // But type=default takes precedence.
+ if ( e.hasAttribute( "type" ) && e.attribute( "type" ) == "default" )
+ {
+ firstScreenshot = i;
+ break;
+ }
+ }
+
+ if ( firstScreenshot >= 0 )
+ {
+ return shotList.at( firstScreenshot ).firstChildElement( "image" ).text();
+ }
+
+ return QString();
+}
+
+/** @brief Returns language of the given element @p e
+ *
+ * Transforms the attribute value for xml:lang to something
+ * suitable for TranslatedString (e.g. [lang]).
+ */
+static inline QString
+getLanguage( const QDomElement& e )
+{
+ QString language = e.attribute( "xml:lang" );
+ if ( !language.isEmpty() )
+ {
+ language.replace( '-', '_' );
+ language.prepend( '[' );
+ language.append( ']' );
+ }
+ return language;
+}
+
+/** @brief Scan the list of @p children for @p tagname elements and add them to the map
+ *
+ * Uses @p mapname instead of @p tagname for the entries in map @p m
+ * to allow renaming from XML to map keys (in particular for
+ * TranslatedString). Also transforms xml:lang attributes to suitable
+ * key-decorations on @p mapname.
+ */
+static inline void
+fillMap( QVariantMap& m, const QDomNodeList& children, const QString& tagname, const QString& mapname )
+{
+ for ( int i = 0; i < children.count(); ++i )
+ {
+ if ( !children.at( i ).isElement() )
+ {
+ continue;
+ }
+
+ QDomElement e = children.at( i ).toElement();
+ if ( e.tagName() != tagname )
+ {
+ continue;
+ }
+
+ m[ mapname + getLanguage( e ) ] = e.text();
+ }
+}
+
+/** @brief gets the and elements
+*
+* Builds up a map of the elements (which may have a *lang*
+* attribute to indicate translations and paragraphs of the
+* element (also with lang). Uses the
+* elements to supplement the description if no description
+* is available for a given language.
+*
+* Returns a map with keys suitable for use by TranslatedString.
+*/
+static inline QVariantMap
+getNameAndSummary( const QDomNode& n )
+{
+ QVariantMap m;
+
+ const QDomNodeList children = n.childNodes();
+ fillMap( m, children, "name", "name" );
+ fillMap( m, children, "summary", "description" );
+
+ const QDomElement description = n.firstChildElement( "description" );
+ if ( !description.isNull() )
+ {
+ fillMap( m, description.childNodes(), "p", "description" );
+ }
+
+ return m;
+}
+#endif
+
+PackageItem
+PackageItem::fromAppData( const QVariantMap& item_map )
+{
+#ifdef HAVE_XML
+ QString fileName = CalamaresUtils::getString( item_map, "appdata" );
+ if ( fileName.isEmpty() )
+ {
+ cWarning() << "Can't load AppData without a suitable key.";
+ return PackageItem();
+ }
+ cDebug() << "Loading AppData XML from" << fileName;
+
+ QDomDocument doc = loadAppData( fileName );
+ if ( doc.isNull() )
+ {
+ return PackageItem();
+ }
+
+ QDomElement componentNode = doc.documentElement();
+ if ( !componentNode.isNull() && componentNode.tagName() == "component" )
+ {
+ // An "id" entry in the Calamares config overrides ID in the AppData
+ QString id = CalamaresUtils::getString( item_map, "id" );
+ if ( id.isEmpty() )
+ {
+ id = getChildText( componentNode, "id" );
+ }
+ if ( id.isEmpty() )
+ {
+ return PackageItem();
+ }
+
+ // A "screenshot" entry in the Calamares config overrides AppData
+ QString screenshotPath = CalamaresUtils::getString( item_map, "screenshot" );
+ if ( screenshotPath.isEmpty() )
+ {
+ screenshotPath = getScreenshotPath( componentNode );
+ }
+
+ QVariantMap map = getNameAndSummary( componentNode );
+ map.insert( "id", id );
+ map.insert( "screenshot", screenshotPath );
+
+ return PackageItem( map );
+ }
+
+ return PackageItem();
+#else
+ cWarning() << "Loading AppData XML is not supported.";
+
+ return PackageItem();
+#endif
+}
+
PackageListModel::PackageListModel( QObject* parent )
: QAbstractListModel( parent )
@@ -90,10 +323,14 @@ PackageListModel::~PackageListModel() {}
void
PackageListModel::addPackage( PackageItem&& p )
{
- int c = m_packages.count();
- beginInsertRows( QModelIndex(), c, c );
- m_packages.append( p );
- endInsertRows();
+ // Only add valid packages
+ if ( p.isValid() )
+ {
+ int c = m_packages.count();
+ beginInsertRows( QModelIndex(), c, c );
+ m_packages.append( p );
+ endInsertRows();
+ }
}
int
diff --git a/src/modules/packagechooser/PackageModel.h b/src/modules/packagechooser/PackageModel.h
index 68e19a25d..869e124f0 100644
--- a/src/modules/packagechooser/PackageModel.h
+++ b/src/modules/packagechooser/PackageModel.h
@@ -40,11 +40,10 @@ const NamedEnumTable< PackageChooserMode >& roleNames();
struct PackageItem
{
QString id;
- // TODO: may need more than one
+ // FIXME: unused
QString package;
CalamaresUtils::Locale::TranslatedString name;
CalamaresUtils::Locale::TranslatedString description;
- // TODO: may be more than one
QPixmap screenshot;
/// @brief Create blank PackageItem
@@ -56,14 +55,44 @@ struct PackageItem
*/
PackageItem( const QString& id, const QString& package, const QString& name, const QString& description );
+ /** @brief Creates a PackageItem from given strings.
+ *
+ * Set all the text members and load the screenshot from the given
+ * @p screenshotPath, which may be a QRC path (:/path/in/qrc) or
+ * a filesystem path, whatever QPixmap understands.
+ */
PackageItem( const QString& id,
const QString& package,
const QString& name,
const QString& description,
const QString& screenshotPath );
- // TODO: implement this
- PackageItem fromAppStream( const QString& filename );
+ /** @brief Creates a PackageItem from a QVariantMap
+ *
+ * This is intended for use when loading PackageItems from a
+ * configuration map. It will look up the various keys in the map
+ * and handle translation strings as well.
+ */
+ PackageItem( const QVariantMap& map );
+
+ /** @brief Is this item valid?
+ *
+ * A valid item has an untranslated name available.
+ */
+ bool isValid() const { return !name.isEmpty(); }
+
+ /** @brief Loads an AppData XML file and returns a PackageItem
+ *
+ * The @p map must have a key *appdata*. That is used as the
+ * primary source of information, but keys *id* and *screenshotPath*
+ * may be used to override parts of the AppData -- so that the
+ * ID is under the control of Calamares, and the screenshot can be
+ * forced to a local path available on the installation medium.
+ *
+ * Requires XML support in libcalamares, if not present will
+ * return invalid PackageItems.
+ */
+ static PackageItem fromAppData( const QVariantMap& map );
};
using PackageList = QVector< PackageItem >;
@@ -75,6 +104,10 @@ public:
PackageListModel( QObject* parent );
virtual ~PackageListModel() override;
+ /** @brief Add a package @p to the model
+ *
+ * Only valid packages are added -- that is, they must have a name.
+ */
void addPackage( PackageItem&& p );
int rowCount( const QModelIndex& index ) const override;
diff --git a/src/modules/packagechooser/Tests.cpp b/src/modules/packagechooser/Tests.cpp
index c016f1808..3e7961b92 100644
--- a/src/modules/packagechooser/Tests.cpp
+++ b/src/modules/packagechooser/Tests.cpp
@@ -18,9 +18,13 @@
#include "Tests.h"
+#include "PackageModel.h"
+
+#include "utils/Logger.h"
+
#include
-QTEST_GUILESS_MAIN( PackageChooserTests )
+QTEST_MAIN( PackageChooserTests )
PackageChooserTests::PackageChooserTests() {}
@@ -29,6 +33,7 @@ PackageChooserTests::~PackageChooserTests() {}
void
PackageChooserTests::initTestCase()
{
+ Logger::setupLogLevel( Logger::LOGDEBUG );
}
void
@@ -36,3 +41,37 @@ PackageChooserTests::testBogus()
{
QVERIFY( true );
}
+
+void
+PackageChooserTests::testAppData()
+{
+ // Path from the build-dir
+ QString appdataName( "../io.calamares.calamares.appdata.xml" );
+ QVERIFY( QFile::exists( appdataName ) );
+
+ QVariantMap m;
+ m.insert( "appdata", appdataName );
+
+ PackageItem p1 = PackageItem::fromAppData( m );
+#ifdef HAVE_XML
+ QVERIFY( p1.isValid() );
+ QCOMPARE( p1.id, "io.calamares.calamares.desktop" );
+ QCOMPARE( p1.name.get(), "Calamares" );
+ // The entry has precedence
+ QCOMPARE( p1.description.get(), "Calamares is an installer program for Linux distributions." );
+ // .. but en_GB doesn't have an entry in description, so uses
+ QCOMPARE( p1.description.get( QLocale( "en_GB" ) ), "Calamares Linux Installer" );
+ QCOMPARE( p1.description.get( QLocale( "nl" ) ), "Calamares is een installatieprogramma voor Linux distributies." );
+ QVERIFY( p1.screenshot.isNull() );
+
+ m.insert( "id", "calamares" );
+ m.insert( "screenshot", ":/images/calamares.png" );
+ PackageItem p2= PackageItem::fromAppData( m );
+ QVERIFY( p2.isValid() );
+ QCOMPARE( p2.id, "calamares" );
+ QCOMPARE( p2.description.get( QLocale( "nl" ) ), "Calamares is een installatieprogramma voor Linux distributies." );
+ QVERIFY( !p2.screenshot.isNull() );
+#else
+ QVERIFY( !p1.isValid() );
+#endif
+}
diff --git a/src/modules/packagechooser/Tests.h b/src/modules/packagechooser/Tests.h
index bc257f5a5..62efe92cc 100644
--- a/src/modules/packagechooser/Tests.h
+++ b/src/modules/packagechooser/Tests.h
@@ -31,6 +31,7 @@ public:
private Q_SLOTS:
void initTestCase();
void testBogus();
+ void testAppData();
};
#endif
diff --git a/src/modules/packagechooser/images/calamares.png b/src/modules/packagechooser/images/calamares.png
new file mode 100644
index 000000000..452e4450c
Binary files /dev/null and b/src/modules/packagechooser/images/calamares.png differ
diff --git a/src/modules/packagechooser/packagechooser.conf b/src/modules/packagechooser/packagechooser.conf
index 7d60b50a4..e9b2d9329 100644
--- a/src/modules/packagechooser/packagechooser.conf
+++ b/src/modules/packagechooser/packagechooser.conf
@@ -24,8 +24,11 @@ mode: required
# 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_*). The following fields
+# Either provide the data for an item in the list (using the keys
+# below), or use existing AppData XML files as a source for the data.
+#
+# For data provided by the list: the item has an id, which is used in
+# setting the value of *packagechooser_*). The following fields
# are mandatory:
#
# - *id* ID for the product. The ID "" is special, and is used for
@@ -33,13 +36,30 @@ mode: required
# 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.
+# - *name* Human-readable name of the product. To provide translations,
+# add a *[lang]* decoration as part of the key name, e.g. `name[nl]`
+# for Dutch. The list of usable languages can be found in
+# `CMakeLists.txt` or as part of the debug output of Calamares.
+# - *description* Human-readable description. These can be translated
+# as well.
# - *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.
+#
+# For data provided by AppData XML: the item has an *appdata*
+# key which points to an AppData XML file in the local filesystem.
+# This file is parsed to provide the id (from AppData id), name
+# (from AppData name), description (from AppData description paragraphs
+# or the summary entries), and a screenshot (the defautl screenshot
+# from AppData). No package is set (but that is unused anyway).
+#
+# AppData may contain IDs that are not useful inside Calamares,
+# and the screenshot URL may be remote -- a remote URL will not
+# be loaded and the screenshot will be missing. An item with *appdata*
+# **may** specify an ID or screenshot path, as above. This will override
+# the settings from AppData.
items:
- id: ""
package: ""
@@ -58,5 +78,7 @@ items:
name: GNOME
description: GNU Networked Object Modeling Environment Desktop
screenshot: ":/images/gnome.png"
-
+ - id: calamares
+ appdata: ../io.calamares.calamares.appdata.xml
+ screenshot: ":/images/calamares.png"
diff --git a/src/modules/packagechooser/packagechooser.qrc b/src/modules/packagechooser/packagechooser.qrc
index 9212c2f93..8f211c1bb 100644
--- a/src/modules/packagechooser/packagechooser.qrc
+++ b/src/modules/packagechooser/packagechooser.qrc
@@ -3,5 +3,6 @@
images/no-selection.pngimages/kde.pngimages/gnome.png
+ images/calamares.png