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:
@@ -0,0 +1,343 @@
|
||||
/*
|
||||
This file is part of the KDE project
|
||||
SPDX-FileCopyrightText: 2015 David Edmundson <davidedmundson@kde.org>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "kcollapsiblegroupbox.h"
|
||||
|
||||
#include <QLabel>
|
||||
#include <QLayout>
|
||||
#include <QMouseEvent>
|
||||
#include <QPainter>
|
||||
#include <QStyle>
|
||||
#include <QStyleOption>
|
||||
#include <QTimeLine>
|
||||
|
||||
class KCollapsibleGroupBoxPrivate
|
||||
{
|
||||
public:
|
||||
KCollapsibleGroupBoxPrivate(KCollapsibleGroupBox *qq);
|
||||
void updateChildrenFocus(bool expanded);
|
||||
void recalculateHeaderSize();
|
||||
QSize contentSize() const;
|
||||
QSize contentMinimumSize() const;
|
||||
|
||||
KCollapsibleGroupBox *const q;
|
||||
QTimeLine *animation;
|
||||
QString title;
|
||||
bool isExpanded = false;
|
||||
bool headerContainsMouse = false;
|
||||
QSize headerSize;
|
||||
int shortcutId = 0;
|
||||
QMap<QWidget *, Qt::FocusPolicy> focusMap; // Used to restore focus policy of widgets.
|
||||
};
|
||||
|
||||
KCollapsibleGroupBoxPrivate::KCollapsibleGroupBoxPrivate(KCollapsibleGroupBox *qq)
|
||||
: q(qq)
|
||||
{
|
||||
}
|
||||
|
||||
KCollapsibleGroupBox::KCollapsibleGroupBox(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, d(new KCollapsibleGroupBoxPrivate(this))
|
||||
{
|
||||
d->recalculateHeaderSize();
|
||||
|
||||
d->animation = new QTimeLine(500, this); // duration matches kmessagewidget
|
||||
connect(d->animation, &QTimeLine::valueChanged, this, [this](qreal value) {
|
||||
setFixedHeight((d->contentSize().height() * value) + d->headerSize.height());
|
||||
});
|
||||
connect(d->animation, &QTimeLine::stateChanged, this, [this](QTimeLine::State state) {
|
||||
if (state == QTimeLine::NotRunning) {
|
||||
d->updateChildrenFocus(d->isExpanded);
|
||||
}
|
||||
});
|
||||
|
||||
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
|
||||
setFocusPolicy(Qt::TabFocus);
|
||||
setMouseTracking(true);
|
||||
}
|
||||
|
||||
KCollapsibleGroupBox::~KCollapsibleGroupBox()
|
||||
{
|
||||
if (d->animation->state() == QTimeLine::Running) {
|
||||
d->animation->stop();
|
||||
}
|
||||
}
|
||||
|
||||
void KCollapsibleGroupBox::setTitle(const QString &title)
|
||||
{
|
||||
d->title = title;
|
||||
d->recalculateHeaderSize();
|
||||
|
||||
update();
|
||||
updateGeometry();
|
||||
|
||||
if (d->shortcutId) {
|
||||
releaseShortcut(d->shortcutId);
|
||||
}
|
||||
|
||||
d->shortcutId = grabShortcut(QKeySequence::mnemonic(title));
|
||||
|
||||
#ifndef QT_NO_ACCESSIBILITY
|
||||
setAccessibleName(title);
|
||||
#endif
|
||||
|
||||
Q_EMIT titleChanged();
|
||||
}
|
||||
|
||||
QString KCollapsibleGroupBox::title() const
|
||||
{
|
||||
return d->title;
|
||||
}
|
||||
|
||||
void KCollapsibleGroupBox::setExpanded(bool expanded)
|
||||
{
|
||||
if (expanded == d->isExpanded) {
|
||||
return;
|
||||
}
|
||||
|
||||
d->isExpanded = expanded;
|
||||
Q_EMIT expandedChanged();
|
||||
|
||||
d->updateChildrenFocus(expanded);
|
||||
|
||||
d->animation->setDirection(expanded ? QTimeLine::Forward : QTimeLine::Backward);
|
||||
// QTimeLine::duration() must be > 0
|
||||
const int duration = qMax(1, style()->styleHint(QStyle::SH_Widget_Animation_Duration));
|
||||
d->animation->stop();
|
||||
d->animation->setDuration(duration);
|
||||
d->animation->start();
|
||||
|
||||
// when going from collapsed to expanded changing the child visibility calls an updateGeometry
|
||||
// which calls sizeHint with expanded true before the first frame of the animation kicks in
|
||||
// trigger an effective frame 0
|
||||
if (expanded) {
|
||||
setFixedHeight(d->headerSize.height());
|
||||
}
|
||||
}
|
||||
|
||||
bool KCollapsibleGroupBox::isExpanded() const
|
||||
{
|
||||
return d->isExpanded;
|
||||
}
|
||||
|
||||
void KCollapsibleGroupBox::collapse()
|
||||
{
|
||||
setExpanded(false);
|
||||
}
|
||||
|
||||
void KCollapsibleGroupBox::expand()
|
||||
{
|
||||
setExpanded(true);
|
||||
}
|
||||
|
||||
void KCollapsibleGroupBox::toggle()
|
||||
{
|
||||
setExpanded(!d->isExpanded);
|
||||
}
|
||||
|
||||
void KCollapsibleGroupBox::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
QPainter p(this);
|
||||
|
||||
QStyleOptionButton baseOption;
|
||||
baseOption.initFrom(this);
|
||||
baseOption.rect = QRect(0, 0, width(), d->headerSize.height());
|
||||
baseOption.text = d->title;
|
||||
|
||||
if (d->headerContainsMouse) {
|
||||
baseOption.state |= QStyle::State_MouseOver;
|
||||
}
|
||||
|
||||
QStyle::PrimitiveElement element;
|
||||
if (d->isExpanded) {
|
||||
element = QStyle::PE_IndicatorArrowDown;
|
||||
} else {
|
||||
element = isLeftToRight() ? QStyle::PE_IndicatorArrowRight : QStyle::PE_IndicatorArrowLeft;
|
||||
}
|
||||
|
||||
QStyleOptionButton indicatorOption = baseOption;
|
||||
indicatorOption.rect = style()->subElementRect(QStyle::SE_CheckBoxIndicator, &indicatorOption, this);
|
||||
style()->drawPrimitive(element, &indicatorOption, &p, this);
|
||||
|
||||
QStyleOptionButton labelOption = baseOption;
|
||||
labelOption.rect = style()->subElementRect(QStyle::SE_CheckBoxContents, &labelOption, this);
|
||||
style()->drawControl(QStyle::CE_CheckBoxLabel, &labelOption, &p, this);
|
||||
|
||||
Q_UNUSED(event)
|
||||
}
|
||||
|
||||
QSize KCollapsibleGroupBox::sizeHint() const
|
||||
{
|
||||
if (d->isExpanded) {
|
||||
return d->contentSize() + QSize(0, d->headerSize.height());
|
||||
} else {
|
||||
return QSize(d->contentSize().width(), d->headerSize.height());
|
||||
}
|
||||
}
|
||||
|
||||
QSize KCollapsibleGroupBox::minimumSizeHint() const
|
||||
{
|
||||
int minimumWidth = qMax(d->contentSize().width(), d->headerSize.width());
|
||||
return QSize(minimumWidth, d->headerSize.height());
|
||||
}
|
||||
|
||||
bool KCollapsibleGroupBox::event(QEvent *event)
|
||||
{
|
||||
switch (event->type()) {
|
||||
case QEvent::StyleChange:
|
||||
/*fall through*/
|
||||
case QEvent::FontChange:
|
||||
d->recalculateHeaderSize();
|
||||
break;
|
||||
case QEvent::Shortcut: {
|
||||
QShortcutEvent *se = static_cast<QShortcutEvent *>(event);
|
||||
if (d->shortcutId == se->shortcutId()) {
|
||||
toggle();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case QEvent::ChildAdded: {
|
||||
QChildEvent *ce = static_cast<QChildEvent *>(event);
|
||||
if (ce->child()->isWidgetType()) {
|
||||
auto widget = static_cast<QWidget *>(ce->child());
|
||||
// Needs to be called asynchronously because at this point the widget is likely a "real" QWidget,
|
||||
// i.e. the QWidget base class whose constructor sets the focus policy to NoPolicy.
|
||||
// But the constructor of the child class (not yet called) could set a different focus policy later.
|
||||
auto focusFunc = [this, widget]() {
|
||||
overrideFocusPolicyOf(widget);
|
||||
};
|
||||
QMetaObject::invokeMethod(this, focusFunc, Qt::QueuedConnection);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case QEvent::LayoutRequest:
|
||||
if (d->animation->state() == QTimeLine::NotRunning) {
|
||||
setFixedHeight(sizeHint().height());
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return QWidget::event(event);
|
||||
}
|
||||
|
||||
void KCollapsibleGroupBox::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
const QRect headerRect(0, 0, width(), d->headerSize.height());
|
||||
if (headerRect.contains(event->pos())) {
|
||||
toggle();
|
||||
}
|
||||
event->setAccepted(true);
|
||||
}
|
||||
|
||||
// if mouse has changed whether it is in the top bar or not refresh to change arrow icon
|
||||
void KCollapsibleGroupBox::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
const QRect headerRect(0, 0, width(), d->headerSize.height());
|
||||
bool headerContainsMouse = headerRect.contains(event->pos());
|
||||
|
||||
if (headerContainsMouse != d->headerContainsMouse) {
|
||||
d->headerContainsMouse = headerContainsMouse;
|
||||
update();
|
||||
}
|
||||
|
||||
QWidget::mouseMoveEvent(event);
|
||||
}
|
||||
|
||||
void KCollapsibleGroupBox::leaveEvent(QEvent *event)
|
||||
{
|
||||
d->headerContainsMouse = false;
|
||||
update();
|
||||
QWidget::leaveEvent(event);
|
||||
}
|
||||
|
||||
void KCollapsibleGroupBox::keyPressEvent(QKeyEvent *event)
|
||||
{
|
||||
// event might have just propagated up from a child, if so we don't want to react to it
|
||||
if (!hasFocus()) {
|
||||
return;
|
||||
}
|
||||
const int key = event->key();
|
||||
if (key == Qt::Key_Space || key == Qt::Key_Enter || key == Qt::Key_Return) {
|
||||
toggle();
|
||||
event->setAccepted(true);
|
||||
}
|
||||
}
|
||||
|
||||
void KCollapsibleGroupBox::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
const QMargins margins = contentsMargins();
|
||||
|
||||
if (layout()) {
|
||||
// we don't want the layout trying to fit the current frame of the animation so always set it to the target height
|
||||
layout()->setGeometry(QRect(margins.left(), margins.top(), width() - margins.left() - margins.right(), layout()->sizeHint().height()));
|
||||
}
|
||||
|
||||
QWidget::resizeEvent(event);
|
||||
}
|
||||
|
||||
void KCollapsibleGroupBox::overrideFocusPolicyOf(QWidget *widget)
|
||||
{
|
||||
d->focusMap.insert(widget, widget->focusPolicy());
|
||||
|
||||
if (!isExpanded()) {
|
||||
// Prevent tab focus if not expanded.
|
||||
widget->setFocusPolicy(Qt::NoFocus);
|
||||
}
|
||||
}
|
||||
|
||||
void KCollapsibleGroupBoxPrivate::recalculateHeaderSize()
|
||||
{
|
||||
QStyleOption option;
|
||||
option.initFrom(q);
|
||||
|
||||
QSize textSize = q->style()->itemTextRect(option.fontMetrics, QRect(), Qt::TextShowMnemonic, false, title).size();
|
||||
|
||||
headerSize = q->style()->sizeFromContents(QStyle::CT_CheckBox, &option, textSize, q);
|
||||
q->setContentsMargins(q->style()->pixelMetric(QStyle::PM_IndicatorWidth), headerSize.height(), 0, 0);
|
||||
}
|
||||
|
||||
void KCollapsibleGroupBoxPrivate::updateChildrenFocus(bool expanded)
|
||||
{
|
||||
const auto children = q->children();
|
||||
for (QObject *child : children) {
|
||||
QWidget *widget = qobject_cast<QWidget *>(child);
|
||||
if (!widget) {
|
||||
continue;
|
||||
}
|
||||
// Restore old focus policy if expanded, remove from focus chain otherwise.
|
||||
if (expanded) {
|
||||
widget->setFocusPolicy(focusMap.value(widget));
|
||||
} else {
|
||||
widget->setFocusPolicy(Qt::NoFocus);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QSize KCollapsibleGroupBoxPrivate::contentSize() const
|
||||
{
|
||||
if (q->layout()) {
|
||||
const QMargins margins = q->contentsMargins();
|
||||
const QSize marginSize(margins.left() + margins.right(), margins.top() + margins.bottom());
|
||||
return q->layout()->sizeHint() + marginSize;
|
||||
}
|
||||
return QSize(0, 0);
|
||||
}
|
||||
|
||||
QSize KCollapsibleGroupBoxPrivate::contentMinimumSize() const
|
||||
{
|
||||
if (q->layout()) {
|
||||
const QMargins margins = q->contentsMargins();
|
||||
const QSize marginSize(margins.left() + margins.right(), margins.top() + margins.bottom());
|
||||
return q->layout()->minimumSize() + marginSize;
|
||||
}
|
||||
return QSize(0, 0);
|
||||
}
|
||||
|
||||
#include "moc_kcollapsiblegroupbox.cpp"
|
||||
Reference in New Issue
Block a user