File indexing completed on 2024-04-28 16:44:34

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) for (auto button : qAsConst(buttons)) {
0068         if (!button->isVisible()) {
0069             continue;
0070         }
0071         const auto size = button->size();
0072         const auto buttonPos = QPointF(leftPosition, pos.y());
0073         button->setGeometry(QRectF(buttonPos, size));
0074         leftPosition += size.width() + spacing;
0075     } else if (layoutDirection == Qt::RightToLeft) for (auto button : qAsConst(buttons)) {
0076         if (!button->isVisible()) {
0077             continue;
0078         }
0079         const auto size = button->size();
0080         const auto buttonPos = QPointF(rightPosition - size.width(), pos.y());
0081         button->setGeometry(QRectF(buttonPos, size));
0082         rightPosition -= size.width() + spacing;
0083     } else {
0084         qCritical() << "There's an unhandled layout direction! This is likely an issue of KDecoration2 not being updated to handle it\n"
0085                     << "or the application having an invalid layout direction set. Either way, this is a critical bug.";
0086     }
0087 
0088     s_layoutRecursion = false;
0089 }
0090 
0091 DecorationButtonGroup::DecorationButtonGroup(Decoration *parent)
0092     : QObject(parent)
0093     , d(new Private(parent, this))
0094 {
0095 }
0096 
0097 DecorationButtonGroup::DecorationButtonGroup(DecorationButtonGroup::Position type,
0098                                              Decoration *parent,
0099                                              std::function<DecorationButton *(DecorationButtonType, Decoration *, QObject *)> buttonCreator)
0100     : QObject(parent)
0101     , d(new Private(parent, this))
0102 {
0103     QGuiApplication* app = qobject_cast<QGuiApplication*>(QCoreApplication::instance());
0104     const auto layoutDirection = app ? app->layoutDirection() : Qt::LeftToRight;
0105     auto settings = parent->settings();
0106     auto createButtons = [=] {
0107         const auto &buttons =
0108             (type == Position::Left) ?
0109                 (layoutDirection == Qt::LeftToRight ? settings->decorationButtonsLeft() : settings->decorationButtonsRight()) :
0110                 (layoutDirection == Qt::LeftToRight ? settings->decorationButtonsRight() : settings->decorationButtonsLeft());
0111         for (DecorationButtonType type : buttons) {
0112             if (DecorationButton *b = buttonCreator(type, parent, this)) {
0113                 addButton(QPointer<DecorationButton>(b));
0114             }
0115         }
0116     };
0117     createButtons();
0118     auto changed = type == Position::Left ? &DecorationSettings::decorationButtonsLeftChanged : &DecorationSettings::decorationButtonsRightChanged;
0119     connect(settings.data(), changed, this, [this, createButtons] {
0120         qDeleteAll(d->buttons);
0121         d->buttons.clear();
0122         createButtons();
0123     });
0124 }
0125 
0126 DecorationButtonGroup::~DecorationButtonGroup() = default;
0127 
0128 QPointer<Decoration> DecorationButtonGroup::decoration() const
0129 {
0130     return QPointer<Decoration>(d->decoration);
0131 }
0132 
0133 QRectF DecorationButtonGroup::geometry() const
0134 {
0135     return d->geometry;
0136 }
0137 
0138 bool DecorationButtonGroup::hasButton(DecorationButtonType type) const
0139 {
0140     // TODO: check for deletion of button
0141     auto it = std::find_if(d->buttons.begin(), d->buttons.end(), [type](const QPointer<DecorationButton> &button) {
0142         return button->type() == type;
0143     });
0144     return it != d->buttons.end();
0145 }
0146 
0147 qreal DecorationButtonGroup::spacing() const
0148 {
0149     return d->spacing;
0150 }
0151 
0152 QPointF DecorationButtonGroup::pos() const
0153 {
0154     return d->geometry.topLeft();
0155 }
0156 
0157 void DecorationButtonGroup::setPos(const QPointF &pos)
0158 {
0159     if (d->geometry.topLeft() == pos) {
0160         return;
0161     }
0162     d->setGeometry(QRectF(pos, d->geometry.size()));
0163     d->updateLayout();
0164 }
0165 
0166 void DecorationButtonGroup::setSpacing(qreal spacing)
0167 {
0168     if (d->spacing == spacing) {
0169         return;
0170     }
0171     d->spacing = spacing;
0172     Q_EMIT spacingChanged(d->spacing);
0173     d->updateLayout();
0174 }
0175 
0176 void DecorationButtonGroup::addButton(const QPointer<DecorationButton> &button)
0177 {
0178     Q_ASSERT(!button.isNull());
0179     connect(button.data(), &DecorationButton::visibilityChanged, this, [this]() {
0180         d->updateLayout();
0181     });
0182     connect(button.data(), &DecorationButton::geometryChanged, this, [this]() {
0183         d->updateLayout();
0184     });
0185     d->buttons.append(button);
0186     d->updateLayout();
0187 }
0188 
0189 QVector<QPointer<DecorationButton>> DecorationButtonGroup::buttons() const
0190 {
0191     return d->buttons;
0192 }
0193 
0194 void DecorationButtonGroup::removeButton(DecorationButtonType type)
0195 {
0196     bool needUpdate = false;
0197     auto it = d->buttons.begin();
0198     while (it != d->buttons.end()) {
0199         if ((*it)->type() == type) {
0200             it = d->buttons.erase(it);
0201             needUpdate = true;
0202         } else {
0203             it++;
0204         }
0205     }
0206     if (needUpdate) {
0207         d->updateLayout();
0208     }
0209 }
0210 
0211 void DecorationButtonGroup::removeButton(const QPointer<DecorationButton> &button)
0212 {
0213     bool needUpdate = false;
0214     auto it = d->buttons.begin();
0215     while (it != d->buttons.end()) {
0216         if (*it == button) {
0217             it = d->buttons.erase(it);
0218             needUpdate = true;
0219         } else {
0220             it++;
0221         }
0222     }
0223     if (needUpdate) {
0224         d->updateLayout();
0225     }
0226 }
0227 
0228 void DecorationButtonGroup::paint(QPainter *painter, const QRect &repaintArea)
0229 {
0230     const auto &buttons = d->buttons;
0231     for (auto button : buttons) {
0232         if (!button->isVisible()) {
0233             continue;
0234         }
0235         button->paint(painter, repaintArea);
0236     }
0237 }
0238 
0239 } // namespace