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

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(std::shared_ptr<DecoratedClient>(new DecoratedClient(deco, bridge)))
0038     , opaque(false)
0039     , q(deco)
0040 {
0041 }
0042 
0043 void Decoration::Private::setSectionUnderMouse(Qt::WindowFrameSection section)
0044 {
0045     if (sectionUnderMouse == section) {
0046         return;
0047     }
0048     sectionUnderMouse = section;
0049     Q_EMIT q->sectionUnderMouseChanged(sectionUnderMouse);
0050 }
0051 
0052 void Decoration::Private::updateSectionUnderMouse(const QPoint &mousePosition)
0053 {
0054     if (titleBar.contains(mousePosition)) {
0055         setSectionUnderMouse(Qt::TitleBarArea);
0056         return;
0057     }
0058     const QSize size = q->size();
0059     const int corner = 2 * settings->largeSpacing();
0060     const bool left = mousePosition.x() < borders.left();
0061     const bool top = mousePosition.y() < borders.top();
0062     const bool bottom = size.height() - mousePosition.y() <= borders.bottom();
0063     const bool right = size.width() - mousePosition.x() <= borders.right();
0064     if (left) {
0065         if (top && mousePosition.y() < titleBar.top() + corner) {
0066             setSectionUnderMouse(Qt::TopLeftSection);
0067         } else if (size.height() - mousePosition.y() <= borders.bottom() + corner && mousePosition.y() > titleBar.bottom()) {
0068             setSectionUnderMouse(Qt::BottomLeftSection);
0069         } else {
0070             setSectionUnderMouse(Qt::LeftSection);
0071         }
0072         return;
0073     }
0074     if (right) {
0075         if (top && mousePosition.y() < titleBar.top() + corner) {
0076             setSectionUnderMouse(Qt::TopRightSection);
0077         } else if (size.height() - mousePosition.y() <= borders.bottom() + corner && mousePosition.y() > titleBar.bottom()) {
0078             setSectionUnderMouse(Qt::BottomRightSection);
0079         } else {
0080             setSectionUnderMouse(Qt::RightSection);
0081         }
0082         return;
0083     }
0084     if (bottom) {
0085         if (mousePosition.y() > titleBar.bottom()) {
0086             if (mousePosition.x() < borders.left() + corner) {
0087                 setSectionUnderMouse(Qt::BottomLeftSection);
0088             } else if (size.width() - mousePosition.x() <= borders.right() + corner) {
0089                 setSectionUnderMouse(Qt::BottomRightSection);
0090             } else {
0091                 setSectionUnderMouse(Qt::BottomSection);
0092             }
0093         } else {
0094             setSectionUnderMouse(Qt::TitleBarArea);
0095         }
0096         return;
0097     }
0098     if (top) {
0099         if (mousePosition.y() < titleBar.top()) {
0100             if (mousePosition.x() < borders.left() + corner) {
0101                 setSectionUnderMouse(Qt::TopLeftSection);
0102             } else if (size.width() - mousePosition.x() <= borders.right() + corner) {
0103                 setSectionUnderMouse(Qt::TopRightSection);
0104             } else {
0105                 setSectionUnderMouse(Qt::TopSection);
0106             }
0107         } else {
0108             setSectionUnderMouse(Qt::TitleBarArea);
0109         }
0110         return;
0111     }
0112     setSectionUnderMouse(Qt::NoSection);
0113 }
0114 
0115 void Decoration::Private::addButton(DecorationButton *button)
0116 {
0117     Q_ASSERT(!buttons.contains(button));
0118     buttons << button;
0119     QObject::connect(button, &QObject::destroyed, q, [this](QObject *o) {
0120         auto it = buttons.begin();
0121         while (it != buttons.end()) {
0122             if (*it == static_cast<DecorationButton *>(o)) {
0123                 it = buttons.erase(it);
0124             } else {
0125                 it++;
0126             }
0127         }
0128     });
0129 }
0130 
0131 Decoration::Decoration(QObject *parent, const QVariantList &args)
0132     : QObject(parent)
0133     , d(new Private(this, args))
0134 {
0135     connect(this, &Decoration::bordersChanged, this, [this] {
0136         update();
0137     });
0138 }
0139 
0140 Decoration::~Decoration() = default;
0141 
0142 DecoratedClient *Decoration::client() const
0143 {
0144     return d->client.get();
0145 }
0146 
0147 void Decoration::requestClose()
0148 {
0149     d->client->d->requestClose();
0150 }
0151 
0152 void Decoration::requestContextHelp()
0153 {
0154     d->client->d->requestContextHelp();
0155 }
0156 
0157 void Decoration::requestMinimize()
0158 {
0159     d->client->d->requestMinimize();
0160 }
0161 
0162 void Decoration::requestToggleOnAllDesktops()
0163 {
0164     d->client->d->requestToggleOnAllDesktops();
0165 }
0166 
0167 void Decoration::requestToggleShade()
0168 {
0169     d->client->d->requestToggleShade();
0170 }
0171 
0172 void Decoration::requestToggleKeepAbove()
0173 {
0174     d->client->d->requestToggleKeepAbove();
0175 }
0176 
0177 void Decoration::requestToggleKeepBelow()
0178 {
0179     d->client->d->requestToggleKeepBelow();
0180 }
0181 
0182 #if KDECORATIONS2_ENABLE_DEPRECATED_SINCE(5, 21)
0183 void Decoration::requestShowWindowMenu()
0184 {
0185     requestShowWindowMenu(QRect());
0186 }
0187 #endif
0188 
0189 void Decoration::requestShowWindowMenu(const QRect &rect)
0190 {
0191     d->client->d->requestShowWindowMenu(rect);
0192 }
0193 
0194 void Decoration::requestShowToolTip(const QString &text)
0195 {
0196     d->client->d->requestShowToolTip(text);
0197 }
0198 
0199 void Decoration::requestHideToolTip()
0200 {
0201     d->client->d->requestHideToolTip();
0202 }
0203 
0204 void Decoration::requestToggleMaximization(Qt::MouseButtons buttons)
0205 {
0206     d->client->d->requestToggleMaximization(buttons);
0207 }
0208 
0209 void Decoration::showApplicationMenu(int actionId)
0210 {
0211     const auto it = std::find_if(d->buttons.constBegin(), d->buttons.constEnd(), [](DecorationButton *button) {
0212         return button->type() == DecorationButtonType::ApplicationMenu;
0213     });
0214     if (it != d->buttons.constEnd()) {
0215         requestShowApplicationMenu((*it)->geometry().toRect(), actionId);
0216     }
0217 }
0218 
0219 void Decoration::requestShowApplicationMenu(const QRect &rect, int actionId)
0220 {
0221     if (auto *appMenuEnabledPrivate = dynamic_cast<ApplicationMenuEnabledDecoratedClientPrivate *>(d->client->d.get())) {
0222         appMenuEnabledPrivate->requestShowApplicationMenu(rect, actionId);
0223     }
0224 }
0225 
0226 void Decoration::setBlurRegion(const QRegion &region)
0227 {
0228     if (d->blurRegion != region) {
0229         d->blurRegion = region;
0230         Q_EMIT blurRegionChanged();
0231     }
0232 }
0233 
0234 void Decoration::setBorders(const QMargins &borders)
0235 {
0236     if (d->borders != borders) {
0237         d->borders = borders;
0238         Q_EMIT bordersChanged();
0239     }
0240 }
0241 
0242 void Decoration::setResizeOnlyBorders(const QMargins &borders)
0243 {
0244     if (d->resizeOnlyBorders != borders) {
0245         d->resizeOnlyBorders = borders;
0246         Q_EMIT resizeOnlyBordersChanged();
0247     }
0248 }
0249 
0250 void Decoration::setTitleBar(const QRect &rect)
0251 {
0252     if (d->titleBar != rect) {
0253         d->titleBar = rect;
0254         Q_EMIT titleBarChanged();
0255     }
0256 }
0257 
0258 void Decoration::setOpaque(bool opaque)
0259 {
0260     if (d->opaque != opaque) {
0261         d->opaque = opaque;
0262         Q_EMIT opaqueChanged(opaque);
0263     }
0264 }
0265 
0266 void Decoration::setShadow(const std::shared_ptr<DecorationShadow> &shadow)
0267 {
0268     if (d->shadow != shadow) {
0269         d->shadow = shadow;
0270         Q_EMIT shadowChanged(shadow);
0271     }
0272 }
0273 
0274 QRegion Decoration::blurRegion() const
0275 {
0276     return d->blurRegion;
0277 }
0278 
0279 QMargins Decoration::borders() const
0280 {
0281     return d->borders;
0282 }
0283 
0284 QMargins Decoration::resizeOnlyBorders() const
0285 {
0286     return d->resizeOnlyBorders;
0287 }
0288 
0289 QRect Decoration::titleBar() const
0290 {
0291     return d->titleBar;
0292 }
0293 
0294 Qt::WindowFrameSection Decoration::sectionUnderMouse() const
0295 {
0296     return d->sectionUnderMouse;
0297 }
0298 
0299 std::shared_ptr<DecorationShadow> Decoration::shadow() const
0300 {
0301     return d->shadow;
0302 }
0303 
0304 bool Decoration::isOpaque() const
0305 {
0306     return d->opaque;
0307 }
0308 
0309 int Decoration::borderLeft() const
0310 {
0311     return d->borders.left();
0312 }
0313 
0314 int Decoration::resizeOnlyBorderLeft() const
0315 {
0316     return d->resizeOnlyBorders.left();
0317 }
0318 
0319 int Decoration::borderRight() const
0320 {
0321     return d->borders.right();
0322 }
0323 
0324 int Decoration::resizeOnlyBorderRight() const
0325 {
0326     return d->resizeOnlyBorders.right();
0327 }
0328 
0329 int Decoration::borderTop() const
0330 {
0331     return d->borders.top();
0332 }
0333 
0334 int Decoration::resizeOnlyBorderTop() const
0335 {
0336     return d->resizeOnlyBorders.top();
0337 }
0338 
0339 int Decoration::borderBottom() const
0340 {
0341     return d->borders.bottom();
0342 }
0343 
0344 int Decoration::resizeOnlyBorderBottom() const
0345 {
0346     return d->resizeOnlyBorders.bottom();
0347 }
0348 
0349 QSize Decoration::size() const
0350 {
0351     const QMargins &b = d->borders;
0352     return QSize(d->client->width() + b.left() + b.right(), //
0353                  (d->client->isShaded() ? 0 : d->client->height()) + b.top() + b.bottom());
0354 }
0355 
0356 QRect Decoration::rect() const
0357 {
0358     return QRect(QPoint(0, 0), size());
0359 }
0360 
0361 bool Decoration::event(QEvent *event)
0362 {
0363     switch (event->type()) {
0364     case QEvent::HoverEnter:
0365         hoverEnterEvent(static_cast<QHoverEvent *>(event));
0366         return true;
0367     case QEvent::HoverLeave:
0368         hoverLeaveEvent(static_cast<QHoverEvent *>(event));
0369         return true;
0370     case QEvent::HoverMove:
0371         hoverMoveEvent(static_cast<QHoverEvent *>(event));
0372         return true;
0373     case QEvent::MouseButtonPress:
0374         mousePressEvent(static_cast<QMouseEvent *>(event));
0375         return true;
0376     case QEvent::MouseButtonRelease:
0377         mouseReleaseEvent(static_cast<QMouseEvent *>(event));
0378         return true;
0379     case QEvent::MouseMove:
0380         mouseMoveEvent(static_cast<QMouseEvent *>(event));
0381         return true;
0382     case QEvent::Wheel:
0383         wheelEvent(static_cast<QWheelEvent *>(event));
0384         return true;
0385     default:
0386         return QObject::event(event);
0387     }
0388 }
0389 
0390 void Decoration::hoverEnterEvent(QHoverEvent *event)
0391 {
0392     for (DecorationButton *button : d->buttons) {
0393         QCoreApplication::instance()->sendEvent(button, event);
0394     }
0395     auto flooredPos = QPoint(std::floor(event->position().x()), std::floor(event->position().y()));
0396     d->updateSectionUnderMouse(flooredPos);
0397 }
0398 
0399 void Decoration::hoverLeaveEvent(QHoverEvent *event)
0400 {
0401     for (DecorationButton *button : d->buttons) {
0402         QCoreApplication::instance()->sendEvent(button, event);
0403     }
0404     d->setSectionUnderMouse(Qt::NoSection);
0405 }
0406 
0407 void Decoration::hoverMoveEvent(QHoverEvent *event)
0408 {
0409     for (DecorationButton *button : d->buttons) {
0410         if (!button->isEnabled() || !button->isVisible()) {
0411             continue;
0412         }
0413         const bool hovered = button->isHovered();
0414         const bool contains = button->contains(event->position());
0415         if (!hovered && contains) {
0416             QHoverEvent e(QEvent::HoverEnter, event->position(), event->oldPosF(), event->modifiers());
0417             QCoreApplication::instance()->sendEvent(button, &e);
0418         } else if (hovered && !contains) {
0419             QHoverEvent e(QEvent::HoverLeave, event->position(), event->oldPosF(), event->modifiers());
0420             QCoreApplication::instance()->sendEvent(button, &e);
0421         } else if (hovered && contains) {
0422             QCoreApplication::instance()->sendEvent(button, event);
0423         }
0424     }
0425     auto flooredPos = QPoint(std::floor(event->position().x()), std::floor(event->position().y()));
0426     d->updateSectionUnderMouse(flooredPos);
0427 }
0428 
0429 void Decoration::mouseMoveEvent(QMouseEvent *event)
0430 {
0431     for (DecorationButton *button : d->buttons) {
0432         if (button->isPressed()) {
0433             QCoreApplication::instance()->sendEvent(button, event);
0434             return;
0435         }
0436     }
0437     // not handled, take care ourselves
0438 }
0439 
0440 void Decoration::mousePressEvent(QMouseEvent *event)
0441 {
0442     for (DecorationButton *button : d->buttons) {
0443         if (button->isHovered()) {
0444             if (button->acceptedButtons().testFlag(event->button())) {
0445                 QCoreApplication::instance()->sendEvent(button, event);
0446             }
0447             event->setAccepted(true);
0448             return;
0449         }
0450     }
0451 }
0452 
0453 void Decoration::mouseReleaseEvent(QMouseEvent *event)
0454 {
0455     for (DecorationButton *button : d->buttons) {
0456         if (button->isPressed() && button->acceptedButtons().testFlag(event->button())) {
0457             QCoreApplication::instance()->sendEvent(button, event);
0458             return;
0459         }
0460     }
0461     // not handled, take care ourselves
0462     d->updateSectionUnderMouse(event->pos());
0463 }
0464 
0465 void Decoration::wheelEvent(QWheelEvent *event)
0466 {
0467     for (DecorationButton *button : d->buttons) {
0468         if (button->contains(event->position())) {
0469             QCoreApplication::instance()->sendEvent(button, event);
0470             event->setAccepted(true);
0471         }
0472     }
0473 }
0474 
0475 void Decoration::update(const QRect &r)
0476 {
0477     Q_EMIT damaged(r.isNull() ? rect() : r);
0478 }
0479 
0480 void Decoration::update()
0481 {
0482     update(QRect());
0483 }
0484 
0485 void Decoration::setSettings(const std::shared_ptr<DecorationSettings> &settings)
0486 {
0487     d->settings = settings;
0488 }
0489 
0490 std::shared_ptr<DecorationSettings> Decoration::settings() const
0491 {
0492     return d->settings;
0493 }
0494 
0495 } // namespace
0496 
0497 #include "moc_decoration.cpp"