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

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 "decoration.h"
0007 #include "decoratedclient.h"
0008 #include "decoration_p.h"
0009 #include "decorationbutton.h"
0010 #include "decorationsettings.h"
0011 #include "private/decoratedclientprivate.h"
0012 #include "private/decorationbridge.h"
0013 
0014 #include <QCoreApplication>
0015 #include <QHoverEvent>
0016 
0017 #include <cmath>
0018 
0019 namespace KDecoration2
0020 {
0021 namespace
0022 {
0023 DecorationBridge *findBridge(const QVariantList &args)
0024 {
0025     for (const auto &arg : args) {
0026         if (auto bridge = arg.toMap().value(QStringLiteral("bridge")).value<DecorationBridge *>()) {
0027             return bridge;
0028         }
0029     }
0030     Q_UNREACHABLE();
0031 }
0032 }
0033 
0034 Decoration::Private::Private(Decoration *deco, const QVariantList &args)
0035     : sectionUnderMouse(Qt::NoSection)
0036     , bridge(findBridge(args))
0037     , client(QSharedPointer<DecoratedClient>(new DecoratedClient(deco, bridge)))
0038     , opaque(false)
0039     , q(deco)
0040 {
0041     Q_UNUSED(args)
0042 }
0043 
0044 void Decoration::Private::setSectionUnderMouse(Qt::WindowFrameSection section)
0045 {
0046     if (sectionUnderMouse == section) {
0047         return;
0048     }
0049     sectionUnderMouse = section;
0050     Q_EMIT q->sectionUnderMouseChanged(sectionUnderMouse);
0051 }
0052 
0053 void Decoration::Private::updateSectionUnderMouse(const QPoint &mousePosition)
0054 {
0055     if (titleBar.contains(mousePosition)) {
0056         setSectionUnderMouse(Qt::TitleBarArea);
0057         return;
0058     }
0059     const QSize size = q->size();
0060     const int corner = 2 * settings->largeSpacing();
0061     const bool left = mousePosition.x() < borders.left();
0062     const bool top = mousePosition.y() < borders.top();
0063     const bool bottom = size.height() - mousePosition.y() <= borders.bottom();
0064     const bool right = size.width() - mousePosition.x() <= borders.right();
0065     if (left) {
0066         if (top && mousePosition.y() < titleBar.top() + corner) {
0067             setSectionUnderMouse(Qt::TopLeftSection);
0068         } else if (size.height() - mousePosition.y() <= borders.bottom() + corner && mousePosition.y() > titleBar.bottom()) {
0069             setSectionUnderMouse(Qt::BottomLeftSection);
0070         } else {
0071             setSectionUnderMouse(Qt::LeftSection);
0072         }
0073         return;
0074     }
0075     if (right) {
0076         if (top && mousePosition.y() < titleBar.top() + corner) {
0077             setSectionUnderMouse(Qt::TopRightSection);
0078         } else if (size.height() - mousePosition.y() <= borders.bottom() + corner && mousePosition.y() > titleBar.bottom()) {
0079             setSectionUnderMouse(Qt::BottomRightSection);
0080         } else {
0081             setSectionUnderMouse(Qt::RightSection);
0082         }
0083         return;
0084     }
0085     if (bottom) {
0086         if (mousePosition.y() > titleBar.bottom()) {
0087             if (mousePosition.x() < borders.left() + corner) {
0088                 setSectionUnderMouse(Qt::BottomLeftSection);
0089             } else if (size.width() - mousePosition.x() <= borders.right() + corner) {
0090                 setSectionUnderMouse(Qt::BottomRightSection);
0091             } else {
0092                 setSectionUnderMouse(Qt::BottomSection);
0093             }
0094         } else {
0095             setSectionUnderMouse(Qt::TitleBarArea);
0096         }
0097         return;
0098     }
0099     if (top) {
0100         if (mousePosition.y() < titleBar.top()) {
0101             if (mousePosition.x() < borders.left() + corner) {
0102                 setSectionUnderMouse(Qt::TopLeftSection);
0103             } else if (size.width() - mousePosition.x() <= borders.right() + corner) {
0104                 setSectionUnderMouse(Qt::TopRightSection);
0105             } else {
0106                 setSectionUnderMouse(Qt::TopSection);
0107             }
0108         } else {
0109             setSectionUnderMouse(Qt::TitleBarArea);
0110         }
0111         return;
0112     }
0113     setSectionUnderMouse(Qt::NoSection);
0114 }
0115 
0116 void Decoration::Private::addButton(DecorationButton *button)
0117 {
0118     Q_ASSERT(!buttons.contains(button));
0119     buttons << button;
0120     QObject::connect(button, &QObject::destroyed, q, [this](QObject *o) {
0121         auto it = buttons.begin();
0122         while (it != buttons.end()) {
0123             if (*it == static_cast<DecorationButton *>(o)) {
0124                 it = buttons.erase(it);
0125             } else {
0126                 it++;
0127             }
0128         }
0129     });
0130 }
0131 
0132 Decoration::Decoration(QObject *parent, const QVariantList &args)
0133     : QObject(parent)
0134     , d(new Private(this, args))
0135 {
0136     connect(this, &Decoration::bordersChanged, this, [this] {
0137         update();
0138     });
0139 }
0140 
0141 Decoration::~Decoration() = default;
0142 
0143 void Decoration::init()
0144 {
0145     Q_ASSERT(!d->settings.isNull());
0146 }
0147 
0148 QWeakPointer<DecoratedClient> Decoration::client() const
0149 {
0150     return d->client.toWeakRef();
0151 }
0152 
0153 #define DELEGATE(name)                                                                                                                                         \
0154     void Decoration::name()                                                                                                                                    \
0155     {                                                                                                                                                          \
0156         d->client->d->name();                                                                                                                                  \
0157     }
0158 
0159 DELEGATE(requestClose)
0160 DELEGATE(requestContextHelp)
0161 DELEGATE(requestMinimize)
0162 DELEGATE(requestToggleOnAllDesktops)
0163 DELEGATE(requestToggleShade)
0164 DELEGATE(requestToggleKeepAbove)
0165 DELEGATE(requestToggleKeepBelow)
0166 
0167 #undef DELEGATE
0168 
0169 #if KDECORATIONS2_ENABLE_DEPRECATED_SINCE(5, 21)
0170 void Decoration::requestShowWindowMenu()
0171 {
0172     requestShowWindowMenu(QRect());
0173 }
0174 #endif
0175 
0176 void Decoration::requestShowWindowMenu(const QRect &rect)
0177 {
0178     d->client->d->requestShowWindowMenu(rect);
0179 }
0180 
0181 void Decoration::requestShowToolTip(const QString &text)
0182 {
0183     d->client->d->requestShowToolTip(text);
0184 }
0185 
0186 void Decoration::requestHideToolTip()
0187 {
0188     d->client->d->requestHideToolTip();
0189 }
0190 
0191 void Decoration::requestToggleMaximization(Qt::MouseButtons buttons)
0192 {
0193     d->client->d->requestToggleMaximization(buttons);
0194 }
0195 
0196 void Decoration::showApplicationMenu(int actionId)
0197 {
0198     auto it = std::find_if(d->buttons.constBegin(), d->buttons.constEnd(), [](DecorationButton *button) {
0199         return button->type() == DecorationButtonType::ApplicationMenu;
0200     });
0201 
0202     if (it != d->buttons.constEnd()) {
0203         requestShowApplicationMenu((*it)->geometry().toRect(), actionId);
0204     }
0205 }
0206 
0207 void Decoration::requestShowApplicationMenu(const QRect &rect, int actionId)
0208 {
0209     if (auto *appMenuEnabledPrivate = dynamic_cast<ApplicationMenuEnabledDecoratedClientPrivate *>(d->client->d.get())) {
0210         appMenuEnabledPrivate->requestShowApplicationMenu(rect, actionId);
0211     }
0212 }
0213 
0214 #define DELEGATE(name, variableName, type, emitValue)                                                                                                          \
0215     void Decoration::name(type a)                                                                                                                              \
0216     {                                                                                                                                                          \
0217         if (d->variableName == a) {                                                                                                                            \
0218             return;                                                                                                                                            \
0219         }                                                                                                                                                      \
0220         d->variableName = a;                                                                                                                                   \
0221         Q_EMIT variableName##Changed(emitValue);                                                                                                               \
0222     }
0223 
0224 DELEGATE(setBlurRegion, blurRegion, const QRegion &, )
0225 DELEGATE(setBorders, borders, const QMargins &, )
0226 DELEGATE(setResizeOnlyBorders, resizeOnlyBorders, const QMargins &, )
0227 DELEGATE(setTitleBar, titleBar, const QRect &, )
0228 DELEGATE(setOpaque, opaque, bool, d->opaque)
0229 DELEGATE(setShadow, shadow, const QSharedPointer<DecorationShadow> &, d->shadow)
0230 
0231 #undef DELEGATE
0232 
0233 #define DELEGATE(name, type)                                                                                                                                   \
0234     type Decoration::name() const                                                                                                                              \
0235     {                                                                                                                                                          \
0236         return d->name;                                                                                                                                        \
0237     }
0238 
0239 DELEGATE(blurRegion, QRegion)
0240 DELEGATE(borders, QMargins)
0241 DELEGATE(resizeOnlyBorders, QMargins)
0242 DELEGATE(titleBar, QRect)
0243 DELEGATE(sectionUnderMouse, Qt::WindowFrameSection)
0244 DELEGATE(shadow, QSharedPointer<DecorationShadow>)
0245 
0246 #undef DELEGATE
0247 
0248 bool Decoration::isOpaque() const
0249 {
0250     return d->opaque;
0251 }
0252 
0253 #define BORDER(name, Name)                                                                                                                                     \
0254     int Decoration::border##Name() const                                                                                                                       \
0255     {                                                                                                                                                          \
0256         return d->borders.name();                                                                                                                              \
0257     }                                                                                                                                                          \
0258     int Decoration::resizeOnlyBorder##Name() const                                                                                                             \
0259     {                                                                                                                                                          \
0260         return d->resizeOnlyBorders.name();                                                                                                                    \
0261     }
0262 
0263 BORDER(left, Left)
0264 BORDER(right, Right)
0265 BORDER(top, Top)
0266 BORDER(bottom, Bottom)
0267 #undef BORDER
0268 
0269 QSize Decoration::size() const
0270 {
0271     const QMargins &b = d->borders;
0272     return QSize(d->client->width() + b.left() + b.right(), //
0273                  (d->client->isShaded() ? 0 : d->client->height()) + b.top() + b.bottom());
0274 }
0275 
0276 QRect Decoration::rect() const
0277 {
0278     return QRect(QPoint(0, 0), size());
0279 }
0280 
0281 bool Decoration::event(QEvent *event)
0282 {
0283     switch (event->type()) {
0284     case QEvent::HoverEnter:
0285         hoverEnterEvent(static_cast<QHoverEvent *>(event));
0286         return true;
0287     case QEvent::HoverLeave:
0288         hoverLeaveEvent(static_cast<QHoverEvent *>(event));
0289         return true;
0290     case QEvent::HoverMove:
0291         hoverMoveEvent(static_cast<QHoverEvent *>(event));
0292         return true;
0293     case QEvent::MouseButtonPress:
0294         mousePressEvent(static_cast<QMouseEvent *>(event));
0295         return true;
0296     case QEvent::MouseButtonRelease:
0297         mouseReleaseEvent(static_cast<QMouseEvent *>(event));
0298         return true;
0299     case QEvent::MouseMove:
0300         mouseMoveEvent(static_cast<QMouseEvent *>(event));
0301         return true;
0302     case QEvent::Wheel:
0303         wheelEvent(static_cast<QWheelEvent *>(event));
0304         return true;
0305     default:
0306         return QObject::event(event);
0307     }
0308 }
0309 
0310 void Decoration::hoverEnterEvent(QHoverEvent *event)
0311 {
0312     for (DecorationButton *button : d->buttons) {
0313         QCoreApplication::instance()->sendEvent(button, event);
0314     }
0315     auto flooredPos = QPoint(std::floor(event->posF().x()), std::floor(event->posF().y()));
0316     d->updateSectionUnderMouse(flooredPos);
0317 }
0318 
0319 void Decoration::hoverLeaveEvent(QHoverEvent *event)
0320 {
0321     for (DecorationButton *button : d->buttons) {
0322         QCoreApplication::instance()->sendEvent(button, event);
0323     }
0324     d->setSectionUnderMouse(Qt::NoSection);
0325 }
0326 
0327 void Decoration::hoverMoveEvent(QHoverEvent *event)
0328 {
0329     for (DecorationButton *button : d->buttons) {
0330         if (!button->isEnabled() || !button->isVisible()) {
0331             continue;
0332         }
0333         const bool hovered = button->isHovered();
0334         const bool contains = button->contains(event->posF());
0335         if (!hovered && contains) {
0336             QHoverEvent e(QEvent::HoverEnter, event->posF(), event->oldPosF(), event->modifiers());
0337             QCoreApplication::instance()->sendEvent(button, &e);
0338         } else if (hovered && !contains) {
0339             QHoverEvent e(QEvent::HoverLeave, event->posF(), event->oldPosF(), event->modifiers());
0340             QCoreApplication::instance()->sendEvent(button, &e);
0341         } else if (hovered && contains) {
0342             QCoreApplication::instance()->sendEvent(button, event);
0343         }
0344     }
0345     auto flooredPos = QPoint(std::floor(event->posF().x()), std::floor(event->posF().y()));
0346     d->updateSectionUnderMouse(flooredPos);
0347 }
0348 
0349 void Decoration::mouseMoveEvent(QMouseEvent *event)
0350 {
0351     for (DecorationButton *button : d->buttons) {
0352         if (button->isPressed()) {
0353             QCoreApplication::instance()->sendEvent(button, event);
0354             return;
0355         }
0356     }
0357     // not handled, take care ourselves
0358 }
0359 
0360 void Decoration::mousePressEvent(QMouseEvent *event)
0361 {
0362     for (DecorationButton *button : d->buttons) {
0363         if (button->isHovered()) {
0364             if (button->acceptedButtons().testFlag(event->button())) {
0365                 QCoreApplication::instance()->sendEvent(button, event);
0366             }
0367             event->setAccepted(true);
0368             return;
0369         }
0370     }
0371 }
0372 
0373 void Decoration::mouseReleaseEvent(QMouseEvent *event)
0374 {
0375     for (DecorationButton *button : d->buttons) {
0376         if (button->isPressed() && button->acceptedButtons().testFlag(event->button())) {
0377             QCoreApplication::instance()->sendEvent(button, event);
0378             return;
0379         }
0380     }
0381     // not handled, take care ourselves
0382     d->updateSectionUnderMouse(event->pos());
0383 }
0384 
0385 void Decoration::wheelEvent(QWheelEvent *event)
0386 {
0387     for (DecorationButton *button : d->buttons) {
0388         if (button->contains(event->position())) {
0389             QCoreApplication::instance()->sendEvent(button, event);
0390             event->setAccepted(true);
0391         }
0392     }
0393 }
0394 
0395 void Decoration::update(const QRect &r)
0396 {
0397     Q_EMIT damaged(r.isNull() ? rect() : r);
0398 }
0399 
0400 void Decoration::update()
0401 {
0402     update(QRect());
0403 }
0404 
0405 void Decoration::setSettings(const QSharedPointer<DecorationSettings> &settings)
0406 {
0407     d->settings = settings;
0408 }
0409 
0410 QSharedPointer<DecorationSettings> Decoration::settings() const
0411 {
0412     return d->settings;
0413 }
0414 
0415 } // namespace