diff --git a/src/modules/packagechooser/CMakeLists.txt b/src/modules/packagechooser/CMakeLists.txt index ec3c15826..e93a5816f 100644 --- a/src/modules/packagechooser/CMakeLists.txt +++ b/src/modules/packagechooser/CMakeLists.txt @@ -1,5 +1,6 @@ find_package( Qt5 COMPONENTS Core Gui Widgets REQUIRED ) set( _extra_libraries "" ) +set( _extra_src "" ) ### OPTIONAL AppData XML support in PackageModel # @@ -8,6 +9,7 @@ find_package(Qt5 COMPONENTS Xml) if ( Qt5Xml_FOUND ) add_definitions( -DHAVE_XML ) list( APPEND _extra_libraries Qt5::Xml ) + list( APPEND _extra_src ItemAppData.cpp ) endif() find_package(AppStreamQt) @@ -30,6 +32,7 @@ calamares_add_plugin( packagechooser PackageChooserPage.cpp PackageChooserViewStep.cpp PackageModel.cpp + ${_extra_src} RESOURCES packagechooser.qrc UI diff --git a/src/modules/packagechooser/ItemAppData.cpp b/src/modules/packagechooser/ItemAppData.cpp new file mode 100644 index 000000000..7c80a1c3d --- /dev/null +++ b/src/modules/packagechooser/ItemAppData.cpp @@ -0,0 +1,234 @@ +/* === This file is part of Calamares - === + * + * Copyright 2019, Adriaan de Groot + * + * 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 . + */ + +/** @brief Loading items from AppData XML files. + * + * Only used if QtXML is found, implements PackageItem::fromAppData(). + */ +#include "PackageModel.h" + +#include "utils/Logger.h" +#include "utils/Variant.h" + +#include +#include +#include + +/** @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; +} + +PackageItem +PackageItem::fromAppData( const QVariantMap& item_map ) +{ + 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(); +} diff --git a/src/modules/packagechooser/PackageModel.cpp b/src/modules/packagechooser/PackageModel.cpp index 59c6973ba..e15559552 100644 --- a/src/modules/packagechooser/PackageModel.cpp +++ b/src/modules/packagechooser/PackageModel.cpp @@ -21,12 +21,6 @@ #include "utils/Logger.h" #include "utils/Variant.h" -#ifdef HAVE_XML -#include -#include -#include -#endif - const NamedEnumTable< PackageChooserMode >& roleNames() { @@ -94,217 +88,15 @@ PackageItem::PackageItem::PackageItem( const QVariantMap& item_map ) } } -#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 +#ifndef HAVE_XML 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 } +#endif PackageListModel::PackageListModel( QObject* parent )