Merge branch 'issue-1573' into calamares

main
Adriaan de Groot 4 years ago
commit ff66eacd0d

@ -139,32 +139,33 @@ public:
RunInTarget
};
/**
* Runs the specified command in the chroot of the target system.
* @param args the command with arguments, as a string list.
* @param workingPath the current working directory for the QProcess
* call (optional).
* @param stdInput the input string to send to the running process as
* standard input (optional).
* @param timeoutSec the timeout after which the process will be
* killed (optional, default is 0 i.e. no timeout).
*
* @returns the program's exit code and its output (if any). Special
* exit codes (which will never have any output) are:
* Crashed = QProcess crash
* FailedToStart = QProcess cannot start
* NoWorkingDirectory = bad arguments
* TimedOut = QProcess timeout
*/
/** @brief Runs a command in the host or the target (select explicitly)
*
* @param location whether to run in the host or the target
* @param args the command with arguments, as a string list.
* @param workingPath the current working directory for the QProcess
* call (optional).
* @param stdInput the input string to send to the running process as
* standard input (optional).
* @param timeoutSec the timeout after which the process will be
* killed (optional, default is 0 i.e. no timeout).
*
* @returns the program's exit code and its output (if any). Special
* exit codes (which will never have any output) are:
* Crashed = QProcess crash
* FailedToStart = QProcess cannot start
* NoWorkingDirectory = bad arguments
* TimedOut = QProcess timeout
*/
static DLLEXPORT ProcessResult runCommand( RunLocation location,
const QStringList& args,
const QString& workingPath = QString(),
const QString& stdInput = QString(),
std::chrono::seconds timeoutSec = std::chrono::seconds( 0 ) );
/** @brief Convenience wrapper for runCommand()
/** @brief Convenience wrapper for runCommand() in the host
*
* Runs the given command-line @p args in the host in the current direcory
* Runs the given command-line @p args in the **host** in the current direcory
* with no input, and the given @p timeoutSec for completion.
*/
static inline ProcessResult runCommand( const QStringList& args, std::chrono::seconds timeoutSec )
@ -173,10 +174,11 @@ public:
}
/** @brief Convenience wrapper for runCommand().
* Runs the command in the location specified through the boolean
* doChroot(), which is what you usually want for running commands
* during installation.
*/
*
* Runs the command in the location specified through the boolean
* doChroot(), which is what you usually want for running commands
* during installation.
*/
inline ProcessResult targetEnvCommand( const QStringList& args,
const QString& workingPath = QString(),
const QString& stdInput = QString(),

@ -10,16 +10,18 @@ find_package(ECM ${ECM_VERSION} REQUIRED NO_MODULE)
set( lnf_ver 5.41 )
find_package( KF5Config ${lnf_ver} )
find_package( KF5Plasma ${lnf_ver} )
find_package( KF5Package ${lnf_ver} )
set_package_properties(
KF5Config PROPERTIES
PURPOSE "For finding default Plasma Look-and-Feel"
)
find_package( KF5Plasma ${lnf_ver} )
set_package_properties(
KF5Plasma PROPERTIES
PURPOSE "For Plasma Look-and-Feel selection"
)
find_package( KF5Package ${lnf_ver} )
set_package_properties(
KF5Package PROPERTIES
PURPOSE "For Plasma Look-and-Feel selection"
@ -32,10 +34,11 @@ if ( KF5Plasma_FOUND AND KF5Package_FOUND )
COMPILE_DEFINITIONS
${option_defs}
SOURCES
Config.cpp
PlasmaLnfViewStep.cpp
PlasmaLnfPage.cpp
PlasmaLnfJob.cpp
ThemeWidget.cpp
ThemeInfo.cpp
RESOURCES
page_plasmalnf.qrc
UI

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

@ -10,49 +10,67 @@
#include "PlasmaLnfPage.h"
#include "Config.h"
#include "ui_page_plasmalnf.h"
#include "Settings.h"
#include "utils/Logger.h"
#include "utils/Retranslator.h"
#include <QAbstractButton>
#include <QHeaderView>
#include <QListView>
#include <QStyledItemDelegate>
#include <KPackage/Package>
#include <KPackage/PackageLoader>
class ThemeDelegate : public QStyledItemDelegate
{
public:
using QStyledItemDelegate::QStyledItemDelegate;
void paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const override;
// The size of the item is constant
QSize sizeHint( const QStyleOptionViewItem&, const QModelIndex& ) const override;
};
ThemeInfo::ThemeInfo( const KPluginMetaData& data )
: id( data.pluginId() )
, name( data.name() )
, description( data.description() )
, widget( nullptr )
QSize
ThemeDelegate::sizeHint( const QStyleOptionViewItem&, const QModelIndex& ) const
{
QSize image( ThemesModel::imageSize() );
return { 3 * image.width(), image.height() };
}
static ThemeInfoList
plasma_themes()
void
ThemeDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const
{
ThemeInfoList packages;
QList< KPluginMetaData > pkgs = KPackage::PackageLoader::self()->listPackages( "Plasma/LookAndFeel" );
for ( const KPluginMetaData& data : pkgs )
{
if ( data.isValid() && !data.isHidden() && !data.name().isEmpty() )
{
packages << ThemeInfo { data };
}
}
return packages;
auto label = index.data( ThemesModel::LabelRole ).toString();
auto description = index.data( ThemesModel::DescriptionRole ).toString();
auto selected = index.data( ThemesModel::SelectedRole ).toBool() ? QStyle::State_On : QStyle::State_Off;
auto image_v = index.data( ThemesModel::ImageRole );
QPixmap image = image_v.canConvert< QPixmap >() ? qvariant_cast< QPixmap >( image_v ) : QPixmap();
// The delegate paints three "columns", each of which takes 1/3
// of the space: label, description and screenshot.
QRect labelRect( option.rect );
labelRect.setWidth( labelRect.width() / 3 );
QStyleOptionButton rbOption;
rbOption.state |= QStyle::State_Enabled | selected;
rbOption.rect = labelRect;
rbOption.text = label;
option.widget->style()->drawControl( QStyle::CE_RadioButton, &rbOption, painter, option.widget );
labelRect.moveLeft( labelRect.width() );
option.widget->style()->drawItemText(
painter, labelRect, Qt::AlignLeft | Qt::AlignVCenter | Qt::TextWordWrap, option.palette, false, description );
labelRect.moveLeft( 2 * labelRect.width() );
option.widget->style()->drawItemPixmap( painter, labelRect, Qt::AlignHCenter | Qt::AlignVCenter, image );
}
PlasmaLnfPage::PlasmaLnfPage( QWidget* parent )
PlasmaLnfPage::PlasmaLnfPage( Config* config, QWidget* parent )
: QWidget( parent )
, ui( new Ui::PlasmaLnfPage )
, m_showAll( false )
, m_buttonGroup( nullptr )
, m_config( config )
{
ui->setupUi( this );
CALAMARES_RETRANSLATE( {
@ -67,134 +85,28 @@ PlasmaLnfPage::PlasmaLnfPage( QWidget* parent )
"You can also skip this step and configure the look-and-feel "
"once the system is installed. Clicking on a look-and-feel "
"selection will give you a live preview of that look-and-feel." ) );
updateThemeNames();
fillUi();
} )
}
void
PlasmaLnfPage::setLnfPath( const QString& path )
{
m_lnfPath = path;
}
void
PlasmaLnfPage::setEnabledThemes( const ThemeInfoList& themes, bool showAll )
{
m_enabledThemes = themes;
if ( showAll )
{
auto plasmaThemes = plasma_themes();
for ( auto& installed_theme : plasmaThemes )
if ( !m_enabledThemes.findById( installed_theme.id ) )
{
m_enabledThemes.append( installed_theme );
}
}
updateThemeNames();
winnowThemes();
fillUi();
}
void
PlasmaLnfPage::setEnabledThemesAll()
{
// Don't need to set showAll=true, because we're already passing in
// the complete list of installed themes.
setEnabledThemes( plasma_themes(), false );
}
void
PlasmaLnfPage::setPreselect( const QString& id )
{
m_preselect = id;
if ( !m_enabledThemes.isEmpty() )
{
fillUi();
}
}
void
PlasmaLnfPage::updateThemeNames()
{
auto plasmaThemes = plasma_themes();
for ( auto& enabled_theme : m_enabledThemes )
{
ThemeInfo* t = plasmaThemes.findById( enabled_theme.id );
if ( t != nullptr )
{
enabled_theme.name = t->name;
enabled_theme.description = t->description;
}
}
}
void
PlasmaLnfPage::winnowThemes()
{
auto plasmaThemes = plasma_themes();
bool winnowed = true;
int winnow_index = 0;
while ( winnowed )
{
winnowed = false;
winnow_index = 0;
for ( auto& enabled_theme : m_enabledThemes )
{
ThemeInfo* t = plasmaThemes.findById( enabled_theme.id );
if ( t == nullptr )
{
cDebug() << "Removing" << enabled_theme.id;
winnowed = true;
break;
}
++winnow_index;
}
if ( winnowed )
{
m_enabledThemes.removeAt( winnow_index );
}
}
}
void
PlasmaLnfPage::fillUi()
{
if ( m_enabledThemes.isEmpty() )
{
return;
}
if ( !m_buttonGroup )
{
m_buttonGroup = new QButtonGroup( this );
m_buttonGroup->setExclusive( true );
}
int c = 1; // After the general explanation
for ( auto& theme : m_enabledThemes )
{
if ( !theme.widget )
{
ThemeWidget* w = new ThemeWidget( theme );
m_buttonGroup->addButton( w->button() );
ui->verticalLayout->insertWidget( c, w );
connect( w, &ThemeWidget::themeSelected, this, &PlasmaLnfPage::plasmaThemeSelected );
theme.widget = w;
}
else
{
theme.widget->updateThemeName( theme );
}
if ( theme.id == m_preselect )
{
const QSignalBlocker b( theme.widget->button() );
theme.widget->button()->setChecked( true );
}
++c;
}
auto* view = new QListView( this );
view->setModel( m_config->themeModel() );
view->setItemDelegate( new ThemeDelegate( view ) );
view->setUniformItemSizes( true );
view->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
ui->verticalLayout->addWidget( view );
connect( view->selectionModel(),
&QItemSelectionModel::selectionChanged,
[this]( const QItemSelection& selected, const QItemSelection& ) {
auto i = selected.indexes();
if ( !i.isEmpty() )
{
auto row = i.first().row();
auto* model = m_config->themeModel();
auto id = model->data( model->index( row, 0 ), ThemesModel::KeyRole ).toString();
if ( !id.isEmpty() )
{
m_config->setTheme( id );
}
}
} );
}

@ -24,6 +24,8 @@ namespace Ui
class PlasmaLnfPage;
}
class Config;
/** @brief Page for selecting a Plasma Look-and-Feel theme.
*
* You must call setEnabledThemes -- either overload -- once
@ -34,40 +36,11 @@ class PlasmaLnfPage : public QWidget
{
Q_OBJECT
public:
explicit PlasmaLnfPage( QWidget* parent = nullptr );
void setLnfPath( const QString& path );
/** @brief enable only the listed themes.
*
* Shows the listed @p themes with full information (e.g. screenshot).
* If @p showAll is true, then also show all installed themes
* not explicitly listed (without a screenshot).
*/
void setEnabledThemes( const ThemeInfoList& themes, bool showAll );
/** @brief enable all installed plasma themes. */
void setEnabledThemesAll();
/** @brief set which theme is to be preselected. */
void setPreselect( const QString& id );
signals:
void plasmaThemeSelected( const QString& id );
explicit PlasmaLnfPage( Config* config, QWidget* parent = nullptr );
private:
/** @brief Intersect the list of enabled themes with the installed ones. */
void winnowThemes();
/** @brief Get the translated names for all enabled themes. */
void updateThemeNames();
/** @brief show enabled themes in the UI. */
void fillUi();
Ui::PlasmaLnfPage* ui;
QString m_lnfPath;
QString m_preselect;
bool m_showAll; // If true, don't winnow according to enabledThemes
ThemeInfoList m_enabledThemes;
QButtonGroup* m_buttonGroup;
QList< ThemeWidget* > m_widgets;
Config* m_config;
};
#endif //PLASMALNFPAGE_H

@ -8,40 +8,22 @@
*/
#include "PlasmaLnfViewStep.h"
#include "PlasmaLnfJob.h"
#include "Config.h"
#include "PlasmaLnfPage.h"
#include "ThemeInfo.h"
#include "utils/Logger.h"
#include "utils/Variant.h"
#include <QProcess>
#include <QVariantMap>
#ifdef WITH_KCONFIG
#include <KConfigGroup>
#include <KSharedConfig>
#endif
CALAMARES_PLUGIN_FACTORY_DEFINITION( PlasmaLnfViewStepFactory, registerPlugin< PlasmaLnfViewStep >(); )
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
}
PlasmaLnfViewStep::PlasmaLnfViewStep( QObject* parent )
: Calamares::ViewStep( parent )
, m_widget( new PlasmaLnfPage )
, m_config( new Config( this ) )
, m_widget( new PlasmaLnfPage( m_config ) )
{
connect( m_widget, &PlasmaLnfPage::plasmaThemeSelected, this, &PlasmaLnfViewStep::themeSelected );
emit nextStatusChanged( false );
}
@ -106,112 +88,12 @@ PlasmaLnfViewStep::onLeave()
Calamares::JobList
PlasmaLnfViewStep::jobs() const
{
Calamares::JobList l;
cDebug() << "Creating Plasma LNF jobs ..";
if ( !m_themeId.isEmpty() )
{
if ( !m_lnfPath.isEmpty() )
{
l.append( Calamares::job_ptr( new PlasmaLnfJob( m_lnfPath, m_themeId ) ) );
}
else
{
cWarning() << "no lnftool given for plasmalnf module.";
}
}
return l;
return m_config->createJobs();
}
void
PlasmaLnfViewStep::setConfigurationMap( const QVariantMap& configurationMap )
{
m_lnfPath = CalamaresUtils::getString( configurationMap, "lnftool" );
m_widget->setLnfPath( m_lnfPath );
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();
}
if ( !preselect.isEmpty() )
{
m_widget->setPreselect( preselect );
}
bool showAll = CalamaresUtils::getBool( configurationMap, "showAll", false );
if ( configurationMap.contains( "themes" ) && configurationMap.value( "themes" ).type() == QVariant::List )
{
ThemeInfoList 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.append( ThemeInfo( iv.value( "theme" ).toString(), iv.value( "image" ).toString() ) );
}
else if ( i.type() == QVariant::String )
{
listedThemes.append( ThemeInfo( i.toString() ) );
}
if ( listedThemes.length() == 1 )
{
cWarning() << "only one theme enabled in plasmalnf";
}
m_widget->setEnabledThemes( listedThemes, showAll );
}
else
{
m_widget->setEnabledThemesAll(); // All of them
}
}
void
PlasmaLnfViewStep::themeSelected( const QString& id )
{
m_themeId = id;
if ( m_lnfPath.isEmpty() )
{
cWarning() << "no lnftool given for plasmalnf module.";
return;
}
QProcess lnftool;
if ( !m_liveUser.isEmpty() )
lnftool.start( "sudo", { "-E", "-H", "-u", m_liveUser, m_lnfPath, "--resetLayout", "--apply", id } );
else
lnftool.start( m_lnfPath, { "--resetLayout", "--apply", id } );
if ( !lnftool.waitForStarted( 1000 ) )
{
cWarning() << "could not start look-and-feel" << m_lnfPath;
return;
}
if ( !lnftool.waitForFinished() )
{
cWarning() << m_lnfPath << "timed out.";
return;
}
if ( ( lnftool.exitCode() == 0 ) && ( lnftool.exitStatus() == QProcess::NormalExit ) )
{
cDebug() << "Plasma look-and-feel applied" << id;
}
else
{
cWarning() << "could not apply look-and-feel" << id;
}
m_config->setConfigurationMap( configurationMap );
}

@ -14,6 +14,7 @@
#include "utils/PluginFactory.h"
#include "viewpages/ViewStep.h"
class Config;
class PlasmaLnfPage;
class PLUGINDLLEXPORT PlasmaLnfViewStep : public Calamares::ViewStep
@ -40,14 +41,9 @@ public:
void setConfigurationMap( const QVariantMap& configurationMap ) override;
public slots:
void themeSelected( const QString& id );
private:
Config* m_config;
PlasmaLnfPage* m_widget;
QString m_lnfPath; // Path to the lnf tool
QString m_themeId; // Id of selected theme
QString m_liveUser; // Name of the live user (for OEM mode)
};
CALAMARES_PLUGIN_FACTORY_DECLARATION( PlasmaLnfViewStepFactory )

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

@ -10,82 +10,66 @@
#ifndef PLASMALNF_THEMEINFO_H
#define PLASMALNF_THEMEINFO_H
#include <QAbstractListModel>
#include <QList>
#include <QString>
class KPluginMetaData;
class ThemeWidget;
class ThemeInfoList;
/** @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
class ThemesModel : public QAbstractListModel
{
QString id;
QString name;
QString description;
QString imagePath;
ThemeWidget* widget;
ThemeInfo()
: widget( nullptr )
{
}
Q_OBJECT
explicit ThemeInfo( const QString& _id )
: id( _id )
, widget( nullptr )
public:
enum
{
}
LabelRole = Qt::DisplayRole,
KeyRole = Qt::UserRole,
ShownRole, // Should theme be displayed
SelectedRole, // Is theme selected
DescriptionRole,
ImageRole
};
explicit ThemeInfo( const QString& _id, const QString& image )
: id( _id )
, imagePath( image )
, widget( nullptr )
{
}
explicit ThemesModel( QObject* parent );
// Defined in PlasmaLnfPage.cpp
explicit ThemeInfo( const KPluginMetaData& );
int rowCount( const QModelIndex& = QModelIndex() ) const override;
QVariant data( const QModelIndex& index, int role ) const override;
bool isValid() const { return !id.isEmpty(); }
};
QHash< int, QByteArray > roleNames() const override;
class ThemeInfoList : public QList< ThemeInfo >
{
public:
/** @brief Looks for a given @p id in the list of themes, returns nullptr if not found. */
ThemeInfo* findById( const QString& id )
{
for ( ThemeInfo& i : *this )
{
if ( i.id == id )
{
return &i;
}
}
return nullptr;
}
/** @brief Looks for a given @p id in the list of themes, returns nullptr if not found. */
const ThemeInfo* findById( const QString& id ) const
{
for ( const ThemeInfo& i : *this )
{
if ( i.id == id )
{
return &i;
}
}
return nullptr;
}
/** @brief Checks if a given @p id is in the list of themes. */
bool contains( const QString& id ) const { return findById( id ) != nullptr; }
/// @brief Set the screenshot to go with the given @p id
void setThemeImage( const QString& id, const QString& imagePath );
/// @brief Call setThemeImage( key, value ) for all keys in @p images
void setThemeImage( const QMap< QString, QString >& images );
/// @brief Set whether to show the given theme @p id (or not)
void showTheme( const QString& id, bool show = true );
/// @brief Shows the keys in the @p onlyThese map, and hides the rest
void showOnlyThemes( const QMap< QString, QString >& onlyThese );
/** @brief Mark the @p themeId as current / selected
*
* One theme can be selected at a time; this will emit data
* changed signals for any (one) theme already selected, and
* the newly-selected theme. If @p themeId does not name any
* theme, none are selected.
*/
void select( const QString& themeId );
/** @brief The size of theme Images
*
* The size is dependent on the font size used by Calamares,
* and is constant within one run of Calamares, but may change
* if the font settings do between runs.
*/
static QSize imageSize();
private:
ThemeInfoList* m_themes;
};
#endif

@ -28,19 +28,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources>

@ -24,10 +24,18 @@ lnftool: "/usr/bin/lookandfeeltool"
#
# liveuser: "live"
# If *showAll* is true, then all installed themes are shown in the
# UI for selection, even if they are not listed in *themes* (below).
# This allows selection of all themes even while not all of them are
# listed in *themes* -- which is useful to show screenshots for those
# you do have a screenshot for. If *themes* is empty or missing,
# the value of *showAll* is treated as `true`.
showAll: false
# You can limit the list of Plasma look-and-feel themes by listing ids
# here. If this key is not present, all of the installed themes are listed.
# If the key is present, only installed themes that are **also** included
# in the list are shown (could be none!). See the *showAll* key, below,
# in the list are shown (could be none!). See the *showAll* key, above,
# to change that.
#
# Themes may be listed by id, (e.g. fluffy-bunny, below) or as a theme
@ -58,13 +66,6 @@ themes:
image: "breeze-dark.png"
- org.kde.fluffy-bunny.desktop
# If *showAll* is true, then all installed themes are shown in the
# UI for selection, even if they are not listed in *themes*. This
# allows selection of all themes even while not all of them are
# listed in *themes* -- which is useful to show screenshots for those
# you do have a screenshot for.
showAll: false
# You can pre-select one of the themes; it is not applied
# immediately, but its radio-button is switched on to indicate
# that that is the theme (that is most likely) currently in use.

Loading…
Cancel
Save