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  },
66  Qt::QueuedConnection);
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::checkedChanged, this, updateSlot);
357  connect(this, &DecorationButton::enabledChanged, this, updateSlot);
358  connect(this, &DecorationButton::visibilityChanged, this, updateSlot);
359  connect(this, &DecorationButton::hoveredChanged, this, [this](bool hovered) {
360  if (hovered) {
361  Q_EMIT pointerEntered();
362  } else {
363  Q_EMIT pointerLeft();
364  }
365  });
366  connect(this, &DecorationButton::pressedChanged, this, [this](bool p) {
367  if (p) {
368  Q_EMIT pressed();
369  } else {
370  Q_EMIT released();
371  }
372  });
373 }
374 
375 DecorationButton::~DecorationButton() = default;
376 
378 {
379  decoration()->update(rect.isNull() ? geometry().toRect() : rect.toRect());
380 }
381 
383 {
384  update(QRectF());
385 }
386 
387 QSizeF DecorationButton::size() const
388 {
389  return d->geometry.size();
390 }
391 
392 bool DecorationButton::isPressed() const
393 {
394  return d->isPressed();
395 }
396 
397 #define DELEGATE(name, variableName, type) \
398  type DecorationButton::name() const \
399  { \
400  return d->variableName; \
401  }
402 
403 DELEGATE(isHovered, hovered, bool)
404 DELEGATE(isEnabled, enabled, bool)
405 DELEGATE(isChecked, checked, bool)
406 DELEGATE(isCheckable, checkable, bool)
407 DELEGATE(isVisible, visible, bool)
408 
409 #define DELEGATE2(name, type) DELEGATE(name, name, type)
410 DELEGATE2(geometry, QRectF)
411 DELEGATE2(decoration, QPointer<Decoration>)
412 DELEGATE2(acceptedButtons, Qt::MouseButtons)
413 DELEGATE2(type, DecorationButtonType)
414 
415 #undef DELEGATE2
416 #undef DELEGATE
417 
418 #define DELEGATE(name, type) \
419  void DecorationButton::name(type a) \
420  { \
421  d->name(a); \
422  }
423 
424 DELEGATE(setAcceptedButtons, Qt::MouseButtons)
425 DELEGATE(setEnabled, bool)
426 DELEGATE(setChecked, bool)
427 DELEGATE(setCheckable, bool)
428 DELEGATE(setVisible, bool)
429 
430 #undef DELEGATE
431 
432 #define DELEGATE(name, variableName, type) \
433  void DecorationButton::name(type a) \
434  { \
435  if (d->variableName == a) { \
436  return; \
437  } \
438  d->variableName = a; \
439  Q_EMIT variableName##Changed(d->variableName); \
440  }
441 
442 DELEGATE(setGeometry, geometry, const QRectF &)
443 
444 #undef DELEGATE
445 
446 bool DecorationButton::contains(const QPointF &pos) const
447 {
448  return d->geometry.toRect().contains(pos.toPoint());
449 }
450 
451 bool DecorationButton::event(QEvent *event)
452 {
453  switch (event->type()) {
454  case QEvent::HoverEnter:
455  hoverEnterEvent(static_cast<QHoverEvent *>(event));
456  return true;
457  case QEvent::HoverLeave:
458  hoverLeaveEvent(static_cast<QHoverEvent *>(event));
459  return true;
460  case QEvent::HoverMove:
461  hoverMoveEvent(static_cast<QHoverEvent *>(event));
462  return true;
464  mousePressEvent(static_cast<QMouseEvent *>(event));
465  return true;
467  mouseReleaseEvent(static_cast<QMouseEvent *>(event));
468  return true;
469  case QEvent::MouseMove:
470  mouseMoveEvent(static_cast<QMouseEvent *>(event));
471  return true;
472  case QEvent::Wheel:
473  wheelEvent(static_cast<QWheelEvent *>(event));
474  return true;
475  default:
476  return QObject::event(event);
477  }
478 }
479 
480 void DecorationButton::hoverEnterEvent(QHoverEvent *event)
481 {
482  if (!d->enabled || !d->visible || !contains(event->posF())) {
483  return;
484  }
485  d->setHovered(true);
486  event->setAccepted(true);
487 }
488 
489 void DecorationButton::hoverLeaveEvent(QHoverEvent *event)
490 {
491  if (!d->enabled || !d->visible || !d->hovered || contains(event->posF())) {
492  return;
493  }
494  d->setHovered(false);
495  event->setAccepted(true);
496 }
497 
498 void DecorationButton::hoverMoveEvent(QHoverEvent *event)
499 {
500  Q_UNUSED(event)
501 }
502 
503 void DecorationButton::mouseMoveEvent(QMouseEvent *event)
504 {
505  if (!d->enabled || !d->visible || !d->hovered) {
506  return;
507  }
508  if (!contains(event->localPos())) {
509  d->setHovered(false);
510  event->setAccepted(true);
511  }
512 }
513 
514 void DecorationButton::mousePressEvent(QMouseEvent *event)
515 {
516  if (!d->enabled || !d->visible || !contains(event->localPos()) || !d->acceptedButtons.testFlag(event->button())) {
517  return;
518  }
519  d->setPressed(event->button(), true);
520  event->setAccepted(true);
521  if (d->doubleClickEnabled && event->button() == Qt::LeftButton) {
522  // check for double click
523  if (d->wasDoubleClick()) {
524  event->setAccepted(true);
525  Q_EMIT doubleClicked();
526  }
527  d->invalidateDoubleClickTimer();
528  }
529  if (d->pressAndHold && event->button() == Qt::LeftButton) {
530  d->startPressAndHold();
531  }
532 }
533 
534 void DecorationButton::mouseReleaseEvent(QMouseEvent *event)
535 {
536  if (!d->enabled || !d->visible || !d->isPressed(event->button())) {
537  return;
538  }
539  if (contains(event->localPos())) {
540  if (!d->pressAndHold || event->button() != Qt::LeftButton) {
541  Q_EMIT clicked(event->button());
542  } else {
543  d->stopPressAndHold();
544  }
545  }
546  d->setPressed(event->button(), false);
547  event->setAccepted(true);
548 
549  if (d->doubleClickEnabled && event->button() == Qt::LeftButton) {
550  d->startDoubleClickTimer();
551  }
552 }
553 
554 void DecorationButton::wheelEvent(QWheelEvent *event)
555 {
556  Q_UNUSED(event)
557 }
558 
559 }
The Maximize button requests maximizing the DecoratedClient.
QEvent::Type type() const const
Decoration(QObject *parent, const QVariantList &args)
Constructor for the Decoration.
Definition: decoration.cpp:130
QRect toRect() const const
Framework for creating window decorations.
const QPointF & posF() const const
LeftButton
DecorationButtonType
The DecorationButtonType is a helper type for the DecorationButton.
The ContextHelp button requests entering the context help mode.
QRect rect() const
The decoration&#39;s geometry in local coordinates.
Definition: decoration.cpp:272
virtual bool event(QEvent *e)
The Menu button requests showing the window menu on left or right click.
void timeout()
QStyleHints * styleHints()
const QPointF & localPos() const const
Qt::MouseButton button() const const
virtual void init()
This method gets invoked from the framework once the Decoration is created and completely setup...
Definition: decoration.cpp:141
The ApplicationMenu button requests showing the application&#39;s menu on left or right click...
The KeepAbove button requests toggling the DecoratedClient&#39;s keep above state.
The KeepBelow button requests toggling the DecoratedClient&#39;s keep below state.
bool isNull() const const
KCALENDARCORE_EXPORT uint qHash(const KCalendarCore::Period &key)
void update()
Schedules a repaint of the DecorationButton.
QString i18n(const char *text, const TYPE &arg...)
The Minimize button requests minimizing the DecoratedClient.
bool contains(const QPointF &pos) const
Returns true if pos is inside of the button, otherwise returns false.
QPoint toPoint() const const
QSharedPointer< DecorationSettings > settings() const
Definition: decoration.cpp:404
The OnAllDesktops button requests toggling the DecoratedClient&#39;s on all desktops state.
void requestShowWindowMenu(const QRect &rect)
Definition: decoration.cpp:174
QueuedConnection
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QObject * parent() const const
The Shade button requests toggling the DecoratedClient&#39;s shaded state.
The Close button requests closing the DecoratedClient.
Q_EMITQ_EMIT
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Sun Jun 20 2021 23:07:39 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.