File indexing completed on 2025-02-16 13:11:52
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"