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 "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 case DecorationButtonType::Spacer: 0150 setEnabled(false); 0151 break; 0152 default: 0153 // nothing 0154 break; 0155 } 0156 } 0157 0158 void DecorationButton::Private::setHovered(bool set) 0159 { 0160 if (hovered == set) { 0161 return; 0162 } 0163 hovered = set; 0164 Q_EMIT q->hoveredChanged(hovered); 0165 } 0166 0167 void DecorationButton::Private::setEnabled(bool set) 0168 { 0169 if (enabled == set) { 0170 return; 0171 } 0172 enabled = set; 0173 Q_EMIT q->enabledChanged(enabled); 0174 if (!enabled) { 0175 setHovered(false); 0176 if (isPressed()) { 0177 m_pressed = Qt::NoButton; 0178 Q_EMIT q->pressedChanged(false); 0179 } 0180 } 0181 } 0182 0183 void DecorationButton::Private::setVisible(bool set) 0184 { 0185 if (visible == set) { 0186 return; 0187 } 0188 visible = set; 0189 Q_EMIT q->visibilityChanged(set); 0190 if (!visible) { 0191 setHovered(false); 0192 if (isPressed()) { 0193 m_pressed = Qt::NoButton; 0194 Q_EMIT q->pressedChanged(false); 0195 } 0196 } 0197 } 0198 0199 void DecorationButton::Private::setChecked(bool set) 0200 { 0201 if (!checkable || checked == set) { 0202 return; 0203 } 0204 checked = set; 0205 Q_EMIT q->checkedChanged(checked); 0206 } 0207 0208 void DecorationButton::Private::setCheckable(bool set) 0209 { 0210 if (checkable == set) { 0211 return; 0212 } 0213 if (!set) { 0214 setChecked(false); 0215 } 0216 checkable = set; 0217 Q_EMIT q->checkableChanged(checkable); 0218 } 0219 0220 void DecorationButton::Private::setPressed(Qt::MouseButton button, bool pressed) 0221 { 0222 if (pressed) { 0223 m_pressed = m_pressed | button; 0224 } else { 0225 m_pressed = m_pressed & ~button; 0226 } 0227 Q_EMIT q->pressedChanged(isPressed()); 0228 } 0229 0230 void DecorationButton::Private::setAcceptedButtons(Qt::MouseButtons buttons) 0231 { 0232 if (acceptedButtons == buttons) { 0233 return; 0234 } 0235 acceptedButtons = buttons; 0236 Q_EMIT q->acceptedButtonsChanged(acceptedButtons); 0237 } 0238 0239 void DecorationButton::Private::startDoubleClickTimer() 0240 { 0241 if (!doubleClickEnabled) { 0242 return; 0243 } 0244 if (!m_doubleClickTimer) { 0245 m_doubleClickTimer = std::make_unique<QElapsedTimer>(); 0246 } 0247 m_doubleClickTimer->start(); 0248 } 0249 0250 void DecorationButton::Private::invalidateDoubleClickTimer() 0251 { 0252 if (!m_doubleClickTimer) { 0253 return; 0254 } 0255 m_doubleClickTimer->invalidate(); 0256 } 0257 0258 bool DecorationButton::Private::wasDoubleClick() const 0259 { 0260 if (!m_doubleClickTimer || !m_doubleClickTimer->isValid()) { 0261 return false; 0262 } 0263 return !m_doubleClickTimer->hasExpired(QGuiApplication::styleHints()->mouseDoubleClickInterval()); 0264 } 0265 0266 void DecorationButton::Private::setPressAndHold(bool enable) 0267 { 0268 if (pressAndHold == enable) { 0269 return; 0270 } 0271 pressAndHold = enable; 0272 if (!pressAndHold) { 0273 m_pressAndHoldTimer.reset(); 0274 } 0275 } 0276 0277 void DecorationButton::Private::startPressAndHold() 0278 { 0279 if (!pressAndHold) { 0280 return; 0281 } 0282 if (!m_pressAndHoldTimer) { 0283 m_pressAndHoldTimer.reset(new QTimer()); 0284 m_pressAndHoldTimer->setSingleShot(true); 0285 QObject::connect(m_pressAndHoldTimer.get(), &QTimer::timeout, q, [this]() { 0286 q->clicked(Qt::LeftButton); 0287 }); 0288 } 0289 m_pressAndHoldTimer->start(QGuiApplication::styleHints()->mousePressAndHoldInterval()); 0290 } 0291 0292 void DecorationButton::Private::stopPressAndHold() 0293 { 0294 if (m_pressAndHoldTimer) { 0295 m_pressAndHoldTimer->stop(); 0296 } 0297 } 0298 0299 QString DecorationButton::Private::typeToString(DecorationButtonType type) 0300 { 0301 switch (type) { 0302 case DecorationButtonType::Menu: 0303 return i18n("More actions for this window"); 0304 case DecorationButtonType::ApplicationMenu: 0305 return i18n("Application menu"); 0306 case DecorationButtonType::OnAllDesktops: 0307 if (this->q->isChecked()) 0308 return i18n("On one desktop"); 0309 else 0310 return i18n("On all desktops"); 0311 case DecorationButtonType::Minimize: 0312 return i18n("Minimize"); 0313 case DecorationButtonType::Maximize: 0314 if (this->q->isChecked()) 0315 return i18n("Restore"); 0316 else 0317 return i18n("Maximize"); 0318 case DecorationButtonType::Close: 0319 return i18n("Close"); 0320 case DecorationButtonType::ContextHelp: 0321 return i18n("Context help"); 0322 case DecorationButtonType::Shade: 0323 if (this->q->isChecked()) 0324 return i18n("Unshade"); 0325 else 0326 return i18n("Shade"); 0327 case DecorationButtonType::KeepBelow: 0328 if (this->q->isChecked()) 0329 return i18n("Don't keep below other windows"); 0330 else 0331 return i18n("Keep below other windows"); 0332 case DecorationButtonType::KeepAbove: 0333 if (this->q->isChecked()) 0334 return i18n("Don't keep above other windows"); 0335 else 0336 return i18n("Keep above other windows"); 0337 default: 0338 return QString(); 0339 } 0340 } 0341 0342 DecorationButton::DecorationButton(DecorationButtonType type, Decoration *decoration, QObject *parent) 0343 : QObject(parent) 0344 , d(new Private(type, decoration, this)) 0345 { 0346 decoration->d->addButton(this); 0347 connect(this, &DecorationButton::geometryChanged, this, static_cast<void (DecorationButton::*)(const QRectF &)>(&DecorationButton::update)); 0348 auto updateSlot = static_cast<void (DecorationButton::*)()>(&DecorationButton::update); 0349 connect(this, &DecorationButton::hoveredChanged, this, updateSlot); 0350 connect(this, &DecorationButton::hoveredChanged, this, [this](bool hovered) { 0351 if (hovered) { 0352 // TODO: show tooltip if hovered and hide if not 0353 const QString type = this->d->typeToString(this->type()); 0354 this->decoration()->requestShowToolTip(type); 0355 } else { 0356 this->decoration()->requestHideToolTip(); 0357 } 0358 }); 0359 connect(this, &DecorationButton::pressedChanged, this, updateSlot); 0360 connect(this, &DecorationButton::pressedChanged, this, [this](bool pressed) { 0361 if (pressed) { 0362 this->decoration()->requestHideToolTip(); 0363 } 0364 }); 0365 connect(this, &DecorationButton::checkedChanged, this, updateSlot); 0366 connect(this, &DecorationButton::enabledChanged, this, updateSlot); 0367 connect(this, &DecorationButton::visibilityChanged, this, updateSlot); 0368 connect(this, &DecorationButton::hoveredChanged, this, [this](bool hovered) { 0369 if (hovered) { 0370 Q_EMIT pointerEntered(); 0371 } else { 0372 Q_EMIT pointerLeft(); 0373 } 0374 }); 0375 connect(this, &DecorationButton::pressedChanged, this, [this](bool p) { 0376 if (p) { 0377 Q_EMIT pressed(); 0378 } else { 0379 Q_EMIT released(); 0380 } 0381 }); 0382 } 0383 0384 DecorationButton::~DecorationButton() = default; 0385 0386 void DecorationButton::update(const QRectF &rect) 0387 { 0388 decoration()->update(rect.isNull() ? geometry().toRect() : rect.toRect()); 0389 } 0390 0391 void DecorationButton::update() 0392 { 0393 update(QRectF()); 0394 } 0395 0396 QSizeF DecorationButton::size() const 0397 { 0398 return d->geometry.size(); 0399 } 0400 0401 bool DecorationButton::isPressed() const 0402 { 0403 return d->isPressed(); 0404 } 0405 0406 bool DecorationButton::isHovered() const 0407 { 0408 return d->hovered; 0409 } 0410 0411 bool DecorationButton::isEnabled() const 0412 { 0413 return d->enabled; 0414 } 0415 0416 bool DecorationButton::isChecked() const 0417 { 0418 return d->checked; 0419 } 0420 0421 bool DecorationButton::isCheckable() const 0422 { 0423 return d->checkable; 0424 } 0425 0426 bool DecorationButton::isVisible() const 0427 { 0428 return d->visible; 0429 } 0430 0431 QRectF DecorationButton::geometry() const 0432 { 0433 return d->geometry; 0434 } 0435 0436 Decoration *DecorationButton::decoration() const 0437 { 0438 return d->decoration; 0439 } 0440 0441 Qt::MouseButtons DecorationButton::acceptedButtons() const 0442 { 0443 return d->acceptedButtons; 0444 } 0445 0446 DecorationButtonType DecorationButton::type() const 0447 { 0448 return d->type; 0449 } 0450 0451 void DecorationButton::setAcceptedButtons(Qt::MouseButtons value) 0452 { 0453 d->setAcceptedButtons(value); 0454 } 0455 0456 void DecorationButton::setEnabled(bool value) 0457 { 0458 d->setEnabled(value); 0459 } 0460 0461 void DecorationButton::setChecked(bool value) 0462 { 0463 d->setChecked(value); 0464 } 0465 0466 void DecorationButton::setCheckable(bool value) 0467 { 0468 d->setCheckable(value); 0469 } 0470 0471 void DecorationButton::setVisible(bool value) 0472 { 0473 d->setVisible(value); 0474 } 0475 0476 void DecorationButton::setGeometry(const QRectF &geometry) 0477 { 0478 if (d->geometry == geometry) { 0479 return; 0480 } 0481 d->geometry = geometry; 0482 Q_EMIT geometryChanged(d->geometry); 0483 } 0484 0485 bool DecorationButton::contains(const QPointF &pos) const 0486 { 0487 auto flooredPoint = QPoint(std::floor(pos.x()), std::floor(pos.y())); 0488 return d->geometry.toRect().contains(flooredPoint); 0489 } 0490 0491 bool DecorationButton::event(QEvent *event) 0492 { 0493 switch (event->type()) { 0494 case QEvent::HoverEnter: 0495 hoverEnterEvent(static_cast<QHoverEvent *>(event)); 0496 return true; 0497 case QEvent::HoverLeave: 0498 hoverLeaveEvent(static_cast<QHoverEvent *>(event)); 0499 return true; 0500 case QEvent::HoverMove: 0501 hoverMoveEvent(static_cast<QHoverEvent *>(event)); 0502 return true; 0503 case QEvent::MouseButtonPress: 0504 mousePressEvent(static_cast<QMouseEvent *>(event)); 0505 return true; 0506 case QEvent::MouseButtonRelease: 0507 mouseReleaseEvent(static_cast<QMouseEvent *>(event)); 0508 return true; 0509 case QEvent::MouseMove: 0510 mouseMoveEvent(static_cast<QMouseEvent *>(event)); 0511 return true; 0512 case QEvent::Wheel: 0513 wheelEvent(static_cast<QWheelEvent *>(event)); 0514 return true; 0515 default: 0516 return QObject::event(event); 0517 } 0518 } 0519 0520 void DecorationButton::hoverEnterEvent(QHoverEvent *event) 0521 { 0522 if (!d->enabled || !d->visible || !contains(event->position())) { 0523 return; 0524 } 0525 d->setHovered(true); 0526 event->setAccepted(true); 0527 } 0528 0529 void DecorationButton::hoverLeaveEvent(QHoverEvent *event) 0530 { 0531 if (!d->enabled || !d->visible || !d->hovered || contains(event->position())) { 0532 return; 0533 } 0534 d->setHovered(false); 0535 event->setAccepted(true); 0536 } 0537 0538 void DecorationButton::hoverMoveEvent(QHoverEvent *event) 0539 { 0540 Q_UNUSED(event) 0541 } 0542 0543 void DecorationButton::mouseMoveEvent(QMouseEvent *event) 0544 { 0545 if (!d->enabled || !d->visible || !d->hovered) { 0546 return; 0547 } 0548 if (!contains(event->position())) { 0549 d->setHovered(false); 0550 event->setAccepted(true); 0551 } 0552 } 0553 0554 void DecorationButton::mousePressEvent(QMouseEvent *event) 0555 { 0556 if (!d->enabled || !d->visible || !contains(event->position()) || !d->acceptedButtons.testFlag(event->button())) { 0557 return; 0558 } 0559 d->setPressed(event->button(), true); 0560 event->setAccepted(true); 0561 if (d->doubleClickEnabled && event->button() == Qt::LeftButton) { 0562 // check for double click 0563 if (d->wasDoubleClick()) { 0564 event->setAccepted(true); 0565 Q_EMIT doubleClicked(); 0566 } 0567 d->invalidateDoubleClickTimer(); 0568 } 0569 if (d->pressAndHold && event->button() == Qt::LeftButton) { 0570 d->startPressAndHold(); 0571 } 0572 } 0573 0574 void DecorationButton::mouseReleaseEvent(QMouseEvent *event) 0575 { 0576 if (!d->enabled || !d->visible || !d->isPressed(event->button())) { 0577 return; 0578 } 0579 if (contains(event->position())) { 0580 if (!d->pressAndHold || event->button() != Qt::LeftButton) { 0581 Q_EMIT clicked(event->button()); 0582 } else { 0583 d->stopPressAndHold(); 0584 } 0585 } 0586 d->setPressed(event->button(), false); 0587 event->setAccepted(true); 0588 0589 if (d->doubleClickEnabled && event->button() == Qt::LeftButton) { 0590 d->startDoubleClickTimer(); 0591 } 0592 } 0593 0594 void DecorationButton::wheelEvent(QWheelEvent *event) 0595 { 0596 Q_UNUSED(event) 0597 } 0598 0599 } 0600 0601 #include "moc_decorationbutton.cpp"