KDecoration2

decorationbutton.cpp
1 /*
2  * SPDX-FileCopyrightText: 2014 Martin Gräßlin <[email protected]>
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 namespace KDecoration2
23 {
24 #ifndef K_DOXYGEN
25 uint qHash(const DecorationButtonType &type)
26 {
27  return static_cast<uint>(type);
28 }
29 #endif
30 
31 DecorationButton::Private::Private(DecorationButtonType type, const QPointer<Decoration> &decoration, DecorationButton *parent)
32  : decoration(decoration)
33  , type(type)
34  , hovered(false)
35  , enabled(true)
36  , checkable(false)
37  , checked(false)
38  , visible(true)
39  , acceptedButtons(Qt::LeftButton)
40  , doubleClickEnabled(false)
41  , pressAndHold(false)
42  , q(parent)
43  , m_pressed(Qt::NoButton)
44 {
45  init();
46 }
47 
48 DecorationButton::Private::~Private() = default;
49 
50 void DecorationButton::Private::init()
51 {
52  auto clientPtr = decoration->client().toStrongRef();
53  Q_ASSERT(clientPtr);
54  auto c = clientPtr.data();
55  auto settings = decoration->settings();
56  switch (type) {
59  q,
60  &DecorationButton::clicked,
61  decoration.data(),
62  [this](Qt::MouseButton button) {
63  Q_UNUSED(button)
64  decoration->requestShowWindowMenu(q->geometry().toRect());
65  },
67  QObject::connect(q, &DecorationButton::doubleClicked, decoration.data(), &Decoration::requestClose, Qt::QueuedConnection);
69  settings.data(),
70  &DecorationSettings::closeOnDoubleClickOnMenuChanged,
71  q,
72  [this](bool enabled) {
73  doubleClickEnabled = enabled;
74  setPressAndHold(enabled);
75  },
77  doubleClickEnabled = settings->isCloseOnDoubleClickOnMenu();
78  setPressAndHold(settings->isCloseOnDoubleClickOnMenu());
79  setAcceptedButtons(Qt::LeftButton | Qt::RightButton);
80  break;
82  setVisible(c->hasApplicationMenu());
83  setCheckable(true); // will be "checked" whilst the menu is opened
84  // FIXME TODO connect directly and figure out the button geometry/offset stuff
86  q,
87  &DecorationButton::clicked,
88  decoration.data(),
89  [this] {
90  decoration->requestShowApplicationMenu(q->geometry().toRect(), 0 /* actionId */);
91  },
92  Qt::QueuedConnection); //&Decoration::requestShowApplicationMenu, Qt::QueuedConnection);
93  QObject::connect(c, &DecoratedClient::hasApplicationMenuChanged, q, &DecorationButton::setVisible);
94  QObject::connect(c, &DecoratedClient::applicationMenuActiveChanged, q, &DecorationButton::setChecked);
95  break;
97  setVisible(settings->isOnAllDesktopsAvailable());
98  setCheckable(true);
99  setChecked(c->isOnAllDesktops());
100  QObject::connect(q, &DecorationButton::clicked, decoration.data(), &Decoration::requestToggleOnAllDesktops, Qt::QueuedConnection);
101  QObject::connect(settings.data(), &DecorationSettings::onAllDesktopsAvailableChanged, q, &DecorationButton::setVisible);
102  QObject::connect(c, &DecoratedClient::onAllDesktopsChanged, q, &DecorationButton::setChecked);
103  break;
105  setEnabled(c->isMinimizeable());
106  QObject::connect(q, &DecorationButton::clicked, decoration.data(), &Decoration::requestMinimize, Qt::QueuedConnection);
107  QObject::connect(c, &DecoratedClient::minimizeableChanged, q, &DecorationButton::setEnabled);
108  break;
110  setEnabled(c->isMaximizeable());
111  setCheckable(true);
112  setChecked(c->isMaximized());
113  setAcceptedButtons(Qt::LeftButton | Qt::MiddleButton | Qt::RightButton);
114  QObject::connect(q, &DecorationButton::clicked, decoration.data(), &Decoration::requestToggleMaximization, Qt::QueuedConnection);
115  QObject::connect(c, &DecoratedClient::maximizeableChanged, q, &DecorationButton::setEnabled);
116  QObject::connect(c, &DecoratedClient::maximizedChanged, q, &DecorationButton::setChecked);
117  break;
119  setEnabled(c->isCloseable());
120  QObject::connect(q, &DecorationButton::clicked, decoration.data(), &Decoration::requestClose, Qt::QueuedConnection);
121  QObject::connect(c, &DecoratedClient::closeableChanged, q, &DecorationButton::setEnabled);
122  break;
124  setVisible(c->providesContextHelp());
125  QObject::connect(q, &DecorationButton::clicked, decoration.data(), &Decoration::requestContextHelp, Qt::QueuedConnection);
126  QObject::connect(c, &DecoratedClient::providesContextHelpChanged, q, &DecorationButton::setVisible);
127  break;
129  setCheckable(true);
130  setChecked(c->isKeepAbove());
131  QObject::connect(q, &DecorationButton::clicked, decoration.data(), &Decoration::requestToggleKeepAbove, Qt::QueuedConnection);
132  QObject::connect(c, &DecoratedClient::keepAboveChanged, q, &DecorationButton::setChecked);
133  break;
135  setCheckable(true);
136  setChecked(c->isKeepBelow());
137  QObject::connect(q, &DecorationButton::clicked, decoration.data(), &Decoration::requestToggleKeepBelow, Qt::QueuedConnection);
138  QObject::connect(c, &DecoratedClient::keepBelowChanged, q, &DecorationButton::setChecked);
139  break;
141  setEnabled(c->isShadeable());
142  setCheckable(true);
143  setChecked(c->isShaded());
144  QObject::connect(q, &DecorationButton::clicked, decoration.data(), &Decoration::requestToggleShade, Qt::QueuedConnection);
145  QObject::connect(c, &DecoratedClient::shadedChanged, q, &DecorationButton::setChecked);
146  QObject::connect(c, &DecoratedClient::shadeableChanged, q, &DecorationButton::setEnabled);
147  break;
148  default:
149  // nothing
150  break;
151  }
152 }
153 
154 void DecorationButton::Private::setHovered(bool set)
155 {
156  if (hovered == set) {
157  return;
158  }
159  hovered = set;
160  Q_EMIT q->hoveredChanged(hovered);
161 }
162 
163 void DecorationButton::Private::setEnabled(bool set)
164 {
165  if (enabled == set) {
166  return;
167  }
168  enabled = set;
169  Q_EMIT q->enabledChanged(enabled);
170  if (!enabled) {
171  setHovered(false);
172  if (isPressed()) {
173  m_pressed = Qt::NoButton;
174  Q_EMIT q->pressedChanged(false);
175  }
176  }
177 }
178 
179 void DecorationButton::Private::setVisible(bool set)
180 {
181  if (visible == set) {
182  return;
183  }
184  visible = set;
185  Q_EMIT q->visibilityChanged(set);
186  if (!visible) {
187  setHovered(false);
188  if (isPressed()) {
189  m_pressed = Qt::NoButton;
190  Q_EMIT q->pressedChanged(false);
191  }
192  }
193 }
194 
195 void DecorationButton::Private::setChecked(bool set)
196 {
197  if (!checkable || checked == set) {
198  return;
199  }
200  checked = set;
201  Q_EMIT q->checkedChanged(checked);
202 }
203 
204 void DecorationButton::Private::setCheckable(bool set)
205 {
206  if (checkable == set) {
207  return;
208  }
209  if (!set) {
210  setChecked(false);
211  }
212  checkable = set;
213  Q_EMIT q->checkableChanged(checkable);
214 }
215 
216 void DecorationButton::Private::setPressed(Qt::MouseButton button, bool pressed)
217 {
218  if (pressed) {
219  m_pressed = m_pressed | button;
220  } else {
221  m_pressed = m_pressed & ~button;
222  }
223  Q_EMIT q->pressedChanged(isPressed());
224 }
225 
226 void DecorationButton::Private::setAcceptedButtons(Qt::MouseButtons buttons)
227 {
228  if (acceptedButtons == buttons) {
229  return;
230  }
231  acceptedButtons = buttons;
232  Q_EMIT q->acceptedButtonsChanged(acceptedButtons);
233 }
234 
235 void DecorationButton::Private::startDoubleClickTimer()
236 {
237  if (!doubleClickEnabled) {
238  return;
239  }
240  if (m_doubleClickTimer.isNull()) {
241  m_doubleClickTimer.reset(new QElapsedTimer());
242  }
243  m_doubleClickTimer->start();
244 }
245 
246 void DecorationButton::Private::invalidateDoubleClickTimer()
247 {
248  if (m_doubleClickTimer.isNull()) {
249  return;
250  }
251  m_doubleClickTimer->invalidate();
252 }
253 
254 bool DecorationButton::Private::wasDoubleClick() const
255 {
256  if (m_doubleClickTimer.isNull() || !m_doubleClickTimer->isValid()) {
257  return false;
258  }
259  return !m_doubleClickTimer->hasExpired(QGuiApplication::styleHints()->mouseDoubleClickInterval());
260 }
261 
262 void DecorationButton::Private::setPressAndHold(bool enable)
263 {
264  if (pressAndHold == enable) {
265  return;
266  }
267  pressAndHold = enable;
268  if (!pressAndHold) {
269  m_pressAndHoldTimer.reset();
270  }
271 }
272 
273 void DecorationButton::Private::startPressAndHold()
274 {
275  if (!pressAndHold) {
276  return;
277  }
278  if (m_pressAndHoldTimer.isNull()) {
279  m_pressAndHoldTimer.reset(new QTimer());
280  m_pressAndHoldTimer->setSingleShot(true);
281  QObject::connect(m_pressAndHoldTimer.data(), &QTimer::timeout, q, [this]() {
282  q->clicked(Qt::LeftButton);
283  });
284  }
285  m_pressAndHoldTimer->start(QGuiApplication::styleHints()->mousePressAndHoldInterval());
286 }
287 
288 void DecorationButton::Private::stopPressAndHold()
289 {
290  if (!m_pressAndHoldTimer.isNull()) {
291  m_pressAndHoldTimer->stop();
292  }
293 }
294 
295 QString DecorationButton::Private::typeToString(DecorationButtonType type)
296 {
297  switch (type) {
299  return i18n("More actions for this window");
301  return i18n("Application menu");
303  if (this->q->isChecked())
304  return i18n("On one desktop");
305  else
306  return i18n("On all desktops");
308  return i18n("Minimize");
310  if (this->q->isChecked())
311  return i18n("Restore");
312  else
313  return i18n("Maximize");
315  return i18n("Close");
317  return i18n("Context help");
319  if (this->q->isChecked())
320  return i18n("Unshade");
321  else
322  return i18n("Shade");
324  if (this->q->isChecked())
325  return i18n("Don't keep below other windows");
326  else
327  return i18n("Keep below other windows");
329  if (this->q->isChecked())
330  return i18n("Don't keep above other windows");
331  else
332  return i18n("Keep above other windows");
333  default:
334  return QString();
335  }
336 }
337 
338 DecorationButton::DecorationButton(DecorationButtonType type, const QPointer<Decoration> &decoration, QObject *parent)
339  : QObject(parent)
340  , d(new Private(type, decoration, this))
341 {
342  decoration->d->addButton(this);
343  connect(this, &DecorationButton::geometryChanged, this, static_cast<void (DecorationButton::*)(const QRectF &)>(&DecorationButton::update));
344  auto updateSlot = static_cast<void (DecorationButton::*)()>(&DecorationButton::update);
345  connect(this, &DecorationButton::hoveredChanged, this, updateSlot);
346  connect(this, &DecorationButton::hoveredChanged, this, [this](bool hovered) {
347  if (hovered) {
348  // TODO: show tooltip if hovered and hide if not
349  const QString type = this->d->typeToString(this->type());
350  this->decoration()->requestShowToolTip(type);
351  } else {
352  this->decoration()->requestHideToolTip();
353  }
354  });
355  connect(this, &DecorationButton::pressedChanged, this, updateSlot);
356  connect(this, &DecorationButton::pressedChanged, this, [this](bool pressed) {
357  if (pressed) {
358  this->decoration()->requestHideToolTip();
359  }
360  });
361  connect(this, &DecorationButton::checkedChanged, this, updateSlot);
362  connect(this, &DecorationButton::enabledChanged, this, updateSlot);
363  connect(this, &DecorationButton::visibilityChanged, this, updateSlot);
364  connect(this, &DecorationButton::hoveredChanged, this, [this](bool hovered) {
365  if (hovered) {
366  Q_EMIT pointerEntered();
367  } else {
368  Q_EMIT pointerLeft();
369  }
370  });
371  connect(this, &DecorationButton::pressedChanged, this, [this](bool p) {
372  if (p) {
373  Q_EMIT pressed();
374  } else {
375  Q_EMIT released();
376  }
377  });
378 }
379 
380 DecorationButton::~DecorationButton() = default;
381 
383 {
384  decoration()->update(rect.isNull() ? geometry().toRect() : rect.toRect());
385 }
386 
388 {
389  update(QRectF());
390 }
391 
392 QSizeF DecorationButton::size() const
393 {
394  return d->geometry.size();
395 }
396 
397 bool DecorationButton::isPressed() const
398 {
399  return d->isPressed();
400 }
401 
402 #define DELEGATE(name, variableName, type) \
403  type DecorationButton::name() const \
404  { \
405  return d->variableName; \
406  }
407 
408 DELEGATE(isHovered, hovered, bool)
409 DELEGATE(isEnabled, enabled, bool)
410 DELEGATE(isChecked, checked, bool)
411 DELEGATE(isCheckable, checkable, bool)
412 DELEGATE(isVisible, visible, bool)
413 
414 #define DELEGATE2(name, type) DELEGATE(name, name, type)
415 DELEGATE2(geometry, QRectF)
416 DELEGATE2(decoration, QPointer<Decoration>)
417 DELEGATE2(acceptedButtons, Qt::MouseButtons)
418 DELEGATE2(type, DecorationButtonType)
419 
420 #undef DELEGATE2
421 #undef DELEGATE
422 
423 #define DELEGATE(name, type) \
424  void DecorationButton::name(type a) \
425  { \
426  d->name(a); \
427  }
428 
429 DELEGATE(setAcceptedButtons, Qt::MouseButtons)
430 DELEGATE(setEnabled, bool)
431 DELEGATE(setChecked, bool)
432 DELEGATE(setCheckable, bool)
433 DELEGATE(setVisible, bool)
434 
435 #undef DELEGATE
436 
437 #define DELEGATE(name, variableName, type) \
438  void DecorationButton::name(type a) \
439  { \
440  if (d->variableName == a) { \
441  return; \
442  } \
443  d->variableName = a; \
444  Q_EMIT variableName##Changed(d->variableName); \
445  }
446 
447 DELEGATE(setGeometry, geometry, const QRectF &)
448 
449 #undef DELEGATE
450 
451 bool DecorationButton::contains(const QPointF &pos) const
452 {
453  return d->geometry.toRect().contains(pos.toPoint());
454 }
455 
456 bool DecorationButton::event(QEvent *event)
457 {
458  switch (event->type()) {
459  case QEvent::HoverEnter:
460  hoverEnterEvent(static_cast<QHoverEvent *>(event));
461  return true;
462  case QEvent::HoverLeave:
463  hoverLeaveEvent(static_cast<QHoverEvent *>(event));
464  return true;
465  case QEvent::HoverMove:
466  hoverMoveEvent(static_cast<QHoverEvent *>(event));
467  return true;
469  mousePressEvent(static_cast<QMouseEvent *>(event));
470  return true;
472  mouseReleaseEvent(static_cast<QMouseEvent *>(event));
473  return true;
474  case QEvent::MouseMove:
475  mouseMoveEvent(static_cast<QMouseEvent *>(event));
476  return true;
477  case QEvent::Wheel:
478  wheelEvent(static_cast<QWheelEvent *>(event));
479  return true;
480  default:
481  return QObject::event(event);
482  }
483 }
484 
485 void DecorationButton::hoverEnterEvent(QHoverEvent *event)
486 {
487  if (!d->enabled || !d->visible || !contains(event->posF())) {
488  return;
489  }
490  d->setHovered(true);
491  event->setAccepted(true);
492 }
493 
494 void DecorationButton::hoverLeaveEvent(QHoverEvent *event)
495 {
496  if (!d->enabled || !d->visible || !d->hovered || contains(event->posF())) {
497  return;
498  }
499  d->setHovered(false);
500  event->setAccepted(true);
501 }
502 
503 void DecorationButton::hoverMoveEvent(QHoverEvent *event)
504 {
505  Q_UNUSED(event)
506 }
507 
508 void DecorationButton::mouseMoveEvent(QMouseEvent *event)
509 {
510  if (!d->enabled || !d->visible || !d->hovered) {
511  return;
512  }
513  if (!contains(event->localPos())) {
514  d->setHovered(false);
515  event->setAccepted(true);
516  }
517 }
518 
519 void DecorationButton::mousePressEvent(QMouseEvent *event)
520 {
521  if (!d->enabled || !d->visible || !contains(event->localPos()) || !d->acceptedButtons.testFlag(event->button())) {
522  return;
523  }
524  d->setPressed(event->button(), true);
525  event->setAccepted(true);
526  if (d->doubleClickEnabled && event->button() == Qt::LeftButton) {
527  // check for double click
528  if (d->wasDoubleClick()) {
529  event->setAccepted(true);
530  Q_EMIT doubleClicked();
531  }
532  d->invalidateDoubleClickTimer();
533  }
534  if (d->pressAndHold && event->button() == Qt::LeftButton) {
535  d->startPressAndHold();
536  }
537 }
538 
539 void DecorationButton::mouseReleaseEvent(QMouseEvent *event)
540 {
541  if (!d->enabled || !d->visible || !d->isPressed(event->button())) {
542  return;
543  }
544  if (contains(event->localPos())) {
545  if (!d->pressAndHold || event->button() != Qt::LeftButton) {
546  Q_EMIT clicked(event->button());
547  } else {
548  d->stopPressAndHold();
549  }
550  }
551  d->setPressed(event->button(), false);
552  event->setAccepted(true);
553 
554  if (d->doubleClickEnabled && event->button() == Qt::LeftButton) {
555  d->startDoubleClickTimer();
556  }
557 }
558 
559 void DecorationButton::wheelEvent(QWheelEvent *event)
560 {
561  Q_UNUSED(event)
562 }
563 
564 }
DecorationButtonType
The DecorationButtonType is a helper type for the DecorationButton.
@ KeepBelow
The KeepBelow button requests toggling the DecoratedClient's keep below state.
@ Maximize
The Maximize button requests maximizing the DecoratedClient.
@ ApplicationMenu
The ApplicationMenu button requests showing the application's menu on left or right click.
Q_EMITQ_EMIT
QRect toRect() const const
bool contains(const QPointF &pos) const
Returns true if pos is inside of the button, otherwise returns false.
@ Close
The Close button requests closing the DecoratedClient.
LeftButton
@ ContextHelp
The ContextHelp button requests entering the context help mode.
@ Minimize
The Minimize button requests minimizing the DecoratedClient.
QRectF geometry
The geometry of the DecorationButton in Decoration-local coordinates.
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
@ KeepAbove
The KeepAbove button requests toggling the DecoratedClient's keep above state.
@ Shade
The Shade button requests toggling the DecoratedClient's shaded state.
virtual bool event(QEvent *e)
QString i18n(const char *text, const TYPE &arg...)
void timeout()
void update()
Schedules a repaint of the DecorationButton.
QueuedConnection
KCALENDARCORE_EXPORT uint qHash(const KCalendarCore::Period &key)
virtual void init()
This method gets invoked from the framework once the Decoration is created and completely setup.
Definition: decoration.cpp:141
QStyleHints * styleHints()
AKONADI_CALENDAR_EXPORT KCalendarCore::Event::Ptr event(const Akonadi::Item &item)
@ OnAllDesktops
The OnAllDesktops button requests toggling the DecoratedClient's on all desktops state.
VehicleSection::Type type(QStringView coachNumber, QStringView coachClassification)
QPoint toPoint() const const
bool isNull() const const
@ Menu
The Menu button requests showing the window menu on left or right click.
Framework for creating window decorations.
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Thu Oct 6 2022 04:01:56 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.