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 }