mirror of https://github.com/cutefishos/calamares
Merge branch 'issue-1573' into calamares
commit
ff66eacd0d
@ -0,0 +1,164 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2020 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "Config.h"
|
||||
|
||||
#include "PlasmaLnfJob.h"
|
||||
#include "ThemeInfo.h"
|
||||
|
||||
#include "utils/CalamaresUtilsSystem.h"
|
||||
#include "utils/Logger.h"
|
||||
#include "utils/Variant.h"
|
||||
|
||||
#ifdef WITH_KCONFIG
|
||||
#include <KConfigGroup>
|
||||
#include <KSharedConfig>
|
||||
#endif
|
||||
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
static QString
|
||||
currentPlasmaTheme()
|
||||
{
|
||||
#ifdef WITH_KCONFIG
|
||||
KConfigGroup cg( KSharedConfig::openConfig( QStringLiteral( "kdeglobals" ) ), "KDE" );
|
||||
return cg.readEntry( "LookAndFeelPackage", QString() );
|
||||
#else
|
||||
cWarning() << "No KConfig support, cannot determine Plasma theme.";
|
||||
return QString();
|
||||
#endif
|
||||
}
|
||||
|
||||
Config::Config( QObject* parent )
|
||||
: QObject( parent )
|
||||
, m_themeModel( new ThemesModel( this ) )
|
||||
{
|
||||
auto* filter = new QSortFilterProxyModel( m_themeModel );
|
||||
filter->setFilterRole( ThemesModel::ShownRole );
|
||||
filter->setFilterFixedString( QStringLiteral( "true" ) );
|
||||
filter->setSourceModel( m_themeModel );
|
||||
filter->setSortRole( ThemesModel::LabelRole );
|
||||
filter->sort( 0 );
|
||||
|
||||
m_filteredModel = filter;
|
||||
}
|
||||
|
||||
void
|
||||
Config::setConfigurationMap( const QVariantMap& configurationMap )
|
||||
{
|
||||
m_lnfPath = CalamaresUtils::getString( configurationMap, "lnftool" );
|
||||
|
||||
if ( m_lnfPath.isEmpty() )
|
||||
{
|
||||
cWarning() << "no lnftool given for plasmalnf module.";
|
||||
}
|
||||
|
||||
m_liveUser = CalamaresUtils::getString( configurationMap, "liveuser" );
|
||||
|
||||
QString preselect = CalamaresUtils::getString( configurationMap, "preselect" );
|
||||
if ( preselect == QStringLiteral( "*" ) )
|
||||
{
|
||||
preselect = currentPlasmaTheme();
|
||||
}
|
||||
m_preselectThemeId = preselect;
|
||||
|
||||
if ( configurationMap.contains( "themes" ) && configurationMap.value( "themes" ).type() == QVariant::List )
|
||||
{
|
||||
QMap< QString, QString > listedThemes;
|
||||
auto themeList = configurationMap.value( "themes" ).toList();
|
||||
// Create the ThemInfo objects for the listed themes; information
|
||||
// about the themes from Plasma (e.g. human-readable name and description)
|
||||
// are filled in by update_names() in PlasmaLnfPage.
|
||||
for ( const auto& i : themeList )
|
||||
if ( i.type() == QVariant::Map )
|
||||
{
|
||||
auto iv = i.toMap();
|
||||
listedThemes.insert( iv.value( "theme" ).toString(), iv.value( "image" ).toString() );
|
||||
}
|
||||
else if ( i.type() == QVariant::String )
|
||||
{
|
||||
listedThemes.insert( i.toString(), QString() );
|
||||
}
|
||||
|
||||
if ( listedThemes.count() == 1 )
|
||||
{
|
||||
cWarning() << "only one theme enabled in plasmalnf";
|
||||
}
|
||||
m_themeModel->setThemeImage( listedThemes );
|
||||
|
||||
bool showAll = CalamaresUtils::getBool( configurationMap, "showAll", false );
|
||||
if ( !listedThemes.isEmpty() && !showAll )
|
||||
{
|
||||
m_themeModel->showOnlyThemes( listedThemes );
|
||||
}
|
||||
}
|
||||
|
||||
m_themeModel->select( m_preselectThemeId );
|
||||
}
|
||||
|
||||
Calamares::JobList
|
||||
Config::createJobs() const
|
||||
{
|
||||
Calamares::JobList l;
|
||||
|
||||
cDebug() << "Creating Plasma LNF jobs ..";
|
||||
if ( !theme().isEmpty() )
|
||||
{
|
||||
if ( !lnfToolPath().isEmpty() )
|
||||
{
|
||||
l.append( Calamares::job_ptr( new PlasmaLnfJob( lnfToolPath(), theme() ) ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
cWarning() << "no lnftool given for plasmalnf module.";
|
||||
}
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
Config::setTheme( const QString& id )
|
||||
{
|
||||
if ( m_themeId == id )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_themeId = id;
|
||||
if ( lnfToolPath().isEmpty() )
|
||||
{
|
||||
cWarning() << "no lnftool given for plasmalnf module.";
|
||||
}
|
||||
else
|
||||
{
|
||||
QStringList command;
|
||||
if ( !m_liveUser.isEmpty() )
|
||||
{
|
||||
command << "sudo"
|
||||
<< "-E"
|
||||
<< "-H"
|
||||
<< "-u" << m_liveUser;
|
||||
}
|
||||
command << lnfToolPath() << "--resetLayout"
|
||||
<< "--apply" << id;
|
||||
auto r = CalamaresUtils::System::instance()->runCommand( command, std::chrono::seconds( 10 ) );
|
||||
|
||||
if ( r.getExitCode() )
|
||||
{
|
||||
cWarning() << "Failed (" << r.getExitCode() << ')';
|
||||
}
|
||||
else
|
||||
{
|
||||
cDebug() << "Plasma look-and-feel applied" << id;
|
||||
}
|
||||
}
|
||||
m_themeModel->select( id );
|
||||
emit themeChanged( id );
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2020 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef PLASMALNF_CONFIG_H
|
||||
#define PLASMALNF_CONFIG_H
|
||||
|
||||
#include "Job.h"
|
||||
|
||||
#include "ThemeInfo.h"
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class Config : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY( QString preselectedTheme READ preselectedTheme CONSTANT )
|
||||
Q_PROPERTY( QString theme READ theme WRITE setTheme NOTIFY themeChanged )
|
||||
Q_PROPERTY( QAbstractItemModel* themeModel READ themeModel CONSTANT )
|
||||
|
||||
public:
|
||||
Config( QObject* parent = nullptr );
|
||||
virtual ~Config() override = default; // QObject cleans up the model pointer
|
||||
|
||||
void setConfigurationMap( const QVariantMap& );
|
||||
Calamares::JobList createJobs() const;
|
||||
|
||||
/** @brief Full path to the lookandfeeltool (if it exists)
|
||||
*
|
||||
* This can be configured, or defaults to `lookandfeeltool` to find
|
||||
* it in $PATH.
|
||||
*/
|
||||
QString lnfToolPath() const { return m_lnfPath; }
|
||||
/** @brief For OEM mode, the name of the (current) live user
|
||||
*
|
||||
*/
|
||||
QString liveUser() const { return m_liveUser; }
|
||||
|
||||
/** @brief The id (in reverse-DNS notation) of the current theme.
|
||||
*/
|
||||
QString theme() const { return m_themeId; }
|
||||
|
||||
/** @brief The theme we start with
|
||||
*
|
||||
* This can be configured, or is taken from the live environment
|
||||
* if the environment is (also) KDE Plasma.
|
||||
*/
|
||||
QString preselectedTheme() const { return m_preselectThemeId; }
|
||||
|
||||
/** @brief The (list) model of available themes.
|
||||
*/
|
||||
QAbstractItemModel* themeModel() const { return m_filteredModel; }
|
||||
|
||||
public slots:
|
||||
void setTheme( const QString& id );
|
||||
|
||||
signals:
|
||||
void themeChanged( const QString& id );
|
||||
|
||||
private:
|
||||
QString m_lnfPath; // Path to the lnf tool
|
||||
QString m_liveUser; // Name of the live user (for OEM mode)
|
||||
|
||||
QString m_preselectThemeId;
|
||||
QString m_themeId; // Id of selected theme
|
||||
|
||||
QAbstractItemModel* m_filteredModel = nullptr;
|
||||
ThemesModel* m_themeModel = nullptr;
|
||||
};
|
||||
|
||||
#endif
|
@ -0,0 +1,319 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2020 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
#include "ThemeInfo.h"
|
||||
|
||||
#include "Branding.h"
|
||||
#include "utils/CalamaresUtilsGui.h"
|
||||
#include "utils/Logger.h"
|
||||
|
||||
#include <KPackage/Package>
|
||||
#include <KPackage/PackageLoader>
|
||||
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QPixmap>
|
||||
|
||||
/** @brief describes a single plasma LnF theme.
|
||||
*
|
||||
* A theme description has an id, which is really the name of the desktop
|
||||
* file (e.g. org.kde.breeze.desktop), a name which is human-readable and
|
||||
* translated, and an optional image Page, which points to a local screenshot
|
||||
* of that theme.
|
||||
*/
|
||||
struct ThemeInfo
|
||||
{
|
||||
QString id;
|
||||
QString name;
|
||||
QString description;
|
||||
QString imagePath;
|
||||
mutable QPixmap pixmap;
|
||||
bool show = true;
|
||||
bool selected = false;
|
||||
|
||||
ThemeInfo() {}
|
||||
|
||||
explicit ThemeInfo( const QString& _id )
|
||||
: id( _id )
|
||||
{
|
||||
}
|
||||
|
||||
explicit ThemeInfo( const QString& _id, const QString& image )
|
||||
: id( _id )
|
||||
, imagePath( image )
|
||||
{
|
||||
}
|
||||
|
||||
explicit ThemeInfo( const KPluginMetaData& );
|
||||
|
||||
bool isValid() const { return !id.isEmpty(); }
|
||||
|
||||
/// @brief Fill in the pixmap member based on imagePath
|
||||
QPixmap loadImage() const;
|
||||
};
|
||||
|
||||
class ThemeInfoList : public QList< ThemeInfo >
|
||||
{
|
||||
public:
|
||||
std::pair< int, const ThemeInfo* > indexById( const QString& id ) const
|
||||
{
|
||||
int index = 0;
|
||||
for ( const ThemeInfo& i : *this )
|
||||
{
|
||||
if ( i.id == id )
|
||||
{
|
||||
return { index, &i };
|
||||
}
|
||||
}
|
||||
return { -1, nullptr };
|
||||
}
|
||||
|
||||
std::pair< int, ThemeInfo* > indexById( const QString& id )
|
||||
{
|
||||
// Call the const version and then munge the types
|
||||
auto [ i, p ] = const_cast< const ThemeInfoList* >( this )->indexById( id );
|
||||
return { i, const_cast< ThemeInfo* >( p ) };
|
||||
}
|
||||
|
||||
|
||||
/** @brief Looks for a given @p id in the list of themes, returns nullptr if not found. */
|
||||
ThemeInfo* findById( const QString& id )
|
||||
{
|
||||
auto [ i, p ] = indexById( id );
|
||||
return p;
|
||||
}
|
||||
|
||||
/** @brief Looks for a given @p id in the list of themes, returns nullptr if not found. */
|
||||
const ThemeInfo* findById( const QString& id ) const
|
||||
{
|
||||
auto [ i, p ] = indexById( id );
|
||||
return p;
|
||||
}
|
||||
|
||||
/** @brief Checks if a given @p id is in the list of themes. */
|
||||
bool contains( const QString& id ) const { return findById( id ) != nullptr; }
|
||||
};
|
||||
|
||||
ThemesModel::ThemesModel( QObject* parent )
|
||||
: QAbstractListModel( parent )
|
||||
, m_themes( new ThemeInfoList )
|
||||
{
|
||||
auto packages = KPackage::PackageLoader::self()->listPackages( "Plasma/LookAndFeel" );
|
||||
m_themes->reserve( packages.length() );
|
||||
|
||||
for ( const auto& p : packages )
|
||||
{
|
||||
m_themes->append( ThemeInfo { p } );
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
ThemesModel::rowCount( const QModelIndex& ) const
|
||||
{
|
||||
return m_themes->count();
|
||||
}
|
||||
|
||||
QVariant
|
||||
ThemesModel::data( const QModelIndex& index, int role ) const
|
||||
{
|
||||
if ( !index.isValid() )
|
||||
{
|
||||
return QVariant();
|
||||
}
|
||||
if ( index.row() < 0 || index.row() >= m_themes->count() )
|
||||
{
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
const auto& item = m_themes->at( index.row() );
|
||||
switch ( role )
|
||||
{
|
||||
case LabelRole:
|
||||
return item.name;
|
||||
case KeyRole:
|
||||
return item.id;
|
||||
case ShownRole:
|
||||
return item.show;
|
||||
case SelectedRole:
|
||||
return item.selected;
|
||||
case DescriptionRole:
|
||||
return item.description;
|
||||
case ImageRole:
|
||||
return item.loadImage();
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
__builtin_unreachable();
|
||||
}
|
||||
|
||||
QHash< int, QByteArray >
|
||||
ThemesModel::roleNames() const
|
||||
{
|
||||
return { { LabelRole, "label" },
|
||||
{ KeyRole, "key" },
|
||||
{ SelectedRole, "selected" },
|
||||
{ ShownRole, "show" },
|
||||
{ ImageRole, "image" } };
|
||||
}
|
||||
|
||||
void
|
||||
ThemesModel::setThemeImage( const QString& id, const QString& imagePath )
|
||||
{
|
||||
auto [ i, theme ] = m_themes->indexById( id );
|
||||
if ( theme )
|
||||
{
|
||||
theme->imagePath = imagePath;
|
||||
emit dataChanged( index( i, 0 ), index( i, 0 ), { ImageRole } );
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ThemesModel::setThemeImage( const QMap< QString, QString >& images )
|
||||
{
|
||||
if ( m_themes->isEmpty() )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't emit signals from each call, aggregate to one call (below this block)
|
||||
{
|
||||
QSignalBlocker b( this );
|
||||
for ( auto k = images.constKeyValueBegin(); k != images.constKeyValueEnd(); ++k )
|
||||
{
|
||||
setThemeImage( k->first, k->second );
|
||||
}
|
||||
}
|
||||
emit dataChanged( index( 0, 0 ), index( m_themes->count() - 1 ), { ImageRole } );
|
||||
}
|
||||
|
||||
void
|
||||
ThemesModel::showTheme( const QString& id, bool show )
|
||||
{
|
||||
auto [ i, theme ] = m_themes->indexById( id );
|
||||
if ( theme )
|
||||
{
|
||||
theme->show = show;
|
||||
emit dataChanged( index( i, 0 ), index( i, 0 ), { ShownRole } );
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ThemesModel::showOnlyThemes( const QMap< QString, QString >& onlyThese )
|
||||
{
|
||||
if ( m_themes->isEmpty() )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// No signal blocker block needed here because we're not calling showTheme()
|
||||
// QSignalBlocker b( this );
|
||||
for ( auto& t : *m_themes )
|
||||
{
|
||||
t.show = onlyThese.contains( t.id );
|
||||
}
|
||||
emit dataChanged( index( 0, 0 ), index( m_themes->count() - 1 ), { ShownRole } );
|
||||
}
|
||||
|
||||
QSize
|
||||
ThemesModel::imageSize()
|
||||
{
|
||||
return { qMax( 12 * CalamaresUtils::defaultFontHeight(), 120 ),
|
||||
qMax( 8 * CalamaresUtils::defaultFontHeight(), 80 ) };
|
||||
}
|
||||
|
||||
void
|
||||
ThemesModel::select( const QString& themeId )
|
||||
{
|
||||
int i = 0;
|
||||
for ( auto& t : *m_themes )
|
||||
{
|
||||
if ( t.selected && t.id != themeId )
|
||||
{
|
||||
t.selected = false;
|
||||
emit dataChanged( index( i, 0 ), index( i, 0 ), { SelectedRole } );
|
||||
}
|
||||
if ( !t.selected && t.id == themeId )
|
||||
{
|
||||
t.selected = true;
|
||||
emit dataChanged( index( i, 0 ), index( i, 0 ), { SelectedRole } );
|
||||
}
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Massage the given @p path to the most-likely
|
||||
* path that actually contains a screenshot. For
|
||||
* empty image paths, returns the QRC path for an
|
||||
* empty screenshot. Returns blank if the path
|
||||
* doesn't exist anywhere in the search paths.
|
||||
*/
|
||||
static QString
|
||||
munge_imagepath( const QString& path )
|
||||
{
|
||||
if ( path.isEmpty() )
|
||||
{
|
||||
return ":/view-preview.png";
|
||||
}
|
||||
|
||||
if ( path.startsWith( '/' ) )
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
if ( QFileInfo::exists( path ) )
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
QFileInfo fi( QDir( Calamares::Branding::instance()->componentDirectory() ), path );
|
||||
if ( fi.exists() )
|
||||
{
|
||||
return fi.absoluteFilePath();
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
ThemeInfo::ThemeInfo( const KPluginMetaData& data )
|
||||
: id( data.pluginId() )
|
||||
, name( data.name() )
|
||||
, description( data.description() )
|
||||
{
|
||||
}
|
||||
|
||||
QPixmap
|
||||
ThemeInfo::loadImage() const
|
||||
{
|
||||
if ( pixmap.isNull() )
|
||||
{
|
||||
|
||||
const QSize image_size( ThemesModel::imageSize() );
|
||||
|
||||
const QString path = munge_imagepath( imagePath );
|
||||
cDebug() << "Loading initial image for" << id << imagePath << "->" << path;
|
||||
QPixmap image( path );
|
||||
if ( image.isNull() )
|
||||
{
|
||||
// Not found or not specified, so convert the name into some (horrible, likely)
|
||||
// color instead.
|
||||
image = QPixmap( image_size );
|
||||
auto hash_color = qHash( imagePath.isEmpty() ? id : imagePath );
|
||||
cDebug() << Logger::SubEntry << "Theme image" << imagePath << "not found, hash" << hash_color;
|
||||
image.fill( QColor( QRgb( hash_color ) ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
cDebug() << Logger::SubEntry << "Theme image" << image.size();
|
||||
}
|
||||
|
||||
pixmap = image.scaled( image_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation );
|
||||
}
|
||||
return pixmap;
|
||||
}
|
Loading…
Reference in New Issue