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 "decorationbutton.h"
0007 #include "decoratedclient.h"
0008 #include "decoration.h"
0009 #include "decoration_p.h"
0010 #include "decorationbutton_p.h"
0011 #include "decorationsettings.h"
0012 
0013 #include <KLocalizedString>
0014 
0015 #include <QDebug>
0016 #include <QElapsedTimer>
0017 #include <QGuiApplication>
0018 #include <QHoverEvent>
0019 #include <QStyleHints>
0020 #include <QTimer>
0021 
0022 #include <cmath>
0023 
0024 namespace KDecoration2
0025 {
0026 #ifndef K_DOXYGEN
0027 uint qHash(const DecorationButtonType &type)
0028 {
0029     return static_cast<uint>(type);
0030 }
0031 #endif
0032 
0033 DecorationButton::Private::Private(DecorationButtonType type, const QPointer<Decoration> &decoration, DecorationButton *parent)
0034     : decoration(decoration)
0035     , type(type)
0036     , hovered(false)
0037     , enabled(true)
0038     , checkable(false)
0039     , checked(false)
0040     , visible(true)
0041     , acceptedButtons(Qt::LeftButton)
0042     , doubleClickEnabled(false)
0043     , pressAndHold(false)
0044     , q(parent)
0045     , m_pressed(Qt::NoButton)
0046 {
0047     init();
0048 }
0049 
0050 DecorationButton::Private::~Private() = default;
0051 
0052 void DecorationButton::Private::init()
0053 {
0054     auto clientPtr = decoration->client().toStrongRef();
0055     Q_ASSERT(clientPtr);
0056     auto c = clientPtr.data();
0057     auto settings = decoration->settings();
0058     switch (type) {
0059     case DecorationButtonType::Menu:
0060         QObject::connect(
0061             q,
0062             &DecorationButton::clicked,
0063             decoration.data(),
0064             [this](Qt::MouseButton button) {
0065                 Q_UNUSED(button)
0066                 decoration->requestShowWindowMenu(q->geometry().toRect());
0067             },
0068             Qt::QueuedConnection);
0069         QObject::connect(q, &DecorationButton::doubleClicked, decoration.data(), &Decoration::requestClose, Qt::QueuedConnection);
0070         QObject::connect(
0071             settings.data(),
0072             &DecorationSettings::closeOnDoubleClickOnMenuChanged,
0073             q,
0074             [this](bool enabled) {
0075                 doubleClickEnabled = enabled;
0076                 setPressAndHold(enabled);
0077             },
0078             Qt::QueuedConnection);
0079         doubleClickEnabled = settings->isCloseOnDoubleClickOnMenu();
0080         setPressAndHold(settings->isCloseOnDoubleClickOnMenu());
0081         setAcceptedButtons(Qt::LeftButton | Qt::RightButton);
0082         break;
0083     case DecorationButtonType::ApplicationMenu:
0084         setVisible(c->hasApplicationMenu());
0085         setCheckable(true); // will be "checked" whilst the menu is opened
0086         // FIXME TODO connect directly and figure out the button geometry/offset stuff
0087         QObject::connect(
0088             q,
0089             &DecorationButton::clicked,
0090             decoration.data(),
0091             [this] {
0092                 decoration->requestShowApplicationMenu(q->geometry().toRect(), 0 /* actionId */);
0093             },
0094             Qt::QueuedConnection); //&Decoration::requestShowApplicationMenu, Qt::QueuedConnection);
0095         QObject::connect(c, &DecoratedClient::hasApplicationMenuChanged, q, &DecorationButton::setVisible);
0096         QObject::connect(c, &DecoratedClient::applicationMenuActiveChanged, q, &DecorationButton::setChecked);
0097         break;
0098     case DecorationButtonType::OnAllDesktops:
0099         setVisible(settings->isOnAllDesktopsAvailable());
0100         setCheckable(true);
0101         setChecked(c->isOnAllDesktops());
0102         QObject::connect(q, &DecorationButton::clicked, decoration.data(), &Decoration::requestToggleOnAllDesktops, Qt::QueuedConnection);
0103         QObject::connect(settings.data(), &DecorationSettings::onAllDesktopsAvailableChanged, q, &DecorationButton::setVisible);
0104         QObject::connect(c, &DecoratedClient::onAllDesktopsChanged, q, &DecorationButton::setChecked);
0105         break;
0106     case DecorationButtonType::Minimize:
0107         setEnabled(c->isMinimizeable());
0108         QObject::connect(q, &DecorationButton::clicked, decoration.data(), &Decoration::requestMinimize, Qt::QueuedConnection);
0109         QObject::connect(c, &DecoratedClient::minimizeableChanged, q, &DecorationButton::setEnabled);
0110         break;
0111     case DecorationButtonType::Maximize:
0112         setEnabled(c->isMaximizeable());
0113         setCheckable(true);
0114         setChecked(c->isMaximized());
0115         setAcceptedButtons(Qt::LeftButton | Qt::MiddleButton | Qt::RightButton);
0116         QObject::connect(q, &DecorationButton::clicked, decoration.data(), &Decoration::requestToggleMaximization, Qt::QueuedConnection);
0117         QObject::connect(c, &DecoratedClient::maximizeableChanged, q, &DecorationButton::setEnabled);
0118         QObject::connect(c, &DecoratedClient::maximizedChanged, q, &DecorationButton::setChecked);
0119         break;
0120     case DecorationButtonType::Close:
0121         setEnabled(c->isCloseable());
0122         QObject::connect(q, &DecorationButton::clicked, decoration.data(), &Decoration::requestClose, Qt::QueuedConnection);
0123         QObject::connect(c, &DecoratedClient::closeableChanged, q, &DecorationButton::setEnabled);
0124         break;
0125     case DecorationButtonType::ContextHelp:
0126         setVisible(c->providesContextHelp());
0127         QObject::connect(q, &DecorationButton::clicked, decoration.data(), &Decoration::requestContextHelp, Qt::QueuedConnection);
0128         QObject::connect(c, &DecoratedClient::providesContextHelpChanged, q, &DecorationButton::setVisible);
0129         break;
0130     case DecorationButtonType::KeepAbove:
0131         setCheckable(true);
0132         setChecked(c->isKeepAbove());
0133         QObject::connect(q, &DecorationButton::clicked, decoration.data(), &Decoration::requestToggleKeepAbove, Qt::QueuedConnection);
0134         QObject::connect(c, &DecoratedClient::keepAboveChanged, q, &DecorationButton::setChecked);
0135         break;
0136     case DecorationButtonType::KeepBelow:
0137         setCheckable(true);
0138         setChecked(c->isKeepBelow());
0139         QObject::connect(q, &DecorationButton::clicked, decoration.data(), &Decoration::requestToggleKeepBelow, Qt::QueuedConnection);
0140         QObject::connect(c, &DecoratedClient::keepBelowChanged, q, &DecorationButton::setChecked);
0141         break;
0142     case DecorationButtonType::Shade:
0143         setEnabled(c->isShadeable());
0144         setCheckable(true);
0145         setChecked(c->isShaded());
0146         QObject::connect(q, &DecorationButton::clicked, decoration.data(), &Decoration::requestToggleShade, Qt::QueuedConnection);
0147         QObject::connect(c, &DecoratedClient::shadedChanged, q, &DecorationButton::setChecked);
0148         QObject::connect(c, &DecoratedClient::shadeableChanged, q, &DecorationButton::setEnabled);
0149         break;
0150     default:
0151         // nothing
0152         break;
0153     }
0154 }
0155 
0156 void DecorationButton::Private::setHovered(bool set)
0157 {
0158     if (hovered == set) {
0159         return;
0160     }
0161     hovered = set;
0162     Q_EMIT q->hoveredChanged(hovered);
0163 }
0164 
0165 void DecorationButton::Private::setEnabled(bool set)
0166 {
0167     if (enabled == set) {
0168         return;
0169     }
0170     enabled = set;
0171     Q_EMIT q->enabledChanged(enabled);
0172     if (!enabled) {
0173         setHovered(false);
0174         if (isPressed()) {
0175             m_pressed = Qt::NoButton;
0176             Q_EMIT q->pressedChanged(false);
0177         }
0178     }
0179 }
0180 
0181 void DecorationButton::Private::setVisible(bool set)
0182 {
0183     if (visible == set) {
0184         return;
0185     }
0186     visible = set;
0187     Q_EMIT q->visibilityChanged(set);
0188     if (!visible) {
0189         setHovered(false);
0190         if (isPressed()) {
0191             m_pressed = Qt::NoButton;
0192             Q_EMIT q->pressedChanged(false);
0193         }
0194     }
0195 }
0196 
0197 void DecorationButton::Private::setChecked(bool set)
0198 {
0199     if (!checkable || checked == set) {
0200         return;
0201     }
0202     checked = set;
0203     Q_EMIT q->checkedChanged(checked);
0204 }
0205 
0206 void DecorationButton::Private::setCheckable(bool set)
0207 {
0208     if (checkable == set) {
0209         return;
0210     }
0211     if (!set) {
0212         setChecked(false);
0213     }
0214     checkable = set;
0215     Q_EMIT q->checkableChanged(checkable);
0216 }
0217 
0218 void DecorationButton::Private::setPressed(Qt::MouseButton button, bool pressed)
0219 {
0220     if (pressed) {
0221         m_pressed = m_pressed | button;
0222     } else {
0223         m_pressed = m_pressed & ~button;
0224     }
0225     Q_EMIT q->pressedChanged(isPressed());
0226 }
0227 
0228 void DecorationButton::Private::setAcceptedButtons(Qt::MouseButtons buttons)
0229 {
0230     if (acceptedButtons == buttons) {
0231         return;
0232     }
0233     acceptedButtons = buttons;
0234     Q_EMIT q->acceptedButtonsChanged(acceptedButtons);
0235 }
0236 
0237 void DecorationButton::Private::startDoubleClickTimer()
0238 {
0239     if (!doubleClickEnabled) {
0240         return;
0241     }
0242     if (m_doubleClickTimer.isNull()) {
0243         m_doubleClickTimer.reset(new QElapsedTimer());
0244     }
0245     m_doubleClickTimer->start();
0246 }
0247 
0248 void DecorationButton::Private::invalidateDoubleClickTimer()
0249 {
0250     if (m_doubleClickTimer.isNull()) {
0251         return;
0252     }
0253     m_doubleClickTimer->invalidate();
0254 }
0255 
0256 bool DecorationButton::Private::wasDoubleClick() const
0257 {
0258     if (m_doubleClickTimer.isNull() || !m_doubleClickTimer->isValid()) {
0259         return false;
0260     }
0261     return !m_doubleClickTimer->hasExpired(QGuiApplication::styleHints()->mouseDoubleClickInterval());
0262 }
0263 
0264 void DecorationButton::Private::setPressAndHold(bool enable)
0265 {
0266     if (pressAndHold == enable) {
0267         return;
0268     }
0269     pressAndHold = enable;
0270     if (!pressAndHold) {
0271         m_pressAndHoldTimer.reset();
0272     }
0273 }
0274 
0275 void DecorationButton::Private::startPressAndHold()
0276 {
0277     if (!pressAndHold) {
0278         return;
0279     }
0280     if (m_pressAndHoldTimer.isNull()) {
0281         m_pressAndHoldTimer.reset(new QTimer());
0282         m_pressAndHoldTimer->setSingleShot(true);
0283         QObject::connect(m_pressAndHoldTimer.data(), &QTimer::timeout, q, [this]() {
0284             q->clicked(Qt::LeftButton);
0285         });
0286     }
0287     m_pressAndHoldTimer->start(QGuiApplication::styleHints()->mousePressAndHoldInterval());
0288 }
0289 
0290 void DecorationButton::Private::stopPressAndHold()
0291 {
0292     if (!m_pressAndHoldTimer.isNull()) {
0293         m_pressAndHoldTimer->stop();
0294     }
0295 }
0296 
0297 QString DecorationButton::Private::typeToString(DecorationButtonType type)
0298 {
0299     switch (type) {
0300     case DecorationButtonType::Menu:
0301         return i18n("More actions for this window");
0302     case DecorationButtonType::ApplicationMenu:
0303         return i18n("Application menu");
0304     case DecorationButtonType::OnAllDesktops:
0305         if (this->q->isChecked())
0306             return i18n("On one desktop");
0307         else
0308             return i18n("On all desktops");
0309     case DecorationButtonType::Minimize:
0310         return i18n("Minimize");
0311     case DecorationButtonType::Maximize:
0312         if (this->q->isChecked())
0313             return i18n("Restore");
0314         else
0315             return i18n("Maximize");
0316     case DecorationButtonType::Close:
0317         return i18n("Close");
0318     case DecorationButtonType::ContextHelp:
0319         return i18n("Context help");
0320     case DecorationButtonType::Shade:
0321         if (this->q->isChecked())
0322             return i18n("Unshade");
0323         else
0324             return i18n("Shade");
0325     case DecorationButtonType::KeepBelow:
0326         if (this->q->isChecked())
0327             return i18n("Don't keep below other windows");
0328         else
0329             return i18n("Keep below other windows");
0330     case DecorationButtonType::KeepAbove:
0331         if (this->q->isChecked())
0332             return i18n("Don't keep above other windows");
0333         else
0334             return i18n("Keep above other windows");
0335     default:
0336         return QString();
0337     }
0338 }
0339 
0340 DecorationButton::DecorationButton(DecorationButtonType type, const QPointer<Decoration> &decoration, QObject *parent)
0341     : QObject(parent)
0342     , d(new Private(type, decoration, this))
0343 {
0344     decoration->d->addButton(this);
0345     connect(this, &DecorationButton::geometryChanged, this, static_cast<void (DecorationButton::*)(const QRectF &)>(&DecorationButton::update));
0346     auto updateSlot = static_cast<void (DecorationButton::*)()>(&DecorationButton::update);
0347     connect(this, &DecorationButton::hoveredChanged, this, updateSlot);
0348     connect(this, &DecorationButton::hoveredChanged, this, [this](bool hovered) {
0349         if (hovered) {
0350             // TODO: show tooltip if hovered and hide if not
0351             const QString type = this->d->typeToString(this->type());
0352             this->decoration()->requestShowToolTip(type);
0353         } else {
0354             this->decoration()->requestHideToolTip();
0355         }
0356     });
0357     connect(this, &DecorationButton::pressedChanged, this, updateSlot);
0358     connect(this, &DecorationButton::pressedChanged, this, [this](bool pressed) {
0359         if (pressed) {
0360             this->decoration()->requestHideToolTip();
0361         }
0362     });
0363     connect(this, &DecorationButton::checkedChanged, this, updateSlot);
0364     connect(this, &DecorationButton::enabledChanged, this, updateSlot);
0365     connect(this, &DecorationButton::visibilityChanged, this, updateSlot);
0366     connect(this, &DecorationButton::hoveredChanged, this, [this](bool hovered) {
0367         if (hovered) {
0368             Q_EMIT pointerEntered();
0369         } else {
0370             Q_EMIT pointerLeft();
0371         }
0372     });
0373     connect(this, &DecorationButton::pressedChanged, this, [this](bool p) {
0374         if (p) {
0375             Q_EMIT pressed();
0376         } else {
0377             Q_EMIT released();
0378         }
0379     });
0380 }
0381 
0382 DecorationButton::~DecorationButton() = default;
0383 
0384 void DecorationButton::update(const QRectF &rect)
0385 {
0386     decoration()->update(rect.isNull() ? geometry().toRect() : rect.toRect());
0387 }
0388 
0389 void DecorationButton::update()
0390 {
0391     update(QRectF());
0392 }
0393 
0394 QSizeF DecorationButton::size() const
0395 {
0396     return d->geometry.size();
0397 }
0398 
0399 bool DecorationButton::isPressed() const
0400 {
0401     return d->isPressed();
0402 }
0403 
0404 #define DELEGATE(name, variableName, type)                                                                                                                     \
0405     type DecorationButton::name() const                                                                                                                        \
0406     {                                                                                                                                                          \
0407         return d->variableName;                                                                                                                                \
0408     }
0409 
0410 DELEGATE(isHovered, hovered, bool)
0411 DELEGATE(isEnabled, enabled, bool)
0412 DELEGATE(isChecked, checked, bool)
0413 DELEGATE(isCheckable, checkable, bool)
0414 DELEGATE(isVisible, visible, bool)
0415 
0416 #define DELEGATE2(name, type) DELEGATE(name, name, type)
0417 DELEGATE2(geometry, QRectF)
0418 DELEGATE2(decoration, QPointer<Decoration>)
0419 DELEGATE2(acceptedButtons, Qt::MouseButtons)
0420 DELEGATE2(type, DecorationButtonType)
0421 
0422 #undef DELEGATE2
0423 #undef DELEGATE
0424 
0425 #define DELEGATE(name, type)                                                                                                                                   \
0426     void DecorationButton::name(type a)                                                                                                                        \
0427     {                                                                                                                                                          \
0428         d->name(a);                                                                                                                                            \
0429     }
0430 
0431 DELEGATE(setAcceptedButtons, Qt::MouseButtons)
0432 DELEGATE(setEnabled, bool)
0433 DELEGATE(setChecked, bool)
0434 DELEGATE(setCheckable, bool)
0435 DELEGATE(setVisible, bool)
0436 
0437 #undef DELEGATE
0438 
0439 #define DELEGATE(name, variableName, type)                                                                                                                     \
0440     void DecorationButton::name(type a)                                                                                                                        \
0441     {                                                                                                                                                          \
0442         if (d->variableName == a) {                                                                                                                            \
0443             return;                                                                                                                                            \
0444         }                                                                                                                                                      \
0445         d->variableName = a;                                                                                                                                   \
0446         Q_EMIT variableName##Changed(d->variableName);                                                                                                         \
0447     }
0448 
0449 DELEGATE(setGeometry, geometry, const QRectF &)
0450 
0451 #undef DELEGATE
0452 
0453 bool DecorationButton::contains(const QPointF &pos) const
0454 {
0455     auto flooredPoint = QPoint(std::floor(pos.x()), std::floor(pos.y()));
0456     return d->geometry.toRect().contains(flooredPoint);
0457 }
0458 
0459 bool DecorationButton::event(QEvent *event)
0460 {
0461     switch (event->type()) {
0462     case QEvent::HoverEnter:
0463         hoverEnterEvent(static_cast<QHoverEvent *>(event));
0464         return true;
0465     case QEvent::HoverLeave:
0466         hoverLeaveEvent(static_cast<QHoverEvent *>(event));
0467         return true;
0468     case QEvent::HoverMove:
0469         hoverMoveEvent(static_cast<QHoverEvent *>(event));
0470         return true;
0471     case QEvent::MouseButtonPress:
0472         mousePressEvent(static_cast<QMouseEvent *>(event));
0473         return true;
0474     case QEvent::MouseButtonRelease:
0475         mouseReleaseEvent(static_cast<QMouseEvent *>(event));
0476         return true;
0477     case QEvent::MouseMove:
0478         mouseMoveEvent(static_cast<QMouseEvent *>(event));
0479         return true;
0480     case QEvent::Wheel:
0481         wheelEvent(static_cast<QWheelEvent *>(event));
0482         return true;
0483     default:
0484         return QObject::event(event);
0485     }
0486 }
0487 
0488 void DecorationButton::hoverEnterEvent(QHoverEvent *event)
0489 {
0490     if (!d->enabled || !d->visible || !contains(event->posF())) {
0491         return;
0492     }
0493     d->setHovered(true);
0494     event->setAccepted(true);
0495 }
0496 
0497 void DecorationButton::hoverLeaveEvent(QHoverEvent *event)
0498 {
0499     if (!d->enabled || !d->visible || !d->hovered || contains(event->posF())) {
0500         return;
0501     }
0502     d->setHovered(false);
0503     event->setAccepted(true);
0504 }
0505 
0506 void DecorationButton::hoverMoveEvent(QHoverEvent *event)
0507 {
0508     Q_UNUSED(event)
0509 }
0510 
0511 void DecorationButton::mouseMoveEvent(QMouseEvent *event)
0512 {
0513     if (!d->enabled || !d->visible || !d->hovered) {
0514         return;
0515     }
0516     if (!contains(event->localPos())) {
0517         d->setHovered(false);
0518         event->setAccepted(true);
0519     }
0520 }
0521 
0522 void DecorationButton::mousePressEvent(QMouseEvent *event)
0523 {
0524     if (!d->enabled || !d->visible || !contains(event->localPos()) || !d->acceptedButtons.testFlag(event->button())) {
0525         return;
0526     }
0527     d->setPressed(event->button(), true);
0528     event->setAccepted(true);
0529     if (d->doubleClickEnabled && event->button() == Qt::LeftButton) {
0530         // check for double click
0531         if (d->wasDoubleClick()) {
0532             event->setAccepted(true);
0533             Q_EMIT doubleClicked();
0534         }
0535         d->invalidateDoubleClickTimer();
0536     }
0537     if (d->pressAndHold && event->button() == Qt::LeftButton) {
0538         d->startPressAndHold();
0539     }
0540 }
0541 
0542 void DecorationButton::mouseReleaseEvent(QMouseEvent *event)
0543 {
0544     if (!d->enabled || !d->visible || !d->isPressed(event->button())) {
0545         return;
0546     }
0547     if (contains(event->localPos())) {
0548         if (!d->pressAndHold || event->button() != Qt::LeftButton) {
0549             Q_EMIT clicked(event->button());
0550         } else {
0551             d->stopPressAndHold();
0552         }
0553     }
0554     d->setPressed(event->button(), false);
0555     event->setAccepted(true);
0556 
0557     if (d->doubleClickEnabled && event->button() == Qt::LeftButton) {
0558         d->startDoubleClickTimer();
0559     }
0560 }
0561 
0562 void DecorationButton::wheelEvent(QWheelEvent *event)
0563 {
0564     Q_UNUSED(event)
0565 }
0566 
0567 }