1/*
2 This file is part of the KDE project
3 SPDX-FileCopyrightText: 2008 Fredrik Höglund <fredrik@kde.org>
4 SPDX-FileCopyrightText: 2008 Konstantin Heil <konst.heil@stud.uni-heidelberg.de>
5 SPDX-FileCopyrightText: 2009 Peter Penz <peter.penz@gmx.at>
6 SPDX-FileCopyrightText: 2017 Elvis Angelaccio <elvis.angelaccio@kde.org>
7
8 SPDX-License-Identifier: LGPL-2.1-or-later
9*/
10
11#include "ktooltipwidget.h"
12
13#include <QPaintEvent>
14#include <QScreen>
15#include <QStyleOptionFrame>
16#include <QStylePainter>
17#include <QTimer>
18#include <QVBoxLayout>
19#include <QWindow>
20
21class KToolTipWidgetPrivate
22{
23public:
24 KToolTipWidgetPrivate(KToolTipWidget *parent)
25 : q(parent)
26 {
27 }
28
29 void init();
30 void addWidget(QWidget *widget);
31 void removeWidget();
32 void show(const QPoint &pos, QWindow *transientParent);
33 void storeParent();
34 void restoreParent();
35 QPoint centerBelow(const QRect &rect, QScreen *screen) const;
36
37 KToolTipWidget *const q;
38 QTimer hideTimer;
39 QVBoxLayout *layout = nullptr;
40 QWidget *content = nullptr;
41 QWidget *contentParent = nullptr;
42};
43
44void KToolTipWidgetPrivate::init()
45{
46 layout = new QVBoxLayout(q);
47
48 hideTimer.setSingleShot(true);
49 hideTimer.setInterval(500);
50
51 QObject::connect(sender: &hideTimer, signal: &QTimer::timeout, context: q, slot: &QWidget::hide);
52
53 q->setAttribute(Qt::WA_TranslucentBackground);
54 q->setWindowFlags(Qt::ToolTip | Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint);
55}
56
57void KToolTipWidgetPrivate::addWidget(QWidget *widget)
58{
59 removeWidget();
60 content = widget;
61 storeParent();
62 layout->addWidget(content);
63 QObject::connect(sender: content, signal: &QWidget::destroyed, context: q, slot: &QWidget::hide);
64}
65
66void KToolTipWidgetPrivate::removeWidget()
67{
68 layout->removeWidget(w: content);
69 restoreParent();
70}
71
72void KToolTipWidgetPrivate::show(const QPoint &pos, QWindow *transientParent)
73{
74 if (pos.isNull()) {
75 return;
76 }
77
78 q->move(pos);
79 q->createWinId();
80 q->windowHandle()->setProperty(name: "ENABLE_BLUR_BEHIND_HINT", value: true);
81 q->windowHandle()->setTransientParent(transientParent);
82
83 QObject::connect(sender: transientParent, signal: &QWindow::activeChanged, context: q, slot: &QWidget::hide);
84
85 q->show();
86}
87
88void KToolTipWidgetPrivate::storeParent()
89{
90 if (!content) {
91 return;
92 }
93
94 contentParent = qobject_cast<QWidget *>(o: content->parent());
95}
96
97void KToolTipWidgetPrivate::restoreParent()
98{
99 if (!content || !contentParent) {
100 return;
101 }
102
103 content->setParent(contentParent);
104}
105
106QPoint KToolTipWidgetPrivate::centerBelow(const QRect &rect, QScreen *screen) const
107{
108 // It must be assured that:
109 // - the content is fully visible
110 // - the content is not drawn inside rect
111
112 const QSize size = q->sizeHint();
113 const int margin = q->style()->pixelMetric(metric: QStyle::PM_ToolTipLabelFrameWidth);
114 const QRect screenGeometry = screen->geometry();
115
116 const bool hasRoomToLeft = (rect.left() - size.width() - margin >= screenGeometry.left());
117 const bool hasRoomToRight = (rect.right() + size.width() + margin <= screenGeometry.right());
118 const bool hasRoomAbove = (rect.top() - size.height() - margin >= screenGeometry.top());
119 const bool hasRoomBelow = (rect.bottom() + size.height() + margin <= screenGeometry.bottom());
120 if (!hasRoomAbove && !hasRoomBelow && !hasRoomToLeft && !hasRoomToRight) {
121 return QPoint();
122 }
123
124 int x = 0;
125 int y = 0;
126 if (hasRoomBelow || hasRoomAbove) {
127 x = qMax(a: screenGeometry.left(), b: rect.center().x() - size.width() / 2);
128 if (x + size.width() >= screenGeometry.right()) {
129 x = screenGeometry.right() - size.width() + 1;
130 }
131 Q_ASSERT(x >= 0);
132 if (hasRoomBelow) {
133 y = rect.bottom() + margin;
134 } else {
135 y = rect.top() - size.height() - margin + 1;
136 }
137 } else {
138 Q_ASSERT(hasRoomToLeft || hasRoomToRight);
139 if (hasRoomToRight) {
140 x = rect.right() + margin;
141 } else {
142 x = rect.left() - size.width() - margin + 1;
143 }
144 // Put the tooltip at the bottom of the screen. The x-coordinate has already
145 // been adjusted, so that no overlapping with rect occurs.
146 y = screenGeometry.bottom() - size.height() + 1;
147 }
148
149 return QPoint(x, y);
150}
151
152KToolTipWidget::KToolTipWidget(QWidget *parent)
153 : QWidget(parent)
154 , d(new KToolTipWidgetPrivate(this))
155{
156 d->init();
157}
158
159KToolTipWidget::~KToolTipWidget()
160{
161 d->restoreParent();
162}
163
164void KToolTipWidget::showAt(const QPoint &pos, QWidget *content, QWindow *transientParent)
165{
166 d->addWidget(widget: content);
167 d->show(pos, transientParent);
168}
169
170void KToolTipWidget::showBelow(const QRect &rect, QWidget *content, QWindow *transientParent)
171{
172 d->addWidget(widget: content);
173
174 const auto contentMargins = layout()->contentsMargins();
175 const QSize screenSize = transientParent->screen()->geometry().size();
176
177 content->setMaximumSize(screenSize.shrunkBy(m: contentMargins));
178
179 d->show(pos: d->centerBelow(rect, screen: transientParent->screen()), transientParent);
180}
181
182int KToolTipWidget::hideDelay() const
183{
184 return d->hideTimer.interval();
185}
186
187void KToolTipWidget::hideLater()
188{
189 if (!isVisible()) {
190 return;
191 }
192
193 if (hideDelay() > 0) {
194 d->hideTimer.start();
195 } else {
196 hide();
197 }
198}
199
200void KToolTipWidget::setHideDelay(int delay)
201{
202 d->hideTimer.setInterval(delay);
203}
204
205void KToolTipWidget::enterEvent(QEnterEvent *)
206{
207 // Ignore hide delay and leave tooltip visible.
208 if (hideDelay() > 0) {
209 d->hideTimer.stop();
210 } else {
211 hide();
212 }
213}
214
215void KToolTipWidget::hideEvent(QHideEvent *)
216{
217 d->removeWidget();
218
219 QObject::disconnect(sender: windowHandle()->transientParent(), signal: &QWindow::activeChanged, receiver: this, slot: &QWidget::hide);
220
221 // Give time to the content widget to get his own hide event.
222 QTimer::singleShot(interval: 0, receiver: this, slot: &KToolTipWidget::hidden);
223}
224
225void KToolTipWidget::leaveEvent(QEvent *)
226{
227 // Don't bother starting the hide timer, we are done.
228 hide();
229}
230
231void KToolTipWidget::paintEvent(QPaintEvent *event)
232{
233 QStylePainter painter(this);
234 painter.setClipRegion(event->region());
235 QStyleOptionFrame option;
236 option.initFrom(w: this);
237 painter.drawPrimitive(pe: QStyle::PE_PanelTipLabel, opt: option);
238 painter.end();
239
240 QWidget::paintEvent(event);
241}
242
243#include "moc_ktooltipwidget.cpp"
244

source code of kwidgetsaddons/src/ktooltipwidget.cpp