File indexing completed on 2024-04-28 15:32:16

0001 /*
0002     This file is part of the KDE project
0003     SPDX-FileCopyrightText: 2008 Fredrik Höglund <fredrik@kde.org>
0004     SPDX-FileCopyrightText: 2008 Konstantin Heil <konst.heil@stud.uni-heidelberg.de>
0005     SPDX-FileCopyrightText: 2009 Peter Penz <peter.penz@gmx.at>
0006     SPDX-FileCopyrightText: 2017 Elvis Angelaccio <elvis.angelaccio@kde.org>
0007 
0008     SPDX-License-Identifier: LGPL-2.1-or-later
0009 */
0010 
0011 #include "ktooltipwidget.h"
0012 
0013 #include <QPaintEvent>
0014 #include <QScreen>
0015 #include <QStyleOptionFrame>
0016 #include <QStylePainter>
0017 #include <QTimer>
0018 #include <QVBoxLayout>
0019 #include <QWindow>
0020 
0021 class KToolTipWidgetPrivate
0022 {
0023 public:
0024     KToolTipWidgetPrivate(KToolTipWidget *parent)
0025         : q(parent)
0026     {
0027     }
0028 
0029     void init();
0030     void addWidget(QWidget *widget);
0031     void removeWidget();
0032     void show(const QPoint &pos, QWindow *transientParent);
0033     void storeParent();
0034     void restoreParent();
0035     QPoint centerBelow(const QRect &rect, QScreen *screen) const;
0036 
0037     KToolTipWidget *const q;
0038     QTimer hideTimer;
0039     QVBoxLayout *layout = nullptr;
0040     QWidget *content = nullptr;
0041     QWidget *contentParent = nullptr;
0042 };
0043 
0044 void KToolTipWidgetPrivate::init()
0045 {
0046     layout = new QVBoxLayout(q);
0047 
0048     hideTimer.setSingleShot(true);
0049     hideTimer.setInterval(500);
0050 
0051     QObject::connect(&hideTimer, &QTimer::timeout, q, &QWidget::hide);
0052 
0053     q->setAttribute(Qt::WA_TranslucentBackground);
0054     q->setWindowFlags(Qt::ToolTip | Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint);
0055 }
0056 
0057 void KToolTipWidgetPrivate::addWidget(QWidget *widget)
0058 {
0059     removeWidget();
0060     content = widget;
0061     storeParent();
0062     layout->addWidget(content);
0063     QObject::connect(content, &QWidget::destroyed, q, &QWidget::hide);
0064 }
0065 
0066 void KToolTipWidgetPrivate::removeWidget()
0067 {
0068     layout->removeWidget(content);
0069     restoreParent();
0070 }
0071 
0072 void KToolTipWidgetPrivate::show(const QPoint &pos, QWindow *transientParent)
0073 {
0074     if (pos.isNull()) {
0075         return;
0076     }
0077 
0078     q->move(pos);
0079     q->createWinId();
0080     q->windowHandle()->setProperty("ENABLE_BLUR_BEHIND_HINT", true);
0081     q->windowHandle()->setTransientParent(transientParent);
0082     q->show();
0083 }
0084 
0085 void KToolTipWidgetPrivate::storeParent()
0086 {
0087     if (!content) {
0088         return;
0089     }
0090 
0091     contentParent = qobject_cast<QWidget *>(content->parent());
0092 }
0093 
0094 void KToolTipWidgetPrivate::restoreParent()
0095 {
0096     if (!content || !contentParent) {
0097         return;
0098     }
0099 
0100     content->setParent(contentParent);
0101 }
0102 
0103 QPoint KToolTipWidgetPrivate::centerBelow(const QRect &rect, QScreen *screen) const
0104 {
0105     // It must be assured that:
0106     // - the content is fully visible
0107     // - the content is not drawn inside rect
0108 
0109     const QSize size = q->sizeHint();
0110     const int margin = q->style()->pixelMetric(QStyle::PM_ToolTipLabelFrameWidth);
0111     const QRect screenGeometry = screen->geometry();
0112 
0113     const bool hasRoomToLeft = (rect.left() - size.width() - margin >= screenGeometry.left());
0114     const bool hasRoomToRight = (rect.right() + size.width() + margin <= screenGeometry.right());
0115     const bool hasRoomAbove = (rect.top() - size.height() - margin >= screenGeometry.top());
0116     const bool hasRoomBelow = (rect.bottom() + size.height() + margin <= screenGeometry.bottom());
0117     if (!hasRoomAbove && !hasRoomBelow && !hasRoomToLeft && !hasRoomToRight) {
0118         return QPoint();
0119     }
0120 
0121     int x = 0;
0122     int y = 0;
0123     if (hasRoomBelow || hasRoomAbove) {
0124         x = qMax(screenGeometry.left(), rect.center().x() - size.width() / 2);
0125         if (x + size.width() >= screenGeometry.right()) {
0126             x = screenGeometry.right() - size.width() + 1;
0127         }
0128         Q_ASSERT(x >= 0);
0129         if (hasRoomBelow) {
0130             y = rect.bottom() + margin;
0131         } else {
0132             y = rect.top() - size.height() - margin + 1;
0133         }
0134     } else {
0135         Q_ASSERT(hasRoomToLeft || hasRoomToRight);
0136         if (hasRoomToRight) {
0137             x = rect.right() + margin;
0138         } else {
0139             x = rect.left() - size.width() - margin + 1;
0140         }
0141         // Put the tooltip at the bottom of the screen. The x-coordinate has already
0142         // been adjusted, so that no overlapping with rect occurs.
0143         y = screenGeometry.bottom() - size.height() + 1;
0144     }
0145 
0146     return QPoint(x, y);
0147 }
0148 
0149 KToolTipWidget::KToolTipWidget(QWidget *parent)
0150     : QWidget(parent)
0151     , d(new KToolTipWidgetPrivate(this))
0152 {
0153     d->init();
0154 }
0155 
0156 KToolTipWidget::~KToolTipWidget()
0157 {
0158     d->restoreParent();
0159 }
0160 
0161 void KToolTipWidget::showAt(const QPoint &pos, QWidget *content, QWindow *transientParent)
0162 {
0163     d->addWidget(content);
0164     d->show(pos, transientParent);
0165 }
0166 
0167 void KToolTipWidget::showBelow(const QRect &rect, QWidget *content, QWindow *transientParent)
0168 {
0169     d->addWidget(content);
0170 
0171     const auto contentMargins = layout()->contentsMargins();
0172     const QSize screenSize = transientParent->screen()->geometry().size();
0173 
0174     content->setMaximumSize(screenSize.shrunkBy(contentMargins));
0175 
0176     d->show(d->centerBelow(rect, transientParent->screen()), transientParent);
0177 }
0178 
0179 int KToolTipWidget::hideDelay() const
0180 {
0181     return d->hideTimer.interval();
0182 }
0183 
0184 void KToolTipWidget::hideLater()
0185 {
0186     if (!isVisible()) {
0187         return;
0188     }
0189 
0190     if (hideDelay() > 0) {
0191         d->hideTimer.start();
0192     } else {
0193         hide();
0194     }
0195 }
0196 
0197 void KToolTipWidget::setHideDelay(int delay)
0198 {
0199     d->hideTimer.setInterval(delay);
0200 }
0201 
0202 void KToolTipWidget::enterEvent(QEvent *)
0203 {
0204     // Ignore hide delay and leave tooltip visible.
0205     if (hideDelay() > 0) {
0206         d->hideTimer.stop();
0207     } else {
0208         hide();
0209     }
0210 }
0211 
0212 void KToolTipWidget::hideEvent(QHideEvent *)
0213 {
0214     d->removeWidget();
0215     // Give time to the content widget to get his own hide event.
0216     QTimer::singleShot(0, this, &KToolTipWidget::hidden);
0217 }
0218 
0219 void KToolTipWidget::leaveEvent(QEvent *)
0220 {
0221     // Don't bother starting the hide timer, we are done.
0222     hide();
0223 }
0224 
0225 void KToolTipWidget::paintEvent(QPaintEvent *event)
0226 {
0227     QStylePainter painter(this);
0228     painter.setClipRegion(event->region());
0229     QStyleOptionFrame option;
0230     option.initFrom(this);
0231     painter.drawPrimitive(QStyle::PE_PanelTipLabel, option);
0232     painter.end();
0233 
0234     QWidget::paintEvent(event);
0235 }
0236 
0237 #include "moc_ktooltipwidget.cpp"