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 #include <cmath>
23 
24 namespace KDecoration2
25 {
26 #ifndef K_DOXYGEN
27 uint qHash(const DecorationButtonType &type)
28 {
29  return static_cast<uint>(type);
30 }
31 #endif
32 
33 DecorationButton::Private::Private(DecorationButtonType type, const QPointer<Decoration> &decoration, DecorationButton *parent)
34  : decoration(decoration)
35  , type(type)
36  , hovered(false)
37  , enabled(true)
38  , checkable(false)
39  , checked(false)
40  , visible(true)
41  , acceptedButtons(Qt::LeftButton)
42  , doubleClickEnabled(false)
43  , pressAndHold(false)
44  , q(parent)
45  , m_pressed(Qt::NoButton)
46 {
47  init();
48 }
49 
50 DecorationButton::Private::~Private() = default;
51 
52 void DecorationButton::Private::init()
53 {
54  auto c = decoration->client();
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.get(),
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.get(), &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) {
241  m_doubleClickTimer = std::make_unique<QElapsedTimer>();
242  }
243  m_doubleClickTimer->start();
244 }
245 
246 void DecorationButton::Private::invalidateDoubleClickTimer()
247 {
248  if (!m_doubleClickTimer) {
249  return;
250  }
251  m_doubleClickTimer->invalidate();
252 }
253 
254 bool DecorationButton::Private::wasDoubleClick() const
255 {
256  if (!m_doubleClickTimer || !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) {
279  m_pressAndHoldTimer.reset(new QTimer());
280  m_pressAndHoldTimer->setSingleShot(true);
281  QObject::connect(m_pressAndHoldTimer.get(), &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) {
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, 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, 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  auto flooredPoint = QPoint(std::floor(pos.x()), std::floor(pos.y()));
454  return d->geometry.toRect().contains(flooredPoint);
455 }
456 
457 bool DecorationButton::event(QEvent *event)
458 {
459  switch (event->type()) {
460  case QEvent::HoverEnter:
461  hoverEnterEvent(static_cast<QHoverEvent *>(event));
462  return true;
463  case QEvent::HoverLeave:
464  hoverLeaveEvent(static_cast<QHoverEvent *>(event));
465  return true;
466  case QEvent::HoverMove:
467  hoverMoveEvent(static_cast<QHoverEvent *>(event));
468  return true;
470  mousePressEvent(static_cast<QMouseEvent *>(event));
471  return true;
473  mouseReleaseEvent(static_cast<QMouseEvent *>(event));
474  return true;
475  case QEvent::MouseMove:
476  mouseMoveEvent(static_cast<QMouseEvent *>(event));
477  return true;
478  case QEvent::Wheel:
479  wheelEvent(static_cast<QWheelEvent *>(event));
480  return true;
481  default:
482  return QObject::event(event);
483  }
484 }
485 
486 void DecorationButton::hoverEnterEvent(QHoverEvent *event)
487 {
488  if (!d->enabled || !d->visible || !contains(event->posF())) {
489  return;
490  }
491  d->setHovered(true);
492  event->setAccepted(true);
493 }
494 
495 void DecorationButton::hoverLeaveEvent(QHoverEvent *event)
496 {
497  if (!d->enabled || !d->visible || !d->hovered || contains(event->posF())) {
498  return;
499  }
500  d->setHovered(false);
501  event->setAccepted(true);
502 }
503 
504 void DecorationButton::hoverMoveEvent(QHoverEvent *event)
505 {
506  Q_UNUSED(event)
507 }
508 
509 void DecorationButton::mouseMoveEvent(QMouseEvent *event)
510 {
511  if (!d->enabled || !d->visible || !d->hovered) {
512  return;
513  }
514  if (!contains(event->localPos())) {
515  d->setHovered(false);
516  event->setAccepted(true);
517  }
518 }
519 
520 void DecorationButton::mousePressEvent(QMouseEvent *event)
521 {
522  if (!d->enabled || !d->visible || !contains(event->localPos()) || !d->acceptedButtons.testFlag(event->button())) {
523  return;
524  }
525  d->setPressed(event->button(), true);
526  event->setAccepted(true);
527  if (d->doubleClickEnabled && event->button() == Qt::LeftButton) {
528  // check for double click
529  if (d->wasDoubleClick()) {
530  event->setAccepted(true);
531  Q_EMIT doubleClicked();
532  }
533  d->invalidateDoubleClickTimer();
534  }
535  if (d->pressAndHold && event->button() == Qt::LeftButton) {
536  d->startPressAndHold();
537  }
538 }
539 
540 void DecorationButton::mouseReleaseEvent(QMouseEvent *event)
541 {
542  if (!d->enabled || !d->visible || !d->isPressed(event->button())) {
543  return;
544  }
545  if (contains(event->localPos())) {
546  if (!d->pressAndHold || event->button() != Qt::LeftButton) {
547  Q_EMIT clicked(event->button());
548  } else {
549  d->stopPressAndHold();
550  }
551  }
552  d->setPressed(event->button(), false);
553  event->setAccepted(true);
554 
555  if (d->doubleClickEnabled && event->button() == Qt::LeftButton) {
556  d->startDoubleClickTimer();
557  }
558 }
559 
560 void DecorationButton::wheelEvent(QWheelEvent *event)
561 {
562  Q_UNUSED(event)
563 }
564 
565 }
566 
567 #include "moc_decorationbutton.cpp"
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
Type type(const QSqlDatabase &db)
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:142
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.
qreal x() const const
qreal y() 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-2023 The KDE developers.
Generated on Sat Sep 23 2023 03:51:46 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.