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

KDE's Doxygen guidelines are available online.