File indexing completed on 2024-05-05 05:29:54

0001 /*
0002  * SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
0003  *
0004  * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0005  */
0006 #include "decorationbuttongroup.h"
0007 #include "decoration.h"
0008 #include "decorationbuttongroup_p.h"
0009 #include "decorationsettings.h"
0010 
0011 #include <QDebug>
0012 #include <QGuiApplication>
0013 
0014 namespace KDecoration2
0015 {
0016 DecorationButtonGroup::Private::Private(Decoration *decoration, DecorationButtonGroup *parent)
0017     : decoration(decoration)
0018     , spacing(0.0)
0019     , q(parent)
0020 {
0021 }
0022 
0023 DecorationButtonGroup::Private::~Private() = default;
0024 
0025 void DecorationButtonGroup::Private::setGeometry(const QRectF &geo)
0026 {
0027     if (geometry == geo) {
0028         return;
0029     }
0030     geometry = geo;
0031     Q_EMIT q->geometryChanged(geometry);
0032 }
0033 
0034 namespace
0035 {
0036 static bool s_layoutRecursion = false;
0037 }
0038 
0039 void DecorationButtonGroup::Private::updateLayout()
0040 {
0041     if (s_layoutRecursion) {
0042         return;
0043     }
0044     s_layoutRecursion = true;
0045     const QPointF &pos = geometry.topLeft();
0046     // first calculate new size
0047     qreal height = 0;
0048     qreal width = 0;
0049     for (auto it = buttons.constBegin(); it != buttons.constEnd(); ++it) {
0050         if (!(*it)->isVisible()) {
0051             continue;
0052         }
0053         height = qMax(height, qreal((*it)->size().height()));
0054         width += (*it)->size().width();
0055         if (it + 1 != buttons.constEnd()) {
0056             width += spacing;
0057         }
0058     }
0059     setGeometry(QRectF(pos, QSizeF(width, height)));
0060 
0061     QGuiApplication* app = qobject_cast<QGuiApplication*>(QCoreApplication::instance());
0062     const auto layoutDirection = app ? app->layoutDirection() : Qt::LeftToRight;
0063 
0064     qreal leftPosition = pos.x();
0065     qreal rightPosition = pos.x() + width;
0066 
0067     if (layoutDirection == Qt::LeftToRight)
0068         for (auto button : std::as_const(buttons)) {
0069             if (!button->isVisible()) {
0070                 continue;
0071             }
0072             const auto size = button->size();
0073             const auto buttonPos = QPointF(leftPosition, pos.y());
0074             button->setGeometry(QRectF(buttonPos, size));
0075             leftPosition += size.width() + spacing;
0076         }
0077     else if (layoutDirection == Qt::RightToLeft)
0078         for (auto button : std::as_const(buttons)) {
0079             if (!button->isVisible()) {
0080                 continue;
0081             }
0082             const auto size = button->size();
0083             const auto buttonPos = QPointF(rightPosition - size.width(), pos.y());
0084             button->setGeometry(QRectF(buttonPos, size));
0085             rightPosition -= size.width() + spacing;
0086         }
0087     else {
0088         qCritical() << "There's an unhandled layout direction! This is likely an issue of KDecoration2 not being updated to handle it\n"
0089                     << "or the application having an invalid layout direction set. Either way, this is a critical bug.";
0090     }
0091 
0092     s_layoutRecursion = false;
0093 }
0094 
0095 DecorationButtonGroup::DecorationButtonGroup(Decoration *parent)
0096     : QObject(parent)
0097     , d(new Private(parent, this))
0098 {
0099 }
0100 
0101 DecorationButtonGroup::DecorationButtonGroup(DecorationButtonGroup::Position type,
0102                                              Decoration *parent,
0103                                              std::function<DecorationButton *(DecorationButtonType, Decoration *, QObject *)> buttonCreator)
0104     : QObject(parent)
0105     , d(new Private(parent, this))
0106 {
0107     QGuiApplication* app = qobject_cast<QGuiApplication*>(QCoreApplication::instance());
0108     const auto layoutDirection = app ? app->layoutDirection() : Qt::LeftToRight;
0109     auto settings = parent->settings();
0110     auto createButtons = [&] {
0111         const auto &buttons =
0112             (type == Position::Left) ?
0113                 (layoutDirection == Qt::LeftToRight ? settings->decorationButtonsLeft() : settings->decorationButtonsRight()) :
0114                 (layoutDirection == Qt::LeftToRight ? settings->decorationButtonsRight() : settings->decorationButtonsLeft());
0115         for (DecorationButtonType type : buttons) {
0116             if (DecorationButton *b = buttonCreator(type, parent, this)) {
0117                 addButton(b);
0118             }
0119         }
0120     };
0121     createButtons();
0122     auto changed = type == Position::Left ? &DecorationSettings::decorationButtonsLeftChanged : &DecorationSettings::decorationButtonsRightChanged;
0123     connect(settings.get(), changed, this, [this, createButtons] {
0124         qDeleteAll(d->buttons);
0125         d->buttons.clear();
0126         createButtons();
0127     });
0128 }
0129 
0130 DecorationButtonGroup::~DecorationButtonGroup() = default;
0131 
0132 Decoration *DecorationButtonGroup::decoration() const
0133 {
0134     return d->decoration;
0135 }
0136 
0137 QRectF DecorationButtonGroup::geometry() const
0138 {
0139     return d->geometry;
0140 }
0141 
0142 bool DecorationButtonGroup::hasButton(DecorationButtonType type) const
0143 {
0144     // TODO: check for deletion of button
0145     auto it = std::find_if(d->buttons.begin(), d->buttons.end(), [type](DecorationButton *button) {
0146         return button->type() == type;
0147     });
0148     return it != d->buttons.end();
0149 }
0150 
0151 qreal DecorationButtonGroup::spacing() const
0152 {
0153     return d->spacing;
0154 }
0155 
0156 QPointF DecorationButtonGroup::pos() const
0157 {
0158     return d->geometry.topLeft();
0159 }
0160 
0161 void DecorationButtonGroup::setPos(const QPointF &pos)
0162 {
0163     if (d->geometry.topLeft() == pos) {
0164         return;
0165     }
0166     d->setGeometry(QRectF(pos, d->geometry.size()));
0167     d->updateLayout();
0168 }
0169 
0170 void DecorationButtonGroup::setSpacing(qreal spacing)
0171 {
0172     if (d->spacing == spacing) {
0173         return;
0174     }
0175     d->spacing = spacing;
0176     Q_EMIT spacingChanged(d->spacing);
0177     d->updateLayout();
0178 }
0179 
0180 void DecorationButtonGroup::addButton(DecorationButton *button)
0181 {
0182     Q_ASSERT(button);
0183     connect(button, &DecorationButton::visibilityChanged, this, [this]() {
0184         d->updateLayout();
0185     });
0186     connect(button, &DecorationButton::geometryChanged, this, [this]() {
0187         d->updateLayout();
0188     });
0189     d->buttons.append(button);
0190     d->updateLayout();
0191 }
0192 
0193 QList<DecorationButton *> DecorationButtonGroup::buttons() const
0194 {
0195     return d->buttons;
0196 }
0197 
0198 void DecorationButtonGroup::removeButton(DecorationButtonType type)
0199 {
0200     bool needUpdate = false;
0201     auto it = d->buttons.begin();
0202     while (it != d->buttons.end()) {
0203         if ((*it)->type() == type) {
0204             it = d->buttons.erase(it);
0205             needUpdate = true;
0206         } else {
0207             it++;
0208         }
0209     }
0210     if (needUpdate) {
0211         d->updateLayout();
0212     }
0213 }
0214 
0215 void DecorationButtonGroup::removeButton(DecorationButton *button)
0216 {
0217     bool needUpdate = false;
0218     auto it = d->buttons.begin();
0219     while (it != d->buttons.end()) {
0220         if (*it == button) {
0221             it = d->buttons.erase(it);
0222             needUpdate = true;
0223         } else {
0224             it++;
0225         }
0226     }
0227     if (needUpdate) {
0228         d->updateLayout();
0229     }
0230 }
0231 
0232 void DecorationButtonGroup::paint(QPainter *painter, const QRect &repaintArea)
0233 {
0234     const auto &buttons = d->buttons;
0235     for (auto button : buttons) {
0236         if (!button->isVisible()) {
0237             continue;
0238         }
0239         button->paint(painter, repaintArea);
0240     }
0241 }
0242 
0243 } // namespace
0244 
0245 #include "moc_decorationbuttongroup.cpp"