KDecoration2

decorationbutton.cpp
1/*
2 * SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
3 *
4 * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
5 */
6#include "decorationbutton.h"
7#include "decoratedclient.h"
8#include "decoration.h"
9#include "decoration_p.h"
10#include "decorationbutton_p.h"
11#include "decorationsettings.h"
12
13#include <KLocalizedString>
14
15#include <QDebug>
16#include <QElapsedTimer>
17#include <QGuiApplication>
18#include <QHoverEvent>
19#include <QStyleHints>
20#include <QTimer>
21
22#include <cmath>
23
24#ifndef K_DOXYGEN
25size_t qHash(const KDecoration2::DecorationButtonType &type, size_t seed)
26{
27 return qHash(uint(type), seed);
28}
29#endif
30
31namespace KDecoration2
32{
33
34DecorationButton::Private::Private(DecorationButtonType type, const QPointer<Decoration> &decoration, DecorationButton *parent)
35 : decoration(decoration)
36 , type(type)
37 , hovered(false)
38 , enabled(true)
39 , checkable(false)
40 , checked(false)
41 , visible(true)
42 , acceptedButtons(Qt::LeftButton)
43 , doubleClickEnabled(false)
44 , pressAndHold(false)
45 , q(parent)
46 , m_pressed(Qt::NoButton)
47{
48 init();
49}
50
51DecorationButton::Private::~Private() = default;
52
53void DecorationButton::Private::init()
54{
55 auto c = decoration->client();
56 auto settings = decoration->settings();
57 switch (type) {
58 case DecorationButtonType::Menu:
60 q,
61 &DecorationButton::clicked,
62 decoration.data(),
63 [this](Qt::MouseButton button) {
64 Q_UNUSED(button)
65 decoration->requestShowWindowMenu(q->geometry().toRect());
66 },
68 QObject::connect(q, &DecorationButton::doubleClicked, decoration.data(), &Decoration::requestClose, Qt::QueuedConnection);
70 settings.get(),
71 &DecorationSettings::closeOnDoubleClickOnMenuChanged,
72 q,
73 [this](bool enabled) {
74 doubleClickEnabled = enabled;
75 setPressAndHold(enabled);
76 },
78 doubleClickEnabled = settings->isCloseOnDoubleClickOnMenu();
79 setPressAndHold(settings->isCloseOnDoubleClickOnMenu());
80 setAcceptedButtons(Qt::LeftButton | Qt::RightButton);
81 break;
82 case DecorationButtonType::ApplicationMenu:
83 setVisible(c->hasApplicationMenu());
84 setCheckable(true); // will be "checked" whilst the menu is opened
85 // FIXME TODO connect directly and figure out the button geometry/offset stuff
87 q,
88 &DecorationButton::clicked,
89 decoration.data(),
90 [this] {
91 decoration->requestShowApplicationMenu(q->geometry().toRect(), 0 /* actionId */);
92 },
93 Qt::QueuedConnection); //&Decoration::requestShowApplicationMenu, Qt::QueuedConnection);
94 QObject::connect(c, &DecoratedClient::hasApplicationMenuChanged, q, &DecorationButton::setVisible);
95 QObject::connect(c, &DecoratedClient::applicationMenuActiveChanged, q, &DecorationButton::setChecked);
96 break;
97 case DecorationButtonType::OnAllDesktops:
98 setVisible(settings->isOnAllDesktopsAvailable());
99 setCheckable(true);
100 setChecked(c->isOnAllDesktops());
101 QObject::connect(q, &DecorationButton::clicked, decoration.data(), &Decoration::requestToggleOnAllDesktops, Qt::QueuedConnection);
102 QObject::connect(settings.get(), &DecorationSettings::onAllDesktopsAvailableChanged, q, &DecorationButton::setVisible);
103 QObject::connect(c, &DecoratedClient::onAllDesktopsChanged, q, &DecorationButton::setChecked);
104 break;
105 case DecorationButtonType::Minimize:
106 setEnabled(c->isMinimizeable());
107 QObject::connect(q, &DecorationButton::clicked, decoration.data(), &Decoration::requestMinimize, Qt::QueuedConnection);
108 QObject::connect(c, &DecoratedClient::minimizeableChanged, q, &DecorationButton::setEnabled);
109 break;
110 case DecorationButtonType::Maximize:
111 setEnabled(c->isMaximizeable());
112 setCheckable(true);
113 setChecked(c->isMaximized());
114 setAcceptedButtons(Qt::LeftButton | Qt::MiddleButton | Qt::RightButton);
115 QObject::connect(q, &DecorationButton::clicked, decoration.data(), &Decoration::requestToggleMaximization, Qt::QueuedConnection);
116 QObject::connect(c, &DecoratedClient::maximizeableChanged, q, &DecorationButton::setEnabled);
117 QObject::connect(c, &DecoratedClient::maximizedChanged, q, &DecorationButton::setChecked);
118 break;
119 case DecorationButtonType::Close:
120 setEnabled(c->isCloseable());
121 QObject::connect(q, &DecorationButton::clicked, decoration.data(), &Decoration::requestClose, Qt::QueuedConnection);
122 QObject::connect(c, &DecoratedClient::closeableChanged, q, &DecorationButton::setEnabled);
123 break;
124 case DecorationButtonType::ContextHelp:
125 setVisible(c->providesContextHelp());
126 QObject::connect(q, &DecorationButton::clicked, decoration.data(), &Decoration::requestContextHelp, Qt::QueuedConnection);
127 QObject::connect(c, &DecoratedClient::providesContextHelpChanged, q, &DecorationButton::setVisible);
128 break;
129 case DecorationButtonType::KeepAbove:
130 setCheckable(true);
131 setChecked(c->isKeepAbove());
132 QObject::connect(q, &DecorationButton::clicked, decoration.data(), &Decoration::requestToggleKeepAbove, Qt::QueuedConnection);
133 QObject::connect(c, &DecoratedClient::keepAboveChanged, q, &DecorationButton::setChecked);
134 break;
135 case DecorationButtonType::KeepBelow:
136 setCheckable(true);
137 setChecked(c->isKeepBelow());
138 QObject::connect(q, &DecorationButton::clicked, decoration.data(), &Decoration::requestToggleKeepBelow, Qt::QueuedConnection);
139 QObject::connect(c, &DecoratedClient::keepBelowChanged, q, &DecorationButton::setChecked);
140 break;
141 case DecorationButtonType::Shade:
142 setEnabled(c->isShadeable());
143 setCheckable(true);
144 setChecked(c->isShaded());
145 QObject::connect(q, &DecorationButton::clicked, decoration.data(), &Decoration::requestToggleShade, Qt::QueuedConnection);
146 QObject::connect(c, &DecoratedClient::shadedChanged, q, &DecorationButton::setChecked);
147 QObject::connect(c, &DecoratedClient::shadeableChanged, q, &DecorationButton::setEnabled);
148 break;
149 case DecorationButtonType::Spacer:
150 setEnabled(false);
151 break;
152 default:
153 // nothing
154 break;
155 }
156}
157
158void DecorationButton::Private::setHovered(bool set)
159{
160 if (hovered == set) {
161 return;
162 }
163 hovered = set;
164 Q_EMIT q->hoveredChanged(hovered);
165}
166
167void DecorationButton::Private::setEnabled(bool set)
168{
169 if (enabled == set) {
170 return;
171 }
172 enabled = set;
173 Q_EMIT q->enabledChanged(enabled);
174 if (!enabled) {
175 setHovered(false);
176 if (isPressed()) {
177 m_pressed = Qt::NoButton;
178 Q_EMIT q->pressedChanged(false);
179 }
180 }
181}
182
183void DecorationButton::Private::setVisible(bool set)
184{
185 if (visible == set) {
186 return;
187 }
188 visible = set;
189 Q_EMIT q->visibilityChanged(set);
190 if (!visible) {
191 setHovered(false);
192 if (isPressed()) {
193 m_pressed = Qt::NoButton;
194 Q_EMIT q->pressedChanged(false);
195 }
196 }
197}
198
199void DecorationButton::Private::setChecked(bool set)
200{
201 if (!checkable || checked == set) {
202 return;
203 }
204 checked = set;
205 Q_EMIT q->checkedChanged(checked);
206}
207
208void DecorationButton::Private::setCheckable(bool set)
209{
210 if (checkable == set) {
211 return;
212 }
213 if (!set) {
214 setChecked(false);
215 }
216 checkable = set;
217 Q_EMIT q->checkableChanged(checkable);
218}
219
220void DecorationButton::Private::setPressed(Qt::MouseButton button, bool pressed)
221{
222 if (pressed) {
223 m_pressed = m_pressed | button;
224 } else {
225 m_pressed = m_pressed & ~button;
226 }
227 Q_EMIT q->pressedChanged(isPressed());
228}
229
230void DecorationButton::Private::setAcceptedButtons(Qt::MouseButtons buttons)
231{
232 if (acceptedButtons == buttons) {
233 return;
234 }
235 acceptedButtons = buttons;
236 Q_EMIT q->acceptedButtonsChanged(acceptedButtons);
237}
238
239void DecorationButton::Private::startDoubleClickTimer()
240{
241 if (!doubleClickEnabled) {
242 return;
243 }
244 if (!m_doubleClickTimer) {
245 m_doubleClickTimer = std::make_unique<QElapsedTimer>();
246 }
247 m_doubleClickTimer->start();
248}
249
250void DecorationButton::Private::invalidateDoubleClickTimer()
251{
252 if (!m_doubleClickTimer) {
253 return;
254 }
255 m_doubleClickTimer->invalidate();
256}
257
258bool DecorationButton::Private::wasDoubleClick() const
259{
260 if (!m_doubleClickTimer || !m_doubleClickTimer->isValid()) {
261 return false;
262 }
263 return !m_doubleClickTimer->hasExpired(QGuiApplication::styleHints()->mouseDoubleClickInterval());
264}
265
266void DecorationButton::Private::setPressAndHold(bool enable)
267{
268 if (pressAndHold == enable) {
269 return;
270 }
271 pressAndHold = enable;
272 if (!pressAndHold) {
273 m_pressAndHoldTimer.reset();
274 }
275}
276
277void DecorationButton::Private::startPressAndHold()
278{
279 if (!pressAndHold) {
280 return;
281 }
282 if (!m_pressAndHoldTimer) {
283 m_pressAndHoldTimer.reset(new QTimer());
284 m_pressAndHoldTimer->setSingleShot(true);
285 QObject::connect(m_pressAndHoldTimer.get(), &QTimer::timeout, q, [this]() {
286 q->clicked(Qt::LeftButton);
287 });
288 }
289 m_pressAndHoldTimer->start(QGuiApplication::styleHints()->mousePressAndHoldInterval());
290}
291
292void DecorationButton::Private::stopPressAndHold()
293{
294 if (m_pressAndHoldTimer) {
295 m_pressAndHoldTimer->stop();
296 }
297}
298
299QString DecorationButton::Private::typeToString(DecorationButtonType type)
300{
301 switch (type) {
302 case DecorationButtonType::Menu:
303 return i18n("More actions for this window");
304 case DecorationButtonType::ApplicationMenu:
305 return i18n("Application menu");
306 case DecorationButtonType::OnAllDesktops:
307 if (this->q->isChecked())
308 return i18n("On one desktop");
309 else
310 return i18n("On all desktops");
311 case DecorationButtonType::Minimize:
312 return i18n("Minimize");
313 case DecorationButtonType::Maximize:
314 if (this->q->isChecked())
315 return i18n("Restore");
316 else
317 return i18n("Maximize");
318 case DecorationButtonType::Close:
319 return i18n("Close");
320 case DecorationButtonType::ContextHelp:
321 return i18n("Context help");
322 case DecorationButtonType::Shade:
323 if (this->q->isChecked())
324 return i18n("Unshade");
325 else
326 return i18n("Shade");
327 case DecorationButtonType::KeepBelow:
328 if (this->q->isChecked())
329 return i18n("Don't keep below other windows");
330 else
331 return i18n("Keep below other windows");
332 case DecorationButtonType::KeepAbove:
333 if (this->q->isChecked())
334 return i18n("Don't keep above other windows");
335 else
336 return i18n("Keep above other windows");
337 default:
338 return QString();
339 }
340}
341
342DecorationButton::DecorationButton(DecorationButtonType type, Decoration *decoration, QObject *parent)
343 : QObject(parent)
344 , d(new Private(type, decoration, this))
345{
346 decoration->d->addButton(this);
347 connect(this, &DecorationButton::geometryChanged, this, static_cast<void (DecorationButton::*)(const QRectF &)>(&DecorationButton::update));
348 auto updateSlot = static_cast<void (DecorationButton::*)()>(&DecorationButton::update);
349 connect(this, &DecorationButton::hoveredChanged, this, updateSlot);
350 connect(this, &DecorationButton::hoveredChanged, this, [this](bool hovered) {
351 if (hovered) {
352 // TODO: show tooltip if hovered and hide if not
353 const QString type = this->d->typeToString(this->type());
354 this->decoration()->requestShowToolTip(type);
355 } else {
356 this->decoration()->requestHideToolTip();
357 }
358 });
359 connect(this, &DecorationButton::pressedChanged, this, updateSlot);
360 connect(this, &DecorationButton::pressedChanged, this, [this](bool pressed) {
361 if (pressed) {
362 this->decoration()->requestHideToolTip();
363 }
364 });
365 connect(this, &DecorationButton::checkedChanged, this, updateSlot);
366 connect(this, &DecorationButton::enabledChanged, this, updateSlot);
367 connect(this, &DecorationButton::visibilityChanged, this, updateSlot);
368 connect(this, &DecorationButton::hoveredChanged, this, [this](bool hovered) {
369 if (hovered) {
370 Q_EMIT pointerEntered();
371 } else {
372 Q_EMIT pointerLeft();
373 }
374 });
375 connect(this, &DecorationButton::pressedChanged, this, [this](bool p) {
376 if (p) {
377 Q_EMIT pressed();
378 } else {
379 Q_EMIT released();
380 }
381 });
382}
383
384DecorationButton::~DecorationButton() = default;
385
386void DecorationButton::update(const QRectF &rect)
387{
388 decoration()->update(rect.isNull() ? geometry().toRect() : rect.toRect());
389}
390
391void DecorationButton::update()
392{
393 update(QRectF());
394}
395
396QSizeF DecorationButton::size() const
397{
398 return d->geometry.size();
399}
400
401bool DecorationButton::isPressed() const
402{
403 return d->isPressed();
404}
405
406bool DecorationButton::isHovered() const
407{
408 return d->hovered;
409}
410
411bool DecorationButton::isEnabled() const
412{
413 return d->enabled;
414}
415
416bool DecorationButton::isChecked() const
417{
418 return d->checked;
419}
420
421bool DecorationButton::isCheckable() const
422{
423 return d->checkable;
424}
425
426bool DecorationButton::isVisible() const
427{
428 return d->visible;
429}
430
431QRectF DecorationButton::geometry() const
432{
433 return d->geometry;
434}
435
436Decoration *DecorationButton::decoration() const
437{
438 return d->decoration;
439}
440
441Qt::MouseButtons DecorationButton::acceptedButtons() const
442{
443 return d->acceptedButtons;
444}
445
446DecorationButtonType DecorationButton::type() const
447{
448 return d->type;
449}
450
451void DecorationButton::setAcceptedButtons(Qt::MouseButtons value)
452{
453 d->setAcceptedButtons(value);
454}
455
456void DecorationButton::setEnabled(bool value)
457{
458 d->setEnabled(value);
459}
460
461void DecorationButton::setChecked(bool value)
462{
463 d->setChecked(value);
464}
465
466void DecorationButton::setCheckable(bool value)
467{
468 d->setCheckable(value);
469}
470
471void DecorationButton::setVisible(bool value)
472{
473 d->setVisible(value);
474}
475
476void DecorationButton::setGeometry(const QRectF &geometry)
477{
478 if (d->geometry == geometry) {
479 return;
480 }
481 d->geometry = geometry;
482 Q_EMIT geometryChanged(d->geometry);
483}
484
485bool DecorationButton::contains(const QPointF &pos) const
486{
487 auto flooredPoint = QPoint(std::floor(pos.x()), std::floor(pos.y()));
488 return d->geometry.toRect().contains(flooredPoint);
489}
490
491bool DecorationButton::event(QEvent *event)
492{
493 switch (event->type()) {
495 hoverEnterEvent(static_cast<QHoverEvent *>(event));
496 return true;
498 hoverLeaveEvent(static_cast<QHoverEvent *>(event));
499 return true;
501 hoverMoveEvent(static_cast<QHoverEvent *>(event));
502 return true;
504 mousePressEvent(static_cast<QMouseEvent *>(event));
505 return true;
507 mouseReleaseEvent(static_cast<QMouseEvent *>(event));
508 return true;
510 mouseMoveEvent(static_cast<QMouseEvent *>(event));
511 return true;
512 case QEvent::Wheel:
513 wheelEvent(static_cast<QWheelEvent *>(event));
514 return true;
515 default:
516 return QObject::event(event);
517 }
518}
519
520void DecorationButton::hoverEnterEvent(QHoverEvent *event)
521{
522 if (!d->enabled || !d->visible || !contains(event->position())) {
523 return;
524 }
525 d->setHovered(true);
526 event->setAccepted(true);
527}
528
529void DecorationButton::hoverLeaveEvent(QHoverEvent *event)
530{
531 if (!d->enabled || !d->visible || !d->hovered || contains(event->position())) {
532 return;
533 }
534 d->setHovered(false);
535 event->setAccepted(true);
536}
537
538void DecorationButton::hoverMoveEvent(QHoverEvent *event)
539{
540 Q_UNUSED(event)
541}
542
543void DecorationButton::mouseMoveEvent(QMouseEvent *event)
544{
545 if (!d->enabled || !d->visible || !d->hovered) {
546 return;
547 }
548 if (!contains(event->position())) {
549 d->setHovered(false);
550 event->setAccepted(true);
551 }
552}
553
554void DecorationButton::mousePressEvent(QMouseEvent *event)
555{
556 if (!d->enabled || !d->visible || !contains(event->position()) || !d->acceptedButtons.testFlag(event->button())) {
557 return;
558 }
559 d->setPressed(event->button(), true);
560 event->setAccepted(true);
561 if (d->doubleClickEnabled && event->button() == Qt::LeftButton) {
562 // check for double click
563 if (d->wasDoubleClick()) {
564 event->setAccepted(true);
565 Q_EMIT doubleClicked();
566 }
567 d->invalidateDoubleClickTimer();
568 }
569 if (d->pressAndHold && event->button() == Qt::LeftButton) {
570 d->startPressAndHold();
571 }
572}
573
574void DecorationButton::mouseReleaseEvent(QMouseEvent *event)
575{
576 if (!d->enabled || !d->visible || !d->isPressed(event->button())) {
577 return;
578 }
579 if (contains(event->position())) {
580 if (!d->pressAndHold || event->button() != Qt::LeftButton) {
581 Q_EMIT clicked(event->button());
582 } else {
583 d->stopPressAndHold();
584 }
585 }
586 d->setPressed(event->button(), false);
587 event->setAccepted(true);
588
589 if (d->doubleClickEnabled && event->button() == Qt::LeftButton) {
590 d->startDoubleClickTimer();
591 }
592}
593
594void DecorationButton::wheelEvent(QWheelEvent *event)
595{
596 Q_UNUSED(event)
597}
598
599}
600
601#include "moc_decorationbutton.cpp"
QString i18n(const char *text, const TYPE &arg...)
AKONADI_CALENDAR_EXPORT KCalendarCore::Event::Ptr event(const Akonadi::Item &item)
Type type(const QSqlDatabase &db)
Framework for creating window decorations.
DecorationButtonType
The DecorationButtonType is a helper type for the DecorationButton.
KTEXTEDITOR_EXPORT size_t qHash(KTextEditor::Cursor cursor, size_t seed=0) noexcept
QCA_EXPORT void init()
QStyleHints * styleHints()
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
virtual bool event(QEvent *e)
qreal x() const const
qreal y() const const
bool isNull() const const
QRect toRect() const const
QueuedConnection
LeftButton
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void timeout()
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:17:09 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.