KNotifications

kpassivepopup.cpp
1 /*
2  SPDX-FileCopyrightText: 2001-2006 Richard Moore <[email protected]>
3  SPDX-FileCopyrightText: 2004-2005 Sascha Cunz <[email protected]>
4 
5  SPDX-License-Identifier: LGPL-2.0-or-later
6 */
7 
8 #include "kpassivepopup.h"
9 
10 #include "debug_p.h"
11 #include <config-knotifications.h>
12 
13 #if KNOTIFICATIONS_BUILD_DEPRECATED_SINCE(5, 79)
14 // Qt
15 #include <QBitmap>
16 #include <QBoxLayout>
17 #include <QGuiApplication>
18 #include <QLabel>
19 #include <QMouseEvent>
20 #include <QPainter>
21 #include <QPainterPath>
22 #include <QScreen>
23 #include <QStyle>
24 #include <QSystemTrayIcon>
25 #include <QTimer>
26 #include <QToolTip>
27 
28 #if HAVE_X11
29 #include <QX11Info>
30 #include <netwm.h>
31 #endif
32 
33 #if HAVE_KWINDOWSYSTEM
34 #include <KWindowInfo>
35 #endif
36 
37 
38 static const int DEFAULT_POPUP_TYPE = KPassivePopup::Boxed;
39 static const int DEFAULT_POPUP_TIME = 6 * 1000;
41 
42 class Q_DECL_HIDDEN KPassivePopup::Private
43 {
44 public:
45  Private(KPassivePopup *q, WId winId)
46  : q(q)
47  , popupStyle(DEFAULT_POPUP_TYPE)
48  , window(winId)
49  , hideDelay(DEFAULT_POPUP_TIME)
50  , hideTimer(new QTimer(q))
51  , autoDelete(false)
52  {
53 #if HAVE_X11
54  if (QX11Info::isPlatformX11()) {
56  } else
57 #else
58  q->setWindowFlags(POPUP_FLAGS);
59 #endif
61  q->setLineWidth(2);
62 
63  if (popupStyle == Boxed) {
65  q->setLineWidth(2);
66  } else if (popupStyle == Balloon) {
68  }
69  connect(hideTimer, &QTimer::timeout, q, &QWidget::hide);
70  connect(q, qOverload<>(&KPassivePopup::clicked), q, &QWidget::hide);
71  }
72 
73  KPassivePopup *const q;
74 
75  int popupStyle;
76  QPolygon surround;
77  QPoint anchor;
78  QPoint fixedPosition;
79 
80  WId window;
81  QWidget *msgView = nullptr;
82  QBoxLayout *topLayout = nullptr;
83  int hideDelay;
84  QTimer *hideTimer = nullptr;
85 
86  QLabel *ttlIcon = nullptr;
87  QLabel *ttl = nullptr;
88  QLabel *msg = nullptr;
89 
90  bool autoDelete = false;
91 
92  /**
93  * Updates the transparency mask. Unused if PopupStyle == Boxed
94  */
95  void updateMask()
96  {
97  // get screen-geometry for screen our anchor is on
98  // (geometry can differ from screen to screen!
99  QRect deskRect = desktopRectForPoint(anchor);
100 
101  const int width = q->width();
102  const int height = q->height();
103 
104  int xh = 70;
105  int xl = 40;
106  if (width < 80) {
107  xh = xl = 40;
108  } else if (width < 110) {
109  xh = width - 40;
110  }
111 
112  bool bottom = (anchor.y() + height) > ((deskRect.y() + deskRect.height() - 48));
113  bool right = (anchor.x() + width) > ((deskRect.x() + deskRect.width() - 48));
114 
115  /* clang-format off */
116  QPoint corners[4] = {QPoint(width - 50, 10),
117  QPoint(10, 10),
118  QPoint(10, height - 50),
119  QPoint(width - 50, height - 50)};
120  /* clang-format on */
121 
122  QBitmap mask(width, height);
123  mask.clear();
124  QPainter p(&mask);
126  p.setBrush(brush);
127 
128  int i = 0;
129  int z = 0;
130  for (; i < 4; ++i) {
132  path.moveTo(corners[i].x(), corners[i].y());
133  path.arcTo(corners[i].x(), corners[i].y(), 40, 40, i * 90, 90);
134  QPolygon corner = path.toFillPolygon().toPolygon();
135 
136  surround.resize(z + corner.count() - 1);
137  for (int s = 1; s < corner.count() - 1; ++s, ++z) {
138  surround.setPoint(z, corner[s]);
139  }
140 
141  if (bottom && i == 2) {
142  if (right) {
143  surround.resize(z + 3);
144  surround.setPoint(z++, QPoint(width - xh, height - 10));
145  surround.setPoint(z++, QPoint(width - 20, height));
146  surround.setPoint(z++, QPoint(width - xl, height - 10));
147  } else {
148  surround.resize(z + 3);
149  surround.setPoint(z++, QPoint(xl, height - 10));
150  surround.setPoint(z++, QPoint(20, height));
151  surround.setPoint(z++, QPoint(xh, height - 10));
152  }
153  } else if (!bottom && i == 0) {
154  if (right) {
155  surround.resize(z + 3);
156  surround.setPoint(z++, QPoint(width - xl, 10));
157  surround.setPoint(z++, QPoint(width - 20, 0));
158  surround.setPoint(z++, QPoint(width - xh, 10));
159  } else {
160  surround.resize(z + 3);
161  surround.setPoint(z++, QPoint(xh, 10));
162  surround.setPoint(z++, QPoint(20, 0));
163  surround.setPoint(z++, QPoint(xl, 10));
164  }
165  }
166  }
167 
168  surround.resize(z + 1);
169  surround.setPoint(z, surround[0]);
170  p.drawPolygon(surround);
171  q->setMask(mask);
172 
173  q->move(right ? anchor.x() - width + 20 : (anchor.x() < 11 ? 11 : anchor.x() - 20), //
174  bottom ? anchor.y() - height : (anchor.y() < 11 ? 11 : anchor.y()));
175 
176  q->update();
177  }
178 
179  /**
180  * Calculates the position to place the popup near the specified rectangle.
181  */
182  QPoint calculateNearbyPoint(const QRect &target)
183  {
184  QPoint pos = target.topLeft();
185  int x = pos.x();
186  int y = pos.y();
187  int w = q->minimumSizeHint().width();
188  int h = q->minimumSizeHint().height();
189 
190  QRect r = desktopRectForPoint(QPoint(x + w / 2, y + h / 2));
191 
192  if (popupStyle == Balloon) {
193  // find a point to anchor to
194  if (x + w > r.width()) {
195  x = x + target.width();
196  }
197 
198  if (y + h > r.height()) {
199  y = y + target.height();
200  }
201  } else {
202  if (x < r.center().x()) {
203  x = x + target.width();
204  } else {
205  x = x - w;
206  }
207 
208  // It's apparently trying to go off screen, so display it ALL at the bottom.
209  if ((y + h) > r.bottom()) {
210  y = r.bottom() - h;
211  }
212 
213  if ((x + w) > r.right()) {
214  x = r.right() - w;
215  }
216  }
217  if (y < r.top()) {
218  y = r.top();
219  }
220 
221  if (x < r.left()) {
222  x = r.left();
223  }
224 
225  return QPoint(x, y);
226  }
227 
228  QRect desktopRectForPoint(const QPoint &point)
229  {
230  const QList<QScreen *> screens = QGuiApplication::screens();
231  for (const QScreen *screen : screens) {
232  if (screen->geometry().contains(point)) {
233  return screen->geometry();
234  }
235  }
236 
237  // If no screen was found, return the primary screen's geometry
238  return QGuiApplication::primaryScreen()->geometry();
239  }
240 };
241 
243  : QFrame(nullptr, f ? f : POPUP_FLAGS)
244  , d(new Private(this, parent ? parent->effectiveWinId() : 0L))
245 {
246 }
247 
249  : QFrame(nullptr)
250  , d(new Private(this, win))
251 {
252 }
253 
255 
256 void KPassivePopup::setPopupStyle(int popupstyle)
257 {
258  if (d->popupStyle == popupstyle) {
259  return;
260  }
261 
262  d->popupStyle = popupstyle;
263  if (d->popupStyle == Boxed) {
265  setLineWidth(2);
266  } else if (d->popupStyle == Balloon) {
268  }
269 }
270 
272 {
273  delete d->msgView;
274  d->msgView = child;
275 
276  delete d->topLayout;
277  d->topLayout = new QVBoxLayout(this);
278  if (d->popupStyle == Balloon) {
279  auto *style = this->style();
280  const int leftMarginHint = 2 * style->pixelMetric(QStyle::PM_LayoutLeftMargin);
281  const int topMarginHint = 2 * style->pixelMetric(QStyle::PM_LayoutTopMargin);
282  const int rightMarginHint = 2 * style->pixelMetric(QStyle::PM_LayoutRightMargin);
283  const int bottomMarginHint = 2 * style->pixelMetric(QStyle::PM_LayoutBottomMargin);
284  d->topLayout->setContentsMargins(leftMarginHint, topMarginHint, rightMarginHint, bottomMarginHint);
285  }
286  d->topLayout->addWidget(d->msgView);
287  d->topLayout->activate();
288 }
289 
290 void KPassivePopup::setView(const QString &caption, const QString &text, const QPixmap &icon)
291 {
292  // qCDebug(LOG_KNOTIFICATIONS) << "KPassivePopup::setView " << caption << ", " << text;
293  setView(standardView(caption, text, icon, this));
294 }
295 
296 QWidget *KPassivePopup::standardView(const QString &caption, const QString &text, const QPixmap &icon, QWidget *parent)
297 {
298  QWidget *top = new QWidget(parent ? parent : this);
299  QVBoxLayout *vb = new QVBoxLayout(top);
300  vb->setContentsMargins(0, 0, 0, 0);
301 
302  QHBoxLayout *hb = nullptr;
303  if (!icon.isNull()) {
304  hb = new QHBoxLayout;
305  hb->setContentsMargins(0, 0, 0, 0);
306  vb->addLayout(hb);
307  d->ttlIcon = new QLabel(top);
308  d->ttlIcon->setPixmap(icon);
309  d->ttlIcon->setAlignment(Qt::AlignLeft);
310  hb->addWidget(d->ttlIcon);
311  }
312 
313  if (!caption.isEmpty()) {
314  d->ttl = new QLabel(caption, top);
315  QFont fnt = d->ttl->font();
316  fnt.setBold(true);
317  d->ttl->setFont(fnt);
318  d->ttl->setAlignment(Qt::AlignHCenter);
319 
320  if (hb) {
321  hb->addWidget(d->ttl);
322  hb->setStretchFactor(d->ttl, 10); // enforce centering
323  } else {
324  vb->addWidget(d->ttl);
325  }
326  }
327 
328  if (!text.isEmpty()) {
329  d->msg = new QLabel(text, top);
330  d->msg->setAlignment(Qt::AlignLeft);
331  d->msg->setTextInteractionFlags(Qt::LinksAccessibleByMouse);
332  d->msg->setOpenExternalLinks(true);
333  vb->addWidget(d->msg);
334  }
335 
336  return top;
337 }
338 
339 void KPassivePopup::setView(const QString &caption, const QString &text)
340 {
341  setView(caption, text, QPixmap());
342 }
343 
345 {
346  return d->msgView;
347 }
348 
349 int KPassivePopup::timeout() const
350 {
351  return d->hideDelay;
352 }
353 
355 {
356  d->hideDelay = delay < 0 ? DEFAULT_POPUP_TIME : delay;
357  if (d->hideTimer->isActive()) {
358  if (delay) {
359  d->hideTimer->start(delay);
360  } else {
361  d->hideTimer->stop();
362  }
363  }
364 }
365 
366 bool KPassivePopup::autoDelete() const
367 {
368  return d->autoDelete;
369 }
370 
371 void KPassivePopup::setAutoDelete(bool autoDelete)
372 {
373  d->autoDelete = autoDelete;
374 }
375 
377 {
378  Q_EMIT clicked();
379  Q_EMIT clicked(e->pos());
380 }
381 
382 //
383 // Main Implementation
384 //
385 
386 void KPassivePopup::setVisible(bool visible)
387 {
388  if (!visible) {
390  return;
391  }
392 
393  if (size() != sizeHint()) {
394  resize(sizeHint());
395  }
396 
397  if (d->fixedPosition.isNull()) {
398  positionSelf();
399  } else {
400  if (d->popupStyle == Balloon) {
401  setAnchor(d->fixedPosition);
402  } else {
403  move(d->fixedPosition);
404  }
405  }
406  QFrame::setVisible(/*visible=*/true);
407 
408  int delay = d->hideDelay;
409  if (delay < 0) {
410  delay = DEFAULT_POPUP_TIME;
411  }
412 
413  if (delay > 0) {
414  d->hideTimer->start(delay);
415  }
416 }
417 
419 {
420  d->fixedPosition = p;
421  show();
422 }
423 
425 {
426  d->hideTimer->stop();
427  if (d->autoDelete) {
428  deleteLater();
429  }
430 }
431 
433 {
434  const QRect r = QGuiApplication::primaryScreen()->availableGeometry();
435  return QPoint(r.left(), r.top());
436 }
437 
439 {
440  QRect target;
441 
442  if (d->window) {
443 #if HAVE_X11
444  if (QX11Info::isPlatformX11()) {
445  NETWinInfo ni(QX11Info::connection(), d->window, QX11Info::appRootWindow(), NET::WMIconGeometry | NET::WMState, NET::Properties2());
446 
447  // Try to put the popup by the taskbar entry
448  if (!(ni.state() & NET::SkipTaskbar)) {
449  NETRect r = ni.iconGeometry();
450  target.setRect(r.pos.x, r.pos.y, r.size.width, r.size.height);
451  }
452  }
453 #endif
454  // If that failed, put it by the window itself
455 
456  if (target.isNull()) {
457  // Avoid making calls to the window system if we can
458  QWidget *widget = QWidget::find(d->window);
459  if (widget) {
460  target = widget->geometry();
461  }
462  }
463 #if HAVE_KWINDOWSYSTEM
464  if (target.isNull()) {
465  KWindowInfo info(d->window, NET::WMGeometry);
466  if (info.valid()) {
467  target = info.geometry();
468  }
469  }
470 #endif
471  }
472  if (target.isNull()) {
473  target = QRect(defaultLocation(), QSize(0, 0));
474  }
475  moveNear(target);
476 }
477 
478 void KPassivePopup::moveNear(const QRect &target)
479 {
480  QPoint pos = d->calculateNearbyPoint(target);
481  if (d->popupStyle == Balloon) {
482  setAnchor(pos);
483  } else {
484  move(pos.x(), pos.y());
485  }
486 }
487 
489 {
490  return d->anchor;
491 }
492 
493 void KPassivePopup::setAnchor(const QPoint &anchor)
494 {
495  d->anchor = anchor;
496  d->updateMask();
497 }
498 
500 {
501  if (d->popupStyle == Balloon) {
502  QPainter p;
503  p.begin(this);
504  p.drawPolygon(d->surround);
505  } else {
506  QFrame::paintEvent(pe);
507  }
508 }
509 
510 //
511 // Convenience Methods
512 //
513 
514 KPassivePopup *KPassivePopup::message(const QString &caption, const QString &text, const QPixmap &icon, QWidget *parent, int timeout, const QPoint &p)
515 {
516  return message(DEFAULT_POPUP_TYPE, caption, text, icon, parent, timeout, p);
517 }
518 
520 {
521  return message(DEFAULT_POPUP_TYPE, QString(), text, QPixmap(), parent, -1, p);
522 }
523 
524 KPassivePopup *KPassivePopup::message(const QString &caption, const QString &text, QWidget *parent, const QPoint &p)
525 {
526  return message(DEFAULT_POPUP_TYPE, caption, text, QPixmap(), parent, -1, p);
527 }
528 
529 KPassivePopup *KPassivePopup::message(const QString &caption, const QString &text, const QPixmap &icon, WId parent, int timeout, const QPoint &p)
530 {
531  return message(DEFAULT_POPUP_TYPE, caption, text, icon, parent, timeout, p);
532 }
533 
534 KPassivePopup *KPassivePopup::message(const QString &caption, const QString &text, const QPixmap &icon, QSystemTrayIcon *parent, int timeout)
535 {
536  return message(DEFAULT_POPUP_TYPE, caption, text, icon, parent, timeout);
537 }
538 
540 {
541  return message(DEFAULT_POPUP_TYPE, QString(), text, QPixmap(), parent);
542 }
543 
545 {
546  return message(DEFAULT_POPUP_TYPE, caption, text, QPixmap(), parent);
547 }
548 
550 KPassivePopup::message(int popupStyle, const QString &caption, const QString &text, const QPixmap &icon, QWidget *parent, int timeout, const QPoint &p)
551 {
552  KPassivePopup *pop = new KPassivePopup(parent);
553  pop->setPopupStyle(popupStyle);
554  pop->setAutoDelete(true);
555  pop->setView(caption, text, icon);
556  pop->d->hideDelay = timeout < 0 ? DEFAULT_POPUP_TIME : timeout;
557  if (p.isNull()) {
558  pop->show();
559  } else {
560  pop->show(p);
561  }
562 
563  return pop;
564 }
565 
566 KPassivePopup *KPassivePopup::message(int popupStyle, const QString &text, QWidget *parent, const QPoint &p)
567 {
568  return message(popupStyle, QString(), text, QPixmap(), parent, -1, p);
569 }
570 
571 KPassivePopup *KPassivePopup::message(int popupStyle, const QString &caption, const QString &text, QWidget *parent, const QPoint &p)
572 {
573  return message(popupStyle, caption, text, QPixmap(), parent, -1, p);
574 }
575 
577 KPassivePopup::message(int popupStyle, const QString &caption, const QString &text, const QPixmap &icon, WId parent, int timeout, const QPoint &p)
578 {
579  KPassivePopup *pop = new KPassivePopup(parent);
580  pop->setPopupStyle(popupStyle);
581  pop->setAutoDelete(true);
582  pop->setView(caption, text, icon);
583  pop->d->hideDelay = timeout < 0 ? DEFAULT_POPUP_TIME : timeout;
584  if (p.isNull()) {
585  pop->show();
586  } else {
587  pop->show(p);
588  }
589 
590  return pop;
591 }
592 
593 KPassivePopup *KPassivePopup::message(int popupStyle, const QString &caption, const QString &text, const QPixmap &icon, QSystemTrayIcon *parent, int timeout)
594 {
595  KPassivePopup *pop = new KPassivePopup();
596  pop->setPopupStyle(popupStyle);
597  pop->setAutoDelete(true);
598  pop->setView(caption, text, icon);
599  pop->d->hideDelay = timeout < 0 ? DEFAULT_POPUP_TIME : timeout;
600  QPoint pos = pop->d->calculateNearbyPoint(parent->geometry());
601  pop->show(pos);
602  pop->moveNear(parent->geometry());
603 
604  return pop;
605 }
606 
607 KPassivePopup *KPassivePopup::message(int popupStyle, const QString &text, QSystemTrayIcon *parent)
608 {
609  return message(popupStyle, QString(), text, QPixmap(), parent);
610 }
611 
612 KPassivePopup *KPassivePopup::message(int popupStyle, const QString &caption, const QString &text, QSystemTrayIcon *parent)
613 {
614  return message(popupStyle, caption, text, QPixmap(), parent);
615 }
616 
617 #endif
618 
619 // Local Variables:
620 // End:
~KPassivePopup() override
Cleans up.
QPoint pos() const const
AlignLeft
QTextStream & right(QTextStream &stream)
NET::States state() const
@ Boxed
Information will appear in a framed box (default)
Definition: kpassivepopup.h:92
bool isNull() const const
KPassivePopup(QWidget *parent=nullptr, Qt::WindowFlags f=Qt::WindowFlags())
Creates a popup for the specified widget.
QWidget(QWidget *parent, Qt::WindowFlags f)
virtual void positionSelf()
Positions the popup.
QPoint topLeft() const const
void setPalette(const QPalette &)
int right() const const
Q_EMITQ_EMIT
void mouseReleaseEvent(QMouseEvent *e) override
@ Balloon
Information will appear in a comic-alike balloon.
Definition: kpassivepopup.h:93
QList< QScreen * > screens()
void show(const QPoint &p)
Shows the popup in the given point.
QPalette palette()
void update()
bool setStretchFactor(QWidget *widget, int stretch)
SkipTaskbar
void clicked()
Emitted when the popup is clicked.
int width() const const
QPoint anchor() const
Returns the position to which this popup is anchored.
int x() const const
int y() const const
void setFrameStyle(int style)
int x() const const
int y() const const
A dialog-like popup that displays messages without interrupting the user.
Definition: kpassivepopup.h:81
int left() const const
void hideEvent(QHideEvent *) override
void hide()
void addWidget(QWidget *widget, int stretch, Qt::Alignment alignment)
virtual int pixelMetric(QStyle::PixelMetric metric, const QStyleOption *option, const QWidget *widget) const const=0
void drawPolygon(const QPointF *points, int pointCount, Qt::FillRule fillRule)
bool begin(QPaintDevice *device)
typedef WindowFlags
void deleteLater()
int bottom() const const
LinksAccessibleByMouse
int top() const const
void setWindowFlags(Qt::WindowFlags type)
QStyle * style() const const
NETSize size
void setLineWidth(int)
static KPassivePopup * message(const QString &text, QWidget *parent, const QPoint &p=QPoint())
Convenience method that displays popup with the specified message beside the icon of the specified wi...
void timeout()
bool isEmpty() const const
QWidget * view() const
Returns the main view.
QWidget * find(WId id)
virtual QSize sizeHint() const const override
void moveNear(const QRect &target)
Moves the popup to be adjacent to target.
NETPoint pos
QPoint center() const const
QWidget * standardView(const QString &caption, const QString &text, const QPixmap &icon, QWidget *parent=nullptr)
Returns a widget that is used as standard view if one of the setView() methods taking the QString arg...
bool isNull() const const
void resize(int size)
bool valid(bool withdrawn_is_valid=false) const
virtual QPoint defaultLocation() const
Returns a default location for popups when a better placement cannot be found.
PM_LayoutLeftMargin
void show()
void paintEvent(QPaintEvent *pe) override
bool isNull() const const
void resize(int w, int h)
void setPopupStyle(int popupstyle)
Sets the visual appearance of the popup.
void setPoint(int index, int x, int y)
int height() const const
QString path(const QString &relativePath)
void setRect(int x, int y, int width, int height)
virtual void paintEvent(QPaintEvent *) override
int height
QRect geometry() const
void setContentsMargins(int left, int top, int right, int bottom)
void setTimeout(int delay)
Sets the delay for the popup is removed automatically.
void setAnchor(const QPoint &anchor)
Sets the anchor of this popup.
void move(int x, int y)
int count(const T &value) const const
void addLayout(QLayout *layout, int stretch)
void setBold(bool enable)
virtual void setAutoDelete(bool autoDelete)
Sets whether the popup will be deleted when it is hidden.
KJOBWIDGETS_EXPORT QWidget * window(KJob *job)
NETRect iconGeometry() const
void setMask(const QBitmap &bitmap)
SolidPattern
QObject * parent() const const
void setView(QWidget *child)
Sets the main view to be the specified widget (which must be a child of the popup).
void setVisible(bool visible) override
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Mon May 8 2023 03:49:15 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.