File indexing completed on 2024-04-28 03:59: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 
0083     QObject::connect(transientParent, &QWindow::activeChanged, q, &QWidget::hide);
0084 
0085     q->show();
0086 }
0087 
0088 void KToolTipWidgetPrivate::storeParent()
0089 {
0090     if (!content) {
0091         return;
0092     }
0093 
0094     contentParent = qobject_cast<QWidget *>(content->parent());
0095 }
0096 
0097 void KToolTipWidgetPrivate::restoreParent()
0098 {
0099     if (!content || !contentParent) {
0100         return;
0101     }
0102 
0103     content->setParent(contentParent);
0104 }
0105 
0106 QPoint KToolTipWidgetPrivate::centerBelow(const QRect &rect, QScreen *screen) const
0107 {
0108     // It must be assured that:
0109     // - the content is fully visible
0110     // - the content is not drawn inside rect
0111 
0112     const QSize size = q->sizeHint();
0113     const int margin = q->style()->pixelMetric(QStyle::PM_ToolTipLabelFrameWidth);
0114     const QRect screenGeometry = screen->geometry();
0115 
0116     const bool hasRoomToLeft = (rect.left() - size.width() - margin >= screenGeometry.left());
0117     const bool hasRoomToRight = (rect.right() + size.width() + margin <= screenGeometry.right());
0118     const bool hasRoomAbove = (rect.top() - size.height() - margin >= screenGeometry.top());
0119     const bool hasRoomBelow = (rect.bottom() + size.height() + margin <= screenGeometry.bottom());
0120     if (!hasRoomAbove && !hasRoomBelow && !hasRoomToLeft && !hasRoomToRight) {
0121         return QPoint();
0122     }
0123 
0124     int x = 0;
0125     int y = 0;
0126     if (hasRoomBelow || hasRoomAbove) {
0127         x = qMax(screenGeometry.left(), rect.center().x() - size.width() / 2);
0128         if (x + size.width() >= screenGeometry.right()) {
0129             x = screenGeometry.right() - size.width() + 1;
0130         }
0131         Q_ASSERT(x >= 0);
0132         if (hasRoomBelow) {
0133             y = rect.bottom() + margin;
0134         } else {
0135             y = rect.top() - size.height() - margin + 1;
0136         }
0137     } else {
0138         Q_ASSERT(hasRoomToLeft || hasRoomToRight);
0139         if (hasRoomToRight) {
0140             x = rect.right() + margin;
0141         } else {
0142             x = rect.left() - size.width() - margin + 1;
0143         }
0144         // Put the tooltip at the bottom of the screen. The x-coordinate has already
0145         // been adjusted, so that no overlapping with rect occurs.
0146         y = screenGeometry.bottom() - size.height() + 1;
0147     }
0148 
0149     return QPoint(x, y);
0150 }
0151 
0152 KToolTipWidget::KToolTipWidget(QWidget *parent)
0153     : QWidget(parent)
0154     , d(new KToolTipWidgetPrivate(this))
0155 {
0156     d->init();
0157 }
0158 
0159 KToolTipWidget::~KToolTipWidget()
0160 {
0161     d->restoreParent();
0162 }
0163 
0164 void KToolTipWidget::showAt(const QPoint &pos, QWidget *content, QWindow *transientParent)
0165 {
0166     d->addWidget(content);
0167     d->show(pos, transientParent);
0168 }
0169 
0170 void KToolTipWidget::showBelow(const QRect &rect, QWidget *content, QWindow *transientParent)
0171 {
0172     d->addWidget(content);
0173 
0174     const auto contentMargins = layout()->contentsMargins();
0175     const QSize screenSize = transientParent->screen()->geometry().size();
0176 
0177     content->setMaximumSize(screenSize.shrunkBy(contentMargins));
0178 
0179     d->show(d->centerBelow(rect, transientParent->screen()), transientParent);
0180 }
0181 
0182 int KToolTipWidget::hideDelay() const
0183 {
0184     return d->hideTimer.interval();
0185 }
0186 
0187 void KToolTipWidget::hideLater()
0188 {
0189     if (!isVisible()) {
0190         return;
0191     }
0192 
0193     if (hideDelay() > 0) {
0194         d->hideTimer.start();
0195     } else {
0196         hide();
0197     }
0198 }
0199 
0200 void KToolTipWidget::setHideDelay(int delay)
0201 {
0202     d->hideTimer.setInterval(delay);
0203 }
0204 
0205 void KToolTipWidget::enterEvent(QEnterEvent *)
0206 {
0207     // Ignore hide delay and leave tooltip visible.
0208     if (hideDelay() > 0) {
0209         d->hideTimer.stop();
0210     } else {
0211         hide();
0212     }
0213 }
0214 
0215 void KToolTipWidget::hideEvent(QHideEvent *)
0216 {
0217     d->removeWidget();
0218 
0219     QObject::disconnect(windowHandle()->transientParent(), &QWindow::activeChanged, this, &QWidget::hide);
0220 
0221     // Give time to the content widget to get his own hide event.
0222     QTimer::singleShot(0, this, &KToolTipWidget::hidden);
0223 }
0224 
0225 void KToolTipWidget::leaveEvent(QEvent *)
0226 {
0227     // Don't bother starting the hide timer, we are done.
0228     hide();
0229 }
0230 
0231 void KToolTipWidget::paintEvent(QPaintEvent *event)
0232 {
0233     QStylePainter painter(this);
0234     painter.setClipRegion(event->region());
0235     QStyleOptionFrame option;
0236     option.initFrom(this);
0237     painter.drawPrimitive(QStyle::PE_PanelTipLabel, option);
0238     painter.end();
0239 
0240     QWidget::paintEvent(event);
0241 }
0242 
0243 #include "moc_ktooltipwidget.cpp"