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,501 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2020 Ahmad Samir <a.samirh78@gmail.com>
SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "kmessagedialog.h"
#include "kmessagebox_p.h"
#include "loggingcategory.h"
#include <QApplication>
#include <QCheckBox>
#include <QDebug>
#include <QDialogButtonBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QListWidget>
#include <QPushButton>
#include <QScreen>
#include <QScrollArea>
#include <QScrollBar>
#include <QStyle>
#include <QStyleOption>
#include <QTextBrowser>
#include <QVBoxLayout>
#include <QWindow>
#include <KCollapsibleGroupBox>
#include <KSqueezedTextLabel>
static const Qt::TextInteractionFlags s_textFlags = Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse | Qt::LinksAccessibleByKeyboard;
// TODO KF6 remove QObject inheritance again
class KMessageDialogPrivate : public QObject
{
Q_OBJECT
public:
explicit KMessageDialogPrivate(KMessageDialog::Type type, KMessageDialog *qq)
: m_type(type)
, q(qq)
{
}
KMessageDialog::Type m_type;
KMessageDialog *const q;
QVBoxLayout *m_topLayout = nullptr;
QWidget *m_mainWidget = nullptr;
QLabel *m_iconLabel = nullptr;
QLabel *m_messageLabel = nullptr;
QListWidget *m_listWidget = nullptr;
QLabel *m_detailsLabel = nullptr;
QTextBrowser *m_detailsTextEdit = nullptr;
KCollapsibleGroupBox *m_detailsGroup = nullptr;
QCheckBox *m_dontAskAgainCB = nullptr;
QDialogButtonBox *m_buttonBox = nullptr;
QMetaObject::Connection m_buttonBoxConnection;
bool m_notifyEnabled = true;
};
KMessageDialog::KMessageDialog(KMessageDialog::Type type, const QString &text, QWidget *parent)
: QDialog(parent)
, d(new KMessageDialogPrivate(type, this))
{
// Dialog top-level layout
d->m_topLayout = new QVBoxLayout(this);
d->m_topLayout->setSizeConstraint(QLayout::SetFixedSize);
// Main widget
d->m_mainWidget = new QWidget(this);
d->m_topLayout->addWidget(d->m_mainWidget);
// Layout for the main widget
auto *mainLayout = new QVBoxLayout(d->m_mainWidget);
QStyle *widgetStyle = d->m_mainWidget->style();
// Provide extra spacing
mainLayout->setSpacing(widgetStyle->pixelMetric(QStyle::PM_LayoutVerticalSpacing) * 2);
mainLayout->setContentsMargins(0, 0, 0, 0);
auto *hLayout = new QHBoxLayout{};
mainLayout->addLayout(hLayout, 5);
// Icon
auto *iconLayout = new QVBoxLayout{};
hLayout->addLayout(iconLayout, 0);
d->m_iconLabel = new QLabel(d->m_mainWidget);
d->m_iconLabel->setVisible(false);
iconLayout->addWidget(d->m_iconLabel);
hLayout->addSpacing(widgetStyle->pixelMetric(QStyle::PM_LayoutHorizontalSpacing));
const QRect desktop = screen()->geometry();
const auto desktopWidth = desktop.width();
// Main message text
d->m_messageLabel = new QLabel(text, d->m_mainWidget);
if (d->m_messageLabel->sizeHint().width() > (desktopWidth * 0.5)) {
// Enable automatic wrapping of messages which are longer than 50% of screen width
d->m_messageLabel->setWordWrap(true);
// Use a squeezed label if text is still too wide
const bool usingSqueezedLabel = d->m_messageLabel->sizeHint().width() > (desktopWidth * 0.85);
if (usingSqueezedLabel) {
delete d->m_messageLabel;
d->m_messageLabel = new KSqueezedTextLabel(text, d->m_mainWidget);
}
}
d->m_messageLabel->setTextInteractionFlags(s_textFlags);
const bool usingScrollArea = (desktop.height() / 3) < d->m_messageLabel->sizeHint().height();
if (usingScrollArea) {
QScrollArea *messageScrollArea = new QScrollArea(d->m_mainWidget);
messageScrollArea->setWidget(d->m_messageLabel);
messageScrollArea->setFrameShape(QFrame::NoFrame);
messageScrollArea->setWidgetResizable(true);
hLayout->addWidget(messageScrollArea, 5);
} else {
hLayout->addWidget(d->m_messageLabel, 5);
}
// List widget, will be populated by setListWidgetItems()
d->m_listWidget = new QListWidget(d->m_mainWidget);
mainLayout->addWidget(d->m_listWidget, usingScrollArea ? 10 : 50);
d->m_listWidget->setVisible(false);
// DontAskAgain checkbox, will be set up by setDontAskAgainText()
d->m_dontAskAgainCB = new QCheckBox(d->m_mainWidget);
mainLayout->addWidget(d->m_dontAskAgainCB);
d->m_dontAskAgainCB->setVisible(false);
// Details widget, text will be added by setDetails()
auto *detailsHLayout = new QHBoxLayout{};
d->m_topLayout->addLayout(detailsHLayout);
d->m_detailsGroup = new KCollapsibleGroupBox();
d->m_detailsGroup->setVisible(false);
d->m_detailsGroup->setTitle(QApplication::translate("KMessageDialog", "Details"));
QVBoxLayout *detailsLayout = new QVBoxLayout(d->m_detailsGroup);
d->m_detailsLabel = new QLabel();
d->m_detailsLabel->setTextInteractionFlags(s_textFlags);
d->m_detailsLabel->setWordWrap(true);
detailsLayout->addWidget(d->m_detailsLabel);
d->m_detailsTextEdit = new QTextBrowser{};
d->m_detailsTextEdit->setMinimumHeight(d->m_detailsTextEdit->fontMetrics().lineSpacing() * 11);
detailsLayout->addWidget(d->m_detailsTextEdit, 50);
detailsHLayout->addWidget(d->m_detailsGroup);
// Button box
d->m_buttonBox = new QDialogButtonBox(this);
d->m_topLayout->addWidget(d->m_buttonBox);
// Default buttons
if ((d->m_type == KMessageDialog::Information) || (d->m_type != KMessageDialog::Error)) {
// set Ok button
setButtons();
} else if ((d->m_type == KMessageDialog::WarningContinueCancel)) {
// set Continue & Cancel buttons
setButtons(KStandardGuiItem::cont(), KGuiItem(), KStandardGuiItem::cancel());
}
setNotifyEnabled(true);
// If the dialog is rejected, e.g. by pressing Esc, done() signal connected to the button box
// won't be emitted
connect(this, &QDialog::rejected, this, [this]() {
done(KMessageDialog::Cancel);
});
}
// This method has been copied from KWindowSystem to avoid depending on it
static void setMainWindow(QDialog *dialog, WId mainWindowId)
{
#ifdef Q_OS_OSX
if (!QWidget::find(mainWindowId)) {
return;
}
#endif
// Set the WA_NativeWindow attribute to force the creation of the QWindow.
// Without this QWidget::windowHandle() returns 0.
dialog->setAttribute(Qt::WA_NativeWindow, true);
QWindow *subWindow = dialog->windowHandle();
Q_ASSERT(subWindow);
QWindow *mainWindow = QWindow::fromWinId(mainWindowId);
if (!mainWindow) {
// foreign windows not supported on all platforms
return;
}
// mainWindow is not the child of any object, so make sure it gets deleted at some point
QObject::connect(dialog, &QObject::destroyed, mainWindow, &QObject::deleteLater);
subWindow->setTransientParent(mainWindow);
}
KMessageDialog::KMessageDialog(KMessageDialog::Type type, const QString &text, WId parent_id)
: KMessageDialog(type, text)
{
QWidget *parent = QWidget::find(parent_id);
setParent(parent);
if (!parent && parent_id) {
setMainWindow(this, parent_id);
}
}
KMessageDialog::~KMessageDialog()
{
removeEventFilter(d.get());
}
void KMessageDialog::setCaption(const QString &caption)
{
if (!caption.isEmpty()) {
setWindowTitle(caption);
return;
}
QString title;
switch (d->m_type) { // Get a title based on the dialog Type
case KMessageDialog::QuestionTwoActions:
case KMessageDialog::QuestionTwoActionsCancel:
title = QApplication::translate("KMessageDialog", "Question");
break;
case KMessageDialog::WarningTwoActions:
case KMessageDialog::WarningTwoActionsCancel:
case KMessageDialog::WarningContinueCancel:
title = QApplication::translate("KMessageDialog", "Warning");
break;
case KMessageDialog::Information:
title = QApplication::translate("KMessageDialog", "Information");
break;
case KMessageDialog::Error: {
title = QApplication::translate("KMessageDialog", "Error");
break;
}
default:
break;
}
setWindowTitle(title);
}
void KMessageDialog::setIcon(const QIcon &icon)
{
QIcon effectiveIcon(icon);
if (effectiveIcon.isNull()) { // Fallback to an icon based on the dialog Type
QStyle *style = this->style();
switch (d->m_type) {
case KMessageDialog::QuestionTwoActions:
case KMessageDialog::QuestionTwoActionsCancel:
effectiveIcon = style->standardIcon(QStyle::SP_MessageBoxQuestion, nullptr, this);
break;
case KMessageDialog::WarningTwoActions:
case KMessageDialog::WarningTwoActionsCancel:
case KMessageDialog::WarningContinueCancel:
effectiveIcon = style->standardIcon(QStyle::SP_MessageBoxWarning, nullptr, this);
break;
case KMessageDialog::Information:
effectiveIcon = style->standardIcon(QStyle::SP_MessageBoxInformation, nullptr, this);
break;
case KMessageDialog::Error:
effectiveIcon = style->standardIcon(QStyle::SP_MessageBoxCritical, nullptr, this);
break;
default:
break;
}
}
if (effectiveIcon.isNull()) {
qCWarning(KWidgetsAddonsLog) << "Neither the requested icon nor a generic one based on the "
"dialog type could be found.";
return;
}
d->m_iconLabel->setVisible(true);
QStyleOption option;
option.initFrom(d->m_mainWidget);
QStyle *widgetStyle = d->m_mainWidget->style();
const int size = widgetStyle->pixelMetric(QStyle::PM_MessageBoxIconSize, &option, d->m_mainWidget);
d->m_iconLabel->setPixmap(effectiveIcon.pixmap(size));
}
void KMessageDialog::setListWidgetItems(const QStringList &strlist)
{
const bool isEmpty = strlist.isEmpty();
d->m_listWidget->setVisible(!isEmpty);
if (isEmpty) {
return;
}
// Enable automatic wrapping since the listwidget already has a good initial width
d->m_messageLabel->setWordWrap(true);
d->m_listWidget->addItems(strlist);
QStyleOptionViewItem styleOption;
styleOption.initFrom(d->m_listWidget);
QFontMetrics fm(styleOption.font);
int listWidth = d->m_listWidget->width();
for (const QString &str : strlist) {
listWidth = qMax(listWidth, fm.boundingRect(str).width());
}
const int borderWidth = (d->m_listWidget->width() - d->m_listWidget->viewport()->width() //
+ d->m_listWidget->verticalScrollBar()->height());
listWidth += borderWidth;
const auto deskWidthPortion = screen()->geometry().width() * 0.85;
if (listWidth > deskWidthPortion) { // Limit the list widget size to 85% of screen width
listWidth = qRound(deskWidthPortion);
}
d->m_listWidget->setMinimumWidth(listWidth);
d->m_listWidget->setSelectionMode(QListWidget::NoSelection);
d->m_messageLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum);
}
void KMessageDialog::setDetails(const QString &details)
{
d->m_detailsGroup->setVisible(!details.isEmpty());
if (details.length() < 512) { // random number KMessageBox uses.
d->m_detailsLabel->setText(details);
d->m_detailsLabel->show();
d->m_detailsTextEdit->setText(QString());
d->m_detailsTextEdit->hide();
} else {
d->m_detailsLabel->setText(QString());
d->m_detailsLabel->hide();
d->m_detailsTextEdit->setText(details);
d->m_detailsTextEdit->show();
}
}
void KMessageDialog::setButtons(const KGuiItem &primaryAction, const KGuiItem &secondaryAction, const KGuiItem &cancelAction)
{
switch (d->m_type) {
case KMessageDialog::QuestionTwoActions: {
d->m_buttonBox->setStandardButtons(QDialogButtonBox::Yes | QDialogButtonBox::No);
auto *buttonYes = d->m_buttonBox->button(QDialogButtonBox::Yes);
KGuiItem::assign(buttonYes, primaryAction);
buttonYes->setFocus();
KGuiItem::assign(d->m_buttonBox->button(QDialogButtonBox::No), secondaryAction);
break;
}
case KMessageDialog::QuestionTwoActionsCancel: {
d->m_buttonBox->setStandardButtons(QDialogButtonBox::Yes | QDialogButtonBox::No | QDialogButtonBox::Cancel);
auto *buttonYes = d->m_buttonBox->button(QDialogButtonBox::Yes);
KGuiItem::assign(buttonYes, primaryAction);
buttonYes->setFocus();
KGuiItem::assign(d->m_buttonBox->button(QDialogButtonBox::No), secondaryAction);
KGuiItem::assign(d->m_buttonBox->button(QDialogButtonBox::Cancel), cancelAction);
break;
}
case KMessageDialog::WarningTwoActions: {
d->m_buttonBox->setStandardButtons(QDialogButtonBox::Yes | QDialogButtonBox::No);
KGuiItem::assign(d->m_buttonBox->button(QDialogButtonBox::Yes), primaryAction);
auto *noBtn = d->m_buttonBox->button(QDialogButtonBox::No);
KGuiItem::assign(noBtn, secondaryAction);
noBtn->setDefault(true);
noBtn->setFocus();
break;
}
case KMessageDialog::WarningTwoActionsCancel: {
d->m_buttonBox->setStandardButtons(QDialogButtonBox::Yes | QDialogButtonBox::No | QDialogButtonBox::Cancel);
KGuiItem::assign(d->m_buttonBox->button(QDialogButtonBox::Yes), primaryAction);
KGuiItem::assign(d->m_buttonBox->button(QDialogButtonBox::No), secondaryAction);
auto *cancelButton = d->m_buttonBox->button(QDialogButtonBox::Cancel);
KGuiItem::assign(cancelButton, cancelAction);
cancelButton->setDefault(true);
cancelButton->setFocus();
break;
}
case KMessageDialog::WarningContinueCancel: {
d->m_buttonBox->setStandardButtons(QDialogButtonBox::Yes | QDialogButtonBox::Cancel);
KGuiItem::assign(d->m_buttonBox->button(QDialogButtonBox::Yes), primaryAction);
auto *cancelButton = d->m_buttonBox->button(QDialogButtonBox::Cancel);
KGuiItem::assign(cancelButton, cancelAction);
cancelButton->setDefault(true);
cancelButton->setFocus();
break;
}
case KMessageDialog::Information:
case KMessageDialog::Error: {
d->m_buttonBox->setStandardButtons(QDialogButtonBox::Ok);
auto *okButton = d->m_buttonBox->button(QDialogButtonBox::Ok);
KGuiItem::assign(okButton, KStandardGuiItem::ok());
okButton->setFocus();
break;
}
default:
break;
}
// Button connections
if (!d->m_buttonBoxConnection) {
d->m_buttonBoxConnection = connect(d->m_buttonBox, &QDialogButtonBox::clicked, this, [this](QAbstractButton *button) {
QDialogButtonBox::StandardButton code = d->m_buttonBox->standardButton(button);
const int result = (code == QDialogButtonBox::Ok) ? KMessageDialog::Ok
: (code == QDialogButtonBox::Cancel) ? KMessageDialog::Cancel
: (code == QDialogButtonBox::Yes) ? KMessageDialog::PrimaryAction
: (code == QDialogButtonBox::No) ? KMessageDialog::SecondaryAction
:
/* else */ -1;
if (result != -1) {
done(result);
}
});
}
}
void KMessageDialog::setDontAskAgainText(const QString &dontAskAgainText)
{
d->m_dontAskAgainCB->setVisible(!dontAskAgainText.isEmpty());
d->m_dontAskAgainCB->setText(dontAskAgainText);
}
void KMessageDialog::setDontAskAgainChecked(bool isChecked)
{
if (d->m_dontAskAgainCB->text().isEmpty()) {
qCWarning(KWidgetsAddonsLog) << "setDontAskAgainChecked() method was called on a dialog that doesn't "
"appear to have a checkbox; you need to use setDontAskAgainText() "
"to add a checkbox to the dialog first.";
return;
}
d->m_dontAskAgainCB->setChecked(isChecked);
}
bool KMessageDialog::isDontAskAgainChecked() const
{
if (d->m_dontAskAgainCB->text().isEmpty()) {
qCWarning(KWidgetsAddonsLog) << "isDontAskAgainChecked() method was called on a dialog that doesn't "
"appear to have a checkbox; you need to use setDontAskAgainText() "
"to add a checkbox to the dialog first.";
return false;
}
return d->m_dontAskAgainCB->isChecked();
}
void KMessageDialog::setOpenExternalLinks(bool isAllowed)
{
d->m_messageLabel->setOpenExternalLinks(isAllowed);
d->m_detailsLabel->setOpenExternalLinks(isAllowed);
d->m_detailsTextEdit->setOpenExternalLinks(isAllowed);
}
bool KMessageDialog::isNotifyEnabled() const
{
return d->m_notifyEnabled;
}
void KMessageDialog::setNotifyEnabled(bool enable)
{
d->m_notifyEnabled = enable;
}
void KMessageDialog::showEvent(QShowEvent *event)
{
if (d->m_notifyEnabled) {
// TODO include m_listWidget items
beep(d->m_type, d->m_messageLabel->text(), topLevelWidget());
}
QDialog::showEvent(event);
}
void KMessageDialog::beep(Type type, const QString &text, QWidget *widget)
{
#ifndef Q_OS_WIN // FIXME problems with KNotify on Windows
QMessageBox::Icon notifyType = QMessageBox::NoIcon;
switch (type) {
case KMessageDialog::QuestionTwoActions:
case KMessageDialog::QuestionTwoActionsCancel:
notifyType = QMessageBox::Question;
break;
case KMessageDialog::WarningTwoActions:
case KMessageDialog::WarningTwoActionsCancel:
case KMessageDialog::WarningContinueCancel:
notifyType = QMessageBox::Warning;
break;
case KMessageDialog::Information:
notifyType = QMessageBox::Information;
break;
case KMessageDialog::Error:
notifyType = QMessageBox::Critical;
break;
}
KMessageBox::notifyInterface()->sendNotification(notifyType, text, widget);
#endif
}
#include "kmessagedialog.moc"
#include "moc_kmessagedialog.cpp"