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