File indexing completed on 2024-04-28 15:29:10
0001 /* 0002 SPDX-FileCopyrightText: 2001-2006 Richard Moore <rich@kde.org> 0003 SPDX-FileCopyrightText: 2004-2005 Sascha Cunz <sascha.cunz@tiscali.de> 0004 0005 SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "kpassivepopup.h" 0009 0010 #include "debug_p.h" 0011 #include <config-knotifications.h> 0012 0013 #if KNOTIFICATIONS_BUILD_DEPRECATED_SINCE(5, 79) 0014 // Qt 0015 #include <QBitmap> 0016 #include <QBoxLayout> 0017 #include <QGuiApplication> 0018 #include <QLabel> 0019 #include <QMouseEvent> 0020 #include <QPainter> 0021 #include <QPainterPath> 0022 #include <QScreen> 0023 #include <QStyle> 0024 #include <QSystemTrayIcon> 0025 #include <QTimer> 0026 #include <QToolTip> 0027 0028 #if HAVE_X11 0029 #include <QX11Info> 0030 #include <netwm.h> 0031 #endif 0032 0033 #if HAVE_KWINDOWSYSTEM 0034 #include <KWindowInfo> 0035 #endif 0036 0037 0038 static const int DEFAULT_POPUP_TYPE = KPassivePopup::Boxed; 0039 static const int DEFAULT_POPUP_TIME = 6 * 1000; 0040 static const Qt::WindowFlags POPUP_FLAGS = Qt::Tool | Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint; 0041 0042 class Q_DECL_HIDDEN KPassivePopup::Private 0043 { 0044 public: 0045 Private(KPassivePopup *q, WId winId) 0046 : q(q) 0047 , popupStyle(DEFAULT_POPUP_TYPE) 0048 , window(winId) 0049 , hideDelay(DEFAULT_POPUP_TIME) 0050 , hideTimer(new QTimer(q)) 0051 , autoDelete(false) 0052 { 0053 #if HAVE_X11 0054 if (QX11Info::isPlatformX11()) { 0055 q->setWindowFlags(POPUP_FLAGS | Qt::X11BypassWindowManagerHint); 0056 } else 0057 #else 0058 q->setWindowFlags(POPUP_FLAGS); 0059 #endif 0060 q->setFrameStyle(QFrame::Box | QFrame::Plain); 0061 q->setLineWidth(2); 0062 0063 if (popupStyle == Boxed) { 0064 q->setFrameStyle(QFrame::Box | QFrame::Plain); 0065 q->setLineWidth(2); 0066 } else if (popupStyle == Balloon) { 0067 q->setPalette(QToolTip::palette()); 0068 } 0069 connect(hideTimer, &QTimer::timeout, q, &QWidget::hide); 0070 connect(q, qOverload<>(&KPassivePopup::clicked), q, &QWidget::hide); 0071 } 0072 0073 KPassivePopup *const q; 0074 0075 int popupStyle; 0076 QPolygon surround; 0077 QPoint anchor; 0078 QPoint fixedPosition; 0079 0080 WId window; 0081 QWidget *msgView = nullptr; 0082 QBoxLayout *topLayout = nullptr; 0083 int hideDelay; 0084 QTimer *hideTimer = nullptr; 0085 0086 QLabel *ttlIcon = nullptr; 0087 QLabel *ttl = nullptr; 0088 QLabel *msg = nullptr; 0089 0090 bool autoDelete = false; 0091 0092 /** 0093 * Updates the transparency mask. Unused if PopupStyle == Boxed 0094 */ 0095 void updateMask() 0096 { 0097 // get screen-geometry for screen our anchor is on 0098 // (geometry can differ from screen to screen! 0099 QRect deskRect = desktopRectForPoint(anchor); 0100 0101 const int width = q->width(); 0102 const int height = q->height(); 0103 0104 int xh = 70; 0105 int xl = 40; 0106 if (width < 80) { 0107 xh = xl = 40; 0108 } else if (width < 110) { 0109 xh = width - 40; 0110 } 0111 0112 bool bottom = (anchor.y() + height) > ((deskRect.y() + deskRect.height() - 48)); 0113 bool right = (anchor.x() + width) > ((deskRect.x() + deskRect.width() - 48)); 0114 0115 /* clang-format off */ 0116 QPoint corners[4] = {QPoint(width - 50, 10), 0117 QPoint(10, 10), 0118 QPoint(10, height - 50), 0119 QPoint(width - 50, height - 50)}; 0120 /* clang-format on */ 0121 0122 QBitmap mask(width, height); 0123 mask.clear(); 0124 QPainter p(&mask); 0125 QBrush brush(Qt::color1, Qt::SolidPattern); 0126 p.setBrush(brush); 0127 0128 int i = 0; 0129 int z = 0; 0130 for (; i < 4; ++i) { 0131 QPainterPath path; 0132 path.moveTo(corners[i].x(), corners[i].y()); 0133 path.arcTo(corners[i].x(), corners[i].y(), 40, 40, i * 90, 90); 0134 QPolygon corner = path.toFillPolygon().toPolygon(); 0135 0136 surround.resize(z + corner.count() - 1); 0137 for (int s = 1; s < corner.count() - 1; ++s, ++z) { 0138 surround.setPoint(z, corner[s]); 0139 } 0140 0141 if (bottom && i == 2) { 0142 if (right) { 0143 surround.resize(z + 3); 0144 surround.setPoint(z++, QPoint(width - xh, height - 10)); 0145 surround.setPoint(z++, QPoint(width - 20, height)); 0146 surround.setPoint(z++, QPoint(width - xl, height - 10)); 0147 } else { 0148 surround.resize(z + 3); 0149 surround.setPoint(z++, QPoint(xl, height - 10)); 0150 surround.setPoint(z++, QPoint(20, height)); 0151 surround.setPoint(z++, QPoint(xh, height - 10)); 0152 } 0153 } else if (!bottom && i == 0) { 0154 if (right) { 0155 surround.resize(z + 3); 0156 surround.setPoint(z++, QPoint(width - xl, 10)); 0157 surround.setPoint(z++, QPoint(width - 20, 0)); 0158 surround.setPoint(z++, QPoint(width - xh, 10)); 0159 } else { 0160 surround.resize(z + 3); 0161 surround.setPoint(z++, QPoint(xh, 10)); 0162 surround.setPoint(z++, QPoint(20, 0)); 0163 surround.setPoint(z++, QPoint(xl, 10)); 0164 } 0165 } 0166 } 0167 0168 surround.resize(z + 1); 0169 surround.setPoint(z, surround[0]); 0170 p.drawPolygon(surround); 0171 q->setMask(mask); 0172 0173 q->move(right ? anchor.x() - width + 20 : (anchor.x() < 11 ? 11 : anchor.x() - 20), // 0174 bottom ? anchor.y() - height : (anchor.y() < 11 ? 11 : anchor.y())); 0175 0176 q->update(); 0177 } 0178 0179 /** 0180 * Calculates the position to place the popup near the specified rectangle. 0181 */ 0182 QPoint calculateNearbyPoint(const QRect &target) 0183 { 0184 QPoint pos = target.topLeft(); 0185 int x = pos.x(); 0186 int y = pos.y(); 0187 int w = q->minimumSizeHint().width(); 0188 int h = q->minimumSizeHint().height(); 0189 0190 QRect r = desktopRectForPoint(QPoint(x + w / 2, y + h / 2)); 0191 0192 if (popupStyle == Balloon) { 0193 // find a point to anchor to 0194 if (x + w > r.width()) { 0195 x = x + target.width(); 0196 } 0197 0198 if (y + h > r.height()) { 0199 y = y + target.height(); 0200 } 0201 } else { 0202 if (x < r.center().x()) { 0203 x = x + target.width(); 0204 } else { 0205 x = x - w; 0206 } 0207 0208 // It's apparently trying to go off screen, so display it ALL at the bottom. 0209 if ((y + h) > r.bottom()) { 0210 y = r.bottom() - h; 0211 } 0212 0213 if ((x + w) > r.right()) { 0214 x = r.right() - w; 0215 } 0216 } 0217 if (y < r.top()) { 0218 y = r.top(); 0219 } 0220 0221 if (x < r.left()) { 0222 x = r.left(); 0223 } 0224 0225 return QPoint(x, y); 0226 } 0227 0228 QRect desktopRectForPoint(const QPoint &point) 0229 { 0230 const QList<QScreen *> screens = QGuiApplication::screens(); 0231 for (const QScreen *screen : screens) { 0232 if (screen->geometry().contains(point)) { 0233 return screen->geometry(); 0234 } 0235 } 0236 0237 // If no screen was found, return the primary screen's geometry 0238 return QGuiApplication::primaryScreen()->geometry(); 0239 } 0240 }; 0241 0242 KPassivePopup::KPassivePopup(QWidget *parent, Qt::WindowFlags f) 0243 : QFrame(nullptr, f ? f : POPUP_FLAGS) 0244 , d(new Private(this, parent ? parent->effectiveWinId() : 0L)) 0245 { 0246 } 0247 0248 KPassivePopup::KPassivePopup(WId win) 0249 : QFrame(nullptr) 0250 , d(new Private(this, win)) 0251 { 0252 } 0253 0254 KPassivePopup::~KPassivePopup() = default; 0255 0256 void KPassivePopup::setPopupStyle(int popupstyle) 0257 { 0258 if (d->popupStyle == popupstyle) { 0259 return; 0260 } 0261 0262 d->popupStyle = popupstyle; 0263 if (d->popupStyle == Boxed) { 0264 setFrameStyle(QFrame::Box | QFrame::Plain); 0265 setLineWidth(2); 0266 } else if (d->popupStyle == Balloon) { 0267 setPalette(QToolTip::palette()); 0268 } 0269 } 0270 0271 void KPassivePopup::setView(QWidget *child) 0272 { 0273 delete d->msgView; 0274 d->msgView = child; 0275 0276 delete d->topLayout; 0277 d->topLayout = new QVBoxLayout(this); 0278 if (d->popupStyle == Balloon) { 0279 auto *style = this->style(); 0280 const int leftMarginHint = 2 * style->pixelMetric(QStyle::PM_LayoutLeftMargin); 0281 const int topMarginHint = 2 * style->pixelMetric(QStyle::PM_LayoutTopMargin); 0282 const int rightMarginHint = 2 * style->pixelMetric(QStyle::PM_LayoutRightMargin); 0283 const int bottomMarginHint = 2 * style->pixelMetric(QStyle::PM_LayoutBottomMargin); 0284 d->topLayout->setContentsMargins(leftMarginHint, topMarginHint, rightMarginHint, bottomMarginHint); 0285 } 0286 d->topLayout->addWidget(d->msgView); 0287 d->topLayout->activate(); 0288 } 0289 0290 void KPassivePopup::setView(const QString &caption, const QString &text, const QPixmap &icon) 0291 { 0292 // qCDebug(LOG_KNOTIFICATIONS) << "KPassivePopup::setView " << caption << ", " << text; 0293 setView(standardView(caption, text, icon, this)); 0294 } 0295 0296 QWidget *KPassivePopup::standardView(const QString &caption, const QString &text, const QPixmap &icon, QWidget *parent) 0297 { 0298 QWidget *top = new QWidget(parent ? parent : this); 0299 QVBoxLayout *vb = new QVBoxLayout(top); 0300 vb->setContentsMargins(0, 0, 0, 0); 0301 0302 QHBoxLayout *hb = nullptr; 0303 if (!icon.isNull()) { 0304 hb = new QHBoxLayout; 0305 hb->setContentsMargins(0, 0, 0, 0); 0306 vb->addLayout(hb); 0307 d->ttlIcon = new QLabel(top); 0308 d->ttlIcon->setPixmap(icon); 0309 d->ttlIcon->setAlignment(Qt::AlignLeft); 0310 hb->addWidget(d->ttlIcon); 0311 } 0312 0313 if (!caption.isEmpty()) { 0314 d->ttl = new QLabel(caption, top); 0315 QFont fnt = d->ttl->font(); 0316 fnt.setBold(true); 0317 d->ttl->setFont(fnt); 0318 d->ttl->setAlignment(Qt::AlignHCenter); 0319 0320 if (hb) { 0321 hb->addWidget(d->ttl); 0322 hb->setStretchFactor(d->ttl, 10); // enforce centering 0323 } else { 0324 vb->addWidget(d->ttl); 0325 } 0326 } 0327 0328 if (!text.isEmpty()) { 0329 d->msg = new QLabel(text, top); 0330 d->msg->setAlignment(Qt::AlignLeft); 0331 d->msg->setTextInteractionFlags(Qt::LinksAccessibleByMouse); 0332 d->msg->setOpenExternalLinks(true); 0333 vb->addWidget(d->msg); 0334 } 0335 0336 return top; 0337 } 0338 0339 void KPassivePopup::setView(const QString &caption, const QString &text) 0340 { 0341 setView(caption, text, QPixmap()); 0342 } 0343 0344 QWidget *KPassivePopup::view() const 0345 { 0346 return d->msgView; 0347 } 0348 0349 int KPassivePopup::timeout() const 0350 { 0351 return d->hideDelay; 0352 } 0353 0354 void KPassivePopup::setTimeout(int delay) 0355 { 0356 d->hideDelay = delay < 0 ? DEFAULT_POPUP_TIME : delay; 0357 if (d->hideTimer->isActive()) { 0358 if (delay) { 0359 d->hideTimer->start(delay); 0360 } else { 0361 d->hideTimer->stop(); 0362 } 0363 } 0364 } 0365 0366 bool KPassivePopup::autoDelete() const 0367 { 0368 return d->autoDelete; 0369 } 0370 0371 void KPassivePopup::setAutoDelete(bool autoDelete) 0372 { 0373 d->autoDelete = autoDelete; 0374 } 0375 0376 void KPassivePopup::mouseReleaseEvent(QMouseEvent *e) 0377 { 0378 Q_EMIT clicked(); 0379 Q_EMIT clicked(e->pos()); 0380 } 0381 0382 // 0383 // Main Implementation 0384 // 0385 0386 void KPassivePopup::setVisible(bool visible) 0387 { 0388 if (!visible) { 0389 QFrame::setVisible(visible); 0390 return; 0391 } 0392 0393 if (size() != sizeHint()) { 0394 resize(sizeHint()); 0395 } 0396 0397 if (d->fixedPosition.isNull()) { 0398 positionSelf(); 0399 } else { 0400 if (d->popupStyle == Balloon) { 0401 setAnchor(d->fixedPosition); 0402 } else { 0403 move(d->fixedPosition); 0404 } 0405 } 0406 QFrame::setVisible(/*visible=*/true); 0407 0408 int delay = d->hideDelay; 0409 if (delay < 0) { 0410 delay = DEFAULT_POPUP_TIME; 0411 } 0412 0413 if (delay > 0) { 0414 d->hideTimer->start(delay); 0415 } 0416 } 0417 0418 void KPassivePopup::show(const QPoint &p) 0419 { 0420 d->fixedPosition = p; 0421 show(); 0422 } 0423 0424 void KPassivePopup::hideEvent(QHideEvent *) 0425 { 0426 d->hideTimer->stop(); 0427 if (d->autoDelete) { 0428 deleteLater(); 0429 } 0430 } 0431 0432 QPoint KPassivePopup::defaultLocation() const 0433 { 0434 const QRect r = QGuiApplication::primaryScreen()->availableGeometry(); 0435 return QPoint(r.left(), r.top()); 0436 } 0437 0438 void KPassivePopup::positionSelf() 0439 { 0440 QRect target; 0441 0442 if (d->window) { 0443 #if HAVE_X11 0444 if (QX11Info::isPlatformX11()) { 0445 NETWinInfo ni(QX11Info::connection(), d->window, QX11Info::appRootWindow(), NET::WMIconGeometry | NET::WMState, NET::Properties2()); 0446 0447 // Try to put the popup by the taskbar entry 0448 if (!(ni.state() & NET::SkipTaskbar)) { 0449 NETRect r = ni.iconGeometry(); 0450 target.setRect(r.pos.x, r.pos.y, r.size.width, r.size.height); 0451 } 0452 } 0453 #endif 0454 // If that failed, put it by the window itself 0455 0456 if (target.isNull()) { 0457 // Avoid making calls to the window system if we can 0458 QWidget *widget = QWidget::find(d->window); 0459 if (widget) { 0460 target = widget->geometry(); 0461 } 0462 } 0463 #if HAVE_KWINDOWSYSTEM 0464 if (target.isNull()) { 0465 KWindowInfo info(d->window, NET::WMGeometry); 0466 if (info.valid()) { 0467 target = info.geometry(); 0468 } 0469 } 0470 #endif 0471 } 0472 if (target.isNull()) { 0473 target = QRect(defaultLocation(), QSize(0, 0)); 0474 } 0475 moveNear(target); 0476 } 0477 0478 void KPassivePopup::moveNear(const QRect &target) 0479 { 0480 QPoint pos = d->calculateNearbyPoint(target); 0481 if (d->popupStyle == Balloon) { 0482 setAnchor(pos); 0483 } else { 0484 move(pos.x(), pos.y()); 0485 } 0486 } 0487 0488 QPoint KPassivePopup::anchor() const 0489 { 0490 return d->anchor; 0491 } 0492 0493 void KPassivePopup::setAnchor(const QPoint &anchor) 0494 { 0495 d->anchor = anchor; 0496 d->updateMask(); 0497 } 0498 0499 void KPassivePopup::paintEvent(QPaintEvent *pe) 0500 { 0501 if (d->popupStyle == Balloon) { 0502 QPainter p; 0503 p.begin(this); 0504 p.drawPolygon(d->surround); 0505 } else { 0506 QFrame::paintEvent(pe); 0507 } 0508 } 0509 0510 // 0511 // Convenience Methods 0512 // 0513 0514 KPassivePopup *KPassivePopup::message(const QString &caption, const QString &text, const QPixmap &icon, QWidget *parent, int timeout, const QPoint &p) 0515 { 0516 return message(DEFAULT_POPUP_TYPE, caption, text, icon, parent, timeout, p); 0517 } 0518 0519 KPassivePopup *KPassivePopup::message(const QString &text, QWidget *parent, const QPoint &p) 0520 { 0521 return message(DEFAULT_POPUP_TYPE, QString(), text, QPixmap(), parent, -1, p); 0522 } 0523 0524 KPassivePopup *KPassivePopup::message(const QString &caption, const QString &text, QWidget *parent, const QPoint &p) 0525 { 0526 return message(DEFAULT_POPUP_TYPE, caption, text, QPixmap(), parent, -1, p); 0527 } 0528 0529 KPassivePopup *KPassivePopup::message(const QString &caption, const QString &text, const QPixmap &icon, WId parent, int timeout, const QPoint &p) 0530 { 0531 return message(DEFAULT_POPUP_TYPE, caption, text, icon, parent, timeout, p); 0532 } 0533 0534 KPassivePopup *KPassivePopup::message(const QString &caption, const QString &text, const QPixmap &icon, QSystemTrayIcon *parent, int timeout) 0535 { 0536 return message(DEFAULT_POPUP_TYPE, caption, text, icon, parent, timeout); 0537 } 0538 0539 KPassivePopup *KPassivePopup::message(const QString &text, QSystemTrayIcon *parent) 0540 { 0541 return message(DEFAULT_POPUP_TYPE, QString(), text, QPixmap(), parent); 0542 } 0543 0544 KPassivePopup *KPassivePopup::message(const QString &caption, const QString &text, QSystemTrayIcon *parent) 0545 { 0546 return message(DEFAULT_POPUP_TYPE, caption, text, QPixmap(), parent); 0547 } 0548 0549 KPassivePopup * 0550 KPassivePopup::message(int popupStyle, const QString &caption, const QString &text, const QPixmap &icon, QWidget *parent, int timeout, const QPoint &p) 0551 { 0552 KPassivePopup *pop = new KPassivePopup(parent); 0553 pop->setPopupStyle(popupStyle); 0554 pop->setAutoDelete(true); 0555 pop->setView(caption, text, icon); 0556 pop->d->hideDelay = timeout < 0 ? DEFAULT_POPUP_TIME : timeout; 0557 if (p.isNull()) { 0558 pop->show(); 0559 } else { 0560 pop->show(p); 0561 } 0562 0563 return pop; 0564 } 0565 0566 KPassivePopup *KPassivePopup::message(int popupStyle, const QString &text, QWidget *parent, const QPoint &p) 0567 { 0568 return message(popupStyle, QString(), text, QPixmap(), parent, -1, p); 0569 } 0570 0571 KPassivePopup *KPassivePopup::message(int popupStyle, const QString &caption, const QString &text, QWidget *parent, const QPoint &p) 0572 { 0573 return message(popupStyle, caption, text, QPixmap(), parent, -1, p); 0574 } 0575 0576 KPassivePopup * 0577 KPassivePopup::message(int popupStyle, const QString &caption, const QString &text, const QPixmap &icon, WId parent, int timeout, const QPoint &p) 0578 { 0579 KPassivePopup *pop = new KPassivePopup(parent); 0580 pop->setPopupStyle(popupStyle); 0581 pop->setAutoDelete(true); 0582 pop->setView(caption, text, icon); 0583 pop->d->hideDelay = timeout < 0 ? DEFAULT_POPUP_TIME : timeout; 0584 if (p.isNull()) { 0585 pop->show(); 0586 } else { 0587 pop->show(p); 0588 } 0589 0590 return pop; 0591 } 0592 0593 KPassivePopup *KPassivePopup::message(int popupStyle, const QString &caption, const QString &text, const QPixmap &icon, QSystemTrayIcon *parent, int timeout) 0594 { 0595 KPassivePopup *pop = new KPassivePopup(); 0596 pop->setPopupStyle(popupStyle); 0597 pop->setAutoDelete(true); 0598 pop->setView(caption, text, icon); 0599 pop->d->hideDelay = timeout < 0 ? DEFAULT_POPUP_TIME : timeout; 0600 QPoint pos = pop->d->calculateNearbyPoint(parent->geometry()); 0601 pop->show(pos); 0602 pop->moveNear(parent->geometry()); 0603 0604 return pop; 0605 } 0606 0607 KPassivePopup *KPassivePopup::message(int popupStyle, const QString &text, QSystemTrayIcon *parent) 0608 { 0609 return message(popupStyle, QString(), text, QPixmap(), parent); 0610 } 0611 0612 KPassivePopup *KPassivePopup::message(int popupStyle, const QString &caption, const QString &text, QSystemTrayIcon *parent) 0613 { 0614 return message(popupStyle, caption, text, QPixmap(), parent); 0615 } 0616 0617 #include "moc_kpassivepopup.cpp" 0618 0619 #endif 0620 0621 // Local Variables: 0622 // End: