Advance Wayland and KDE package bring-up

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-04-14 10:51:06 +01:00
parent 51f3c21121
commit cf12defd28
15214 changed files with 20594243 additions and 269 deletions
@@ -0,0 +1,499 @@
/*
SPDX-FileCopyrightText: 2021 Nicolas Fella <nicolas.fella@gmx.de>
SPDX-FileCopyrightText: 2021 Alexander Lohnau <alexander.lohnau@gmx.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kpluginwidget.h"
#include "kcmoduleloader.h"
#include "kpluginproxymodel.h"
#include "kpluginwidget_p.h"
#include <kcmutils_debug.h>
#include <QApplication>
#include <QCheckBox>
#include <QDialog>
#include <QDialogButtonBox>
#include <QDir>
#include <QLineEdit>
#include <QPainter>
#include <QPushButton>
#include <QSortFilterProxyModel>
#include <QStandardPaths>
#include <QStyle>
#include <QStyleOptionViewItem>
#include <QVBoxLayout>
#include <KAboutPluginDialog>
#include <KCategorizedSortFilterProxyModel>
#include <KCategorizedView>
#include <KCategoryDrawer>
#include <KLocalizedString>
#include <KPluginMetaData>
#include <KStandardGuiItem>
#include <utility>
static constexpr int s_margin = 5;
int KPluginWidgetPrivate::dependantLayoutValue(int value, int width, int totalWidth) const
{
if (listView->layoutDirection() == Qt::LeftToRight) {
return value;
}
return totalWidth - width - value;
}
KPluginWidget::KPluginWidget(QWidget *parent)
: QWidget(parent)
, d(new KPluginWidgetPrivate)
{
auto layout = new QVBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0);
// Adding content margins on a QLineEdit breaks inline actions
auto lineEditWrapper = new QWidget(this);
auto lineEditWrapperLayout = new QVBoxLayout(lineEditWrapper);
lineEditWrapperLayout->setContentsMargins(style()->pixelMetric(QStyle::PM_LayoutLeftMargin),
style()->pixelMetric(QStyle::PM_LayoutTopMargin),
style()->pixelMetric(QStyle::PM_LayoutRightMargin),
style()->pixelMetric(QStyle::PM_LayoutBottomMargin));
d->lineEdit = new QLineEdit(lineEditWrapper);
d->lineEdit->setClearButtonEnabled(true);
d->lineEdit->setPlaceholderText(i18n("Search…"));
lineEditWrapperLayout->addWidget(d->lineEdit);
d->listView = new KCategorizedView(this);
d->listView->setProperty("_breeze_borders_sides", QVariant::fromValue(QFlags{Qt::TopEdge}));
d->categoryDrawer = new KCategoryDrawer(d->listView);
d->listView->setVerticalScrollMode(QListView::ScrollPerPixel);
d->listView->setAlternatingRowColors(true);
d->listView->setCategoryDrawer(d->categoryDrawer);
d->pluginModel = new KPluginModel(this);
connect(d->pluginModel, &KPluginModel::defaulted, this, &KPluginWidget::defaulted);
connect(d->pluginModel,
&QAbstractItemModel::dataChanged,
this,
[this](const QModelIndex &topLeft, const QModelIndex & /*bottomRight*/, const QList<int> &roles) {
if (roles.contains(KPluginModel::EnabledRole)) {
Q_EMIT pluginEnabledChanged(topLeft.data(KPluginModel::IdRole).toString(), topLeft.data(KPluginModel::EnabledRole).toBool());
Q_EMIT changed(d->pluginModel->isSaveNeeded());
}
});
d->proxyModel = new KPluginProxyModel(this);
d->proxyModel->setModel(d->pluginModel);
d->listView->setModel(d->proxyModel);
d->listView->setAlternatingRowColors(true);
auto pluginDelegate = new PluginDelegate(d.get(), this);
d->listView->setItemDelegate(pluginDelegate);
d->listView->setMouseTracking(true);
d->listView->viewport()->setAttribute(Qt::WA_Hover);
connect(d->lineEdit, &QLineEdit::textChanged, d->proxyModel, [this](const QString &query) {
d->proxyModel->setProperty("query", query);
d->proxyModel->invalidate();
});
connect(pluginDelegate, &PluginDelegate::configCommitted, this, &KPluginWidget::pluginConfigSaved);
connect(pluginDelegate, &PluginDelegate::changed, this, &KPluginWidget::pluginEnabledChanged);
layout->addWidget(lineEditWrapper);
layout->addWidget(d->listView);
// When a KPluginWidget instance gets focus,
// it should pass over the focus to its child searchbar.
setFocusProxy(d->lineEdit);
}
KPluginWidget::~KPluginWidget()
{
delete d->listView->itemDelegate();
delete d->listView; // depends on some other things in d, make sure this dies first.
}
void KPluginWidget::addPlugins(const QList<KPluginMetaData> &plugins, const QString &categoryLabel)
{
d->pluginModel->addPlugins(plugins, categoryLabel);
d->proxyModel->sort(0);
}
void KPluginWidget::setConfig(const KConfigGroup &config)
{
d->pluginModel->setConfig(config);
}
void KPluginWidget::clear()
{
d->pluginModel->clear();
}
void KPluginWidget::save()
{
d->pluginModel->save();
}
void KPluginWidget::load()
{
d->pluginModel->load();
}
void KPluginWidget::defaults()
{
d->pluginModel->defaults();
}
bool KPluginWidget::isDefault() const
{
for (int i = 0, count = d->pluginModel->rowCount(); i < count; ++i) {
const QModelIndex index = d->pluginModel->index(i, 0);
if (d->pluginModel->data(index, Qt::CheckStateRole).toBool() != d->pluginModel->data(index, KPluginModel::EnabledByDefaultRole).toBool()) {
return false;
}
}
return true;
}
bool KPluginWidget::isSaveNeeded() const
{
return d->pluginModel->isSaveNeeded();
}
void KPluginWidget::setConfigurationArguments(const QVariantList &arguments)
{
d->kcmArguments = arguments;
}
QVariantList KPluginWidget::configurationArguments() const
{
return d->kcmArguments;
}
void KPluginWidget::showConfiguration(const QString &pluginId)
{
QModelIndex idx;
for (int i = 0, c = d->proxyModel->rowCount(); i < c; ++i) {
const auto currentIndex = d->proxyModel->index(i, 0);
const QString id = currentIndex.data(KPluginModel::IdRole).toString();
if (id == pluginId) {
idx = currentIndex;
break;
}
}
if (idx.isValid()) {
auto delegate = static_cast<PluginDelegate *>(d->listView->itemDelegate());
delegate->configure(idx);
} else {
qCWarning(KCMUTILS_LOG) << "Could not find plugin" << pluginId;
}
}
void KPluginWidget::setDefaultsIndicatorsVisible(bool isVisible)
{
auto delegate = static_cast<PluginDelegate *>(d->listView->itemDelegate());
delegate->resetModel();
d->showDefaultIndicator = isVisible;
}
void KPluginWidget::setAdditionalButtonHandler(const std::function<QPushButton *(const KPluginMetaData &)> &handler)
{
auto delegate = static_cast<PluginDelegate *>(d->listView->itemDelegate());
delegate->handler = handler;
}
PluginDelegate::PluginDelegate(KPluginWidgetPrivate *pluginSelector_d_ptr, QObject *parent)
: KWidgetItemDelegate(pluginSelector_d_ptr->listView, parent)
, checkBox(new QCheckBox)
, pushButton(new QPushButton)
, pluginSelector_d(pluginSelector_d_ptr)
{
// set the icon to make sure the size can be properly calculated
pushButton->setIcon(QIcon::fromTheme(QStringLiteral("configure-symbolic")));
}
PluginDelegate::~PluginDelegate()
{
delete checkBox;
delete pushButton;
}
void PluginDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
if (!index.isValid()) {
return;
}
const int xOffset = checkBox->sizeHint().width();
const bool disabled = !index.model()->data(index, KPluginModel::IsChangeableRole).toBool();
painter->save();
QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, nullptr);
const int iconSize = option.rect.height() - (s_margin * 2);
QIcon icon = QIcon::fromTheme(index.model()->data(index, Qt::DecorationRole).toString());
icon.paint(painter,
QRect(pluginSelector_d->dependantLayoutValue(s_margin + option.rect.left() + xOffset, iconSize, option.rect.width()),
s_margin + option.rect.top(),
iconSize,
iconSize));
QRect contentsRect(pluginSelector_d->dependantLayoutValue(s_margin * 2 + iconSize + option.rect.left() + xOffset,
option.rect.width() - (s_margin * 3) - iconSize - xOffset,
option.rect.width()),
s_margin + option.rect.top(),
option.rect.width() - (s_margin * 3) - iconSize - xOffset,
option.rect.height() - (s_margin * 2));
int lessHorizontalSpace = s_margin * 2 + pushButton->sizeHint().width();
if (index.model()->data(index, KPluginModel::ConfigRole).value<KPluginMetaData>().isValid()) {
lessHorizontalSpace += s_margin + pushButton->sizeHint().width();
}
// Reserve space for extra button
if (handler) {
lessHorizontalSpace += s_margin + pushButton->sizeHint().width();
}
contentsRect.setWidth(contentsRect.width() - lessHorizontalSpace);
if (option.state & QStyle::State_Selected) {
painter->setPen(option.palette.highlightedText().color());
}
if (pluginSelector_d->listView->layoutDirection() == Qt::RightToLeft) {
contentsRect.translate(lessHorizontalSpace, 0);
}
painter->save();
if (disabled) {
QPalette pal(option.palette);
pal.setCurrentColorGroup(QPalette::Disabled);
painter->setPen(pal.text().color());
}
painter->save();
QFont font = titleFont(option.font);
QFontMetrics fmTitle(font);
painter->setFont(font);
painter->drawText(contentsRect,
Qt::AlignLeft | Qt::AlignTop,
fmTitle.elidedText(index.model()->data(index, Qt::DisplayRole).toString(), Qt::ElideRight, contentsRect.width()));
painter->restore();
painter->drawText(
contentsRect,
Qt::AlignLeft | Qt::AlignBottom,
option.fontMetrics.elidedText(index.model()->data(index, KPluginModel::DescriptionRole).toString(), Qt::ElideRight, contentsRect.width()));
painter->restore();
painter->restore();
}
QSize PluginDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
int i = 5;
int j = 1;
if (index.model()->data(index, KPluginModel::ConfigRole).value<KPluginMetaData>().isValid()) {
i = 6;
j = 2;
}
// Reserve space for extra button
if (handler) {
++j;
}
const QFont font = titleFont(option.font);
const QFontMetrics fmTitle(font);
const QString text = index.model()->data(index, Qt::DisplayRole).toString();
const QString comment = index.model()->data(index, KPluginModel::DescriptionRole).toString();
const int maxTextWidth = qMax(fmTitle.boundingRect(text).width(), option.fontMetrics.boundingRect(comment).width());
const auto iconSize = pluginSelector_d->listView->style()->pixelMetric(QStyle::PM_IconViewIconSize);
return QSize(maxTextWidth + iconSize + s_margin * i + pushButton->sizeHint().width() * j,
qMax(iconSize + s_margin * 2, fmTitle.height() + option.fontMetrics.height() + s_margin * 2));
}
QList<QWidget *> PluginDelegate::createItemWidgets(const QModelIndex &index) const
{
Q_UNUSED(index);
QList<QWidget *> widgetList;
auto enabledCheckBox = new QCheckBox;
connect(enabledCheckBox, &QAbstractButton::clicked, this, &PluginDelegate::slotStateChanged);
auto aboutPushButton = new QPushButton;
aboutPushButton->setIcon(QIcon::fromTheme(QStringLiteral("help-about-symbolic")));
aboutPushButton->setToolTip(i18n("About"));
connect(aboutPushButton, &QAbstractButton::clicked, this, &PluginDelegate::slotAboutClicked);
auto configurePushButton = new QPushButton;
configurePushButton->setIcon(QIcon::fromTheme(QStringLiteral("configure-symbolic")));
configurePushButton->setToolTip(i18n("Configure"));
connect(configurePushButton, &QAbstractButton::clicked, this, &PluginDelegate::slotConfigureClicked);
const static QList<QEvent::Type> blockedEvents{
QEvent::MouseButtonPress,
QEvent::MouseButtonRelease,
QEvent::MouseButtonDblClick,
QEvent::KeyPress,
QEvent::KeyRelease,
};
setBlockedEventTypes(enabledCheckBox, blockedEvents);
setBlockedEventTypes(aboutPushButton, blockedEvents);
setBlockedEventTypes(configurePushButton, blockedEvents);
widgetList << enabledCheckBox << aboutPushButton << configurePushButton;
if (handler) {
QPushButton *btn = handler(pluginSelector_d->pluginModel->data(index, KPluginModel::MetaDataRole).value<KPluginMetaData>());
if (btn) {
widgetList << btn;
}
}
return widgetList;
}
void PluginDelegate::updateItemWidgets(const QList<QWidget *> &widgets, const QStyleOptionViewItem &option, const QPersistentModelIndex &index) const
{
int extraButtonWidth = 0;
QPushButton *extraButton = nullptr;
if (widgets.count() == 4) {
extraButton = static_cast<QPushButton *>(widgets[3]);
extraButtonWidth = extraButton->sizeHint().width() + s_margin;
}
auto checkBox = static_cast<QCheckBox *>(widgets[0]);
checkBox->resize(checkBox->sizeHint());
checkBox->move(pluginSelector_d->dependantLayoutValue(s_margin, checkBox->sizeHint().width(), option.rect.width()),
option.rect.height() / 2 - checkBox->sizeHint().height() / 2);
auto aboutPushButton = static_cast<QPushButton *>(widgets[1]);
const QSize aboutPushButtonSizeHint = aboutPushButton->sizeHint();
aboutPushButton->resize(aboutPushButtonSizeHint);
aboutPushButton->move(pluginSelector_d->dependantLayoutValue(option.rect.width() - s_margin - aboutPushButtonSizeHint.width() - extraButtonWidth,
aboutPushButtonSizeHint.width(),
option.rect.width()),
option.rect.height() / 2 - aboutPushButtonSizeHint.height() / 2);
auto configurePushButton = static_cast<QPushButton *>(widgets[2]);
const QSize configurePushButtonSizeHint = configurePushButton->sizeHint();
configurePushButton->resize(configurePushButtonSizeHint);
configurePushButton->move(pluginSelector_d->dependantLayoutValue(option.rect.width() - s_margin * 2 - configurePushButtonSizeHint.width()
- aboutPushButtonSizeHint.width() - extraButtonWidth,
configurePushButtonSizeHint.width(),
option.rect.width()),
option.rect.height() / 2 - configurePushButtonSizeHint.height() / 2);
if (extraButton) {
const QSize extraPushButtonSizeHint = extraButton->sizeHint();
extraButton->resize(extraPushButtonSizeHint);
extraButton->move(pluginSelector_d->dependantLayoutValue(option.rect.width() - extraButtonWidth, extraPushButtonSizeHint.width(), option.rect.width()),
option.rect.height() / 2 - extraPushButtonSizeHint.height() / 2);
}
if (!index.isValid() || !index.internalPointer()) {
checkBox->setVisible(false);
aboutPushButton->setVisible(false);
configurePushButton->setVisible(false);
if (extraButton) {
extraButton->setVisible(false);
}
} else {
const bool enabledByDefault = index.model()->data(index, KPluginModel::EnabledByDefaultRole).toBool();
const bool enabled = index.model()->data(index, KPluginModel::EnabledRole).toBool();
checkBox->setProperty("_kde_highlight_neutral", pluginSelector_d->showDefaultIndicator && enabledByDefault != enabled);
checkBox->setChecked(index.model()->data(index, Qt::CheckStateRole).toBool());
checkBox->setEnabled(index.model()->data(index, KPluginModel::IsChangeableRole).toBool());
configurePushButton->setVisible(index.model()->data(index, KPluginModel::ConfigRole).value<KPluginMetaData>().isValid());
configurePushButton->setEnabled(index.model()->data(index, Qt::CheckStateRole).toBool());
}
}
void PluginDelegate::slotStateChanged(bool state)
{
if (!focusedIndex().isValid()) {
return;
}
QModelIndex index = focusedIndex();
const_cast<QAbstractItemModel *>(index.model())->setData(index, state, Qt::CheckStateRole);
}
void PluginDelegate::slotAboutClicked()
{
const QModelIndex index = focusedIndex();
auto pluginMetaData = index.data(KPluginModel::MetaDataRole).value<KPluginMetaData>();
auto *aboutPlugin = new KAboutPluginDialog(pluginMetaData, itemView());
aboutPlugin->setAttribute(Qt::WA_DeleteOnClose);
aboutPlugin->show();
}
void PluginDelegate::slotConfigureClicked()
{
configure(focusedIndex());
}
void PluginDelegate::configure(const QModelIndex &index)
{
const QAbstractItemModel *model = index.model();
const auto kcm = model->data(index, KPluginModel::ConfigRole).value<KPluginMetaData>();
auto configDialog = new QDialog(itemView());
configDialog->setAttribute(Qt::WA_DeleteOnClose);
configDialog->setModal(true);
configDialog->setWindowTitle(model->data(index, KPluginModel::NameRole).toString());
QWidget *kcmWrapper = new QWidget;
auto kcmInstance = KCModuleLoader::loadModule(kcm, kcmWrapper, pluginSelector_d->kcmArguments);
auto layout = new QVBoxLayout(configDialog);
layout->addWidget(kcmWrapper);
auto buttonBox = new QDialogButtonBox(configDialog);
buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::RestoreDefaults);
KGuiItem::assign(buttonBox->button(QDialogButtonBox::Ok), KStandardGuiItem::ok());
KGuiItem::assign(buttonBox->button(QDialogButtonBox::Cancel), KStandardGuiItem::cancel());
KGuiItem::assign(buttonBox->button(QDialogButtonBox::RestoreDefaults), KStandardGuiItem::defaults());
connect(buttonBox, &QDialogButtonBox::accepted, configDialog, &QDialog::accept);
connect(buttonBox, &QDialogButtonBox::rejected, configDialog, &QDialog::reject);
connect(configDialog, &QDialog::accepted, this, [kcmInstance, this, model, index]() {
Q_EMIT configCommitted(model->data(index, KPluginModel::IdRole).toString());
kcmInstance->save();
});
connect(configDialog, &QDialog::rejected, this, [kcmInstance]() {
kcmInstance->load();
});
connect(buttonBox->button(QDialogButtonBox::RestoreDefaults), &QAbstractButton::clicked, this, [kcmInstance] {
kcmInstance->defaults();
});
layout->addWidget(buttonBox);
// Load KCM right before showing it
kcmInstance->load();
configDialog->show();
}
QFont PluginDelegate::titleFont(const QFont &baseFont) const
{
QFont retFont(baseFont);
retFont.setBold(true);
return retFont;
}
#include "moc_kpluginwidget.cpp"
#include "moc_kpluginwidget_p.cpp"