1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtWidgets module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include <QtWidgets/private/qtwidgetsglobal_p.h>
41
42#include <qapplication.h>
43#include <qdesktopwidget.h>
44#include <private/qdesktopwidget_p.h>
45#include <qevent.h>
46#include <qpointer.h>
47#include <qstyle.h>
48#include <qstyleoption.h>
49#include <qstylepainter.h>
50#include <qtimer.h>
51#if QT_CONFIG(effects)
52#include <private/qeffects_p.h>
53#endif
54#include <qtextdocument.h>
55#include <qdebug.h>
56#include <qpa/qplatformscreen.h>
57#include <qpa/qplatformcursor.h>
58#include <private/qstylesheetstyle_p.h>
59
60#ifndef QT_NO_TOOLTIP
61#include <qlabel.h>
62#include <QtWidgets/private/qlabel_p.h>
63#include <QtGui/private/qhighdpiscaling_p.h>
64#include <qtooltip.h>
65
66QT_BEGIN_NAMESPACE
67
68/*!
69 \class QToolTip
70
71 \brief The QToolTip class provides tool tips (balloon help) for any
72 widget.
73
74 \ingroup helpsystem
75 \inmodule QtWidgets
76
77 The tip is a short piece of text reminding the user of the
78 widget's function. It is drawn immediately below the given
79 position in a distinctive black-on-yellow color combination. The
80 tip can be any \l{QTextEdit}{rich text} formatted string.
81
82 Rich text displayed in a tool tip is implicitly word-wrapped unless
83 specified differently with \c{<p style='white-space:pre'>}.
84
85 The simplest and most common way to set a widget's tool tip is by
86 calling its QWidget::setToolTip() function.
87
88 It is also possible to show different tool tips for different
89 regions of a widget, by using a QHelpEvent of type
90 QEvent::ToolTip. Intercept the help event in your widget's \l
91 {QWidget::}{event()} function and call QToolTip::showText() with
92 the text you want to display. The \l{widgets/tooltips}{Tooltips}
93 example illustrates this technique.
94
95 If you are calling QToolTip::hideText(), or QToolTip::showText()
96 with an empty string, as a result of a \l{QEvent::}{ToolTip}-event you
97 should also call \l{QEvent::}{ignore()} on the event, to signal
98 that you don't want to start any tooltip specific modes.
99
100 Note that, if you want to show tooltips in an item view, the
101 model/view architecture provides functionality to set an item's
102 tool tip; e.g., the QTableWidgetItem::setToolTip() function.
103 However, if you want to provide custom tool tips in an item view,
104 you must intercept the help event in the
105 QAbstractItemView::viewportEvent() function and handle it yourself.
106
107 The default tool tip color and font can be customized with
108 setPalette() and setFont(). When a tooltip is currently on
109 display, isVisible() returns \c true and text() the currently visible
110 text.
111
112 \note Tool tips use the inactive color group of QPalette, because tool
113 tips are not active windows.
114
115 \sa QWidget::toolTip, QAction::toolTip, {Tool Tips Example}
116*/
117
118class QTipLabel : public QLabel
119{
120 Q_OBJECT
121public:
122 QTipLabel(const QString &text, const QPoint &pos, QWidget *w, int msecDisplayTime);
123 ~QTipLabel();
124 static QTipLabel *instance;
125
126 void adjustTooltipScreen(const QPoint &pos);
127 void updateSize(const QPoint &pos);
128
129 bool eventFilter(QObject *, QEvent *) override;
130
131 QBasicTimer hideTimer, expireTimer;
132
133 bool fadingOut;
134
135 void reuseTip(const QString &text, int msecDisplayTime, const QPoint &pos);
136 void hideTip();
137 void hideTipImmediately();
138 void setTipRect(QWidget *w, const QRect &r);
139 void restartExpireTimer(int msecDisplayTime);
140 bool tipChanged(const QPoint &pos, const QString &text, QObject *o);
141 void placeTip(const QPoint &pos, QWidget *w);
142
143 static int getTipScreen(const QPoint &pos, QWidget *w);
144protected:
145 void timerEvent(QTimerEvent *e) override;
146 void paintEvent(QPaintEvent *e) override;
147 void mouseMoveEvent(QMouseEvent *e) override;
148 void resizeEvent(QResizeEvent *e) override;
149
150#ifndef QT_NO_STYLE_STYLESHEET
151public slots:
152 /** \internal
153 Cleanup the _q_stylesheet_parent propery.
154 */
155 void styleSheetParentDestroyed() {
156 setProperty(name: "_q_stylesheet_parent", value: QVariant());
157 styleSheetParent = nullptr;
158 }
159
160private:
161 QWidget *styleSheetParent;
162#endif
163
164private:
165 QWidget *widget;
166 QRect rect;
167};
168
169QTipLabel *QTipLabel::instance = nullptr;
170
171QTipLabel::QTipLabel(const QString &text, const QPoint &pos, QWidget *w, int msecDisplayTime)
172#ifndef QT_NO_STYLE_STYLESHEET
173 : QLabel(w, Qt::ToolTip | Qt::BypassGraphicsProxyWidget), styleSheetParent(nullptr), widget(nullptr)
174#else
175 : QLabel(w, Qt::ToolTip | Qt::BypassGraphicsProxyWidget), widget(0)
176#endif
177{
178 delete instance;
179 instance = this;
180 setForegroundRole(QPalette::ToolTipText);
181 setBackgroundRole(QPalette::ToolTipBase);
182 setPalette(QToolTip::palette());
183 ensurePolished();
184 setMargin(1 + style()->pixelMetric(metric: QStyle::PM_ToolTipLabelFrameWidth, option: nullptr, widget: this));
185 setFrameStyle(QFrame::NoFrame);
186 setAlignment(Qt::AlignLeft);
187 setIndent(1);
188 qApp->installEventFilter(filterObj: this);
189 setWindowOpacity(style()->styleHint(stylehint: QStyle::SH_ToolTipLabel_Opacity, opt: nullptr, widget: this) / 255.0);
190 setMouseTracking(true);
191 fadingOut = false;
192 reuseTip(text, msecDisplayTime, pos);
193}
194
195void QTipLabel::restartExpireTimer(int msecDisplayTime)
196{
197 int time = 10000 + 40 * qMax(a: 0, b: text().length()-100);
198 if (msecDisplayTime > 0)
199 time = msecDisplayTime;
200 expireTimer.start(msec: time, obj: this);
201 hideTimer.stop();
202}
203
204void QTipLabel::reuseTip(const QString &text, int msecDisplayTime, const QPoint &pos)
205{
206#ifndef QT_NO_STYLE_STYLESHEET
207 if (styleSheetParent){
208 disconnect(sender: styleSheetParent, SIGNAL(destroyed()),
209 receiver: QTipLabel::instance, SLOT(styleSheetParentDestroyed()));
210 styleSheetParent = nullptr;
211 }
212#endif
213
214 setText(text);
215 updateSize(pos);
216 restartExpireTimer(msecDisplayTime);
217}
218
219void QTipLabel::updateSize(const QPoint &pos)
220{
221#ifndef Q_OS_WINRT
222 // ### The code below does not always work well on WinRT
223 // (e.g COIN fails an auto test - tst_QToolTip::qtbug64550_stylesheet - QTBUG-72652)
224 d_func()->setScreenForPoint(pos);
225#endif
226 // Ensure that we get correct sizeHints by placing this window on the right screen.
227 QFontMetrics fm(font());
228 QSize extra(1, 0);
229 // Make it look good with the default ToolTip font on Mac, which has a small descent.
230 if (fm.descent() == 2 && fm.ascent() >= 11)
231 ++extra.rheight();
232 setWordWrap(Qt::mightBeRichText(text()));
233 QSize sh = sizeHint();
234 // ### When the above WinRT code is fixed, windowhandle should be used to find the screen.
235 QScreen *screen = QGuiApplication::screenAt(point: pos);
236 if (!screen)
237 screen = QGuiApplication::primaryScreen();
238 if (screen) {
239 const qreal screenWidth = screen->geometry().width();
240 if (!wordWrap() && sh.width() > screenWidth) {
241 setWordWrap(true);
242 sh = sizeHint();
243 }
244 }
245 resize(sh + extra);
246}
247
248void QTipLabel::paintEvent(QPaintEvent *ev)
249{
250 QStylePainter p(this);
251 QStyleOptionFrame opt;
252 opt.init(w: this);
253 p.drawPrimitive(pe: QStyle::PE_PanelTipLabel, opt);
254 p.end();
255
256 QLabel::paintEvent(ev);
257}
258
259void QTipLabel::resizeEvent(QResizeEvent *e)
260{
261 QStyleHintReturnMask frameMask;
262 QStyleOption option;
263 option.init(w: this);
264 if (style()->styleHint(stylehint: QStyle::SH_ToolTip_Mask, opt: &option, widget: this, returnData: &frameMask))
265 setMask(frameMask.region);
266
267 QLabel::resizeEvent(event: e);
268}
269
270void QTipLabel::mouseMoveEvent(QMouseEvent *e)
271{
272 if (!rect.isNull()) {
273 QPoint pos = e->globalPos();
274 if (widget)
275 pos = widget->mapFromGlobal(pos);
276 if (!rect.contains(p: pos))
277 hideTip();
278 }
279 QLabel::mouseMoveEvent(ev: e);
280}
281
282QTipLabel::~QTipLabel()
283{
284 instance = nullptr;
285}
286
287void QTipLabel::hideTip()
288{
289 if (!hideTimer.isActive())
290 hideTimer.start(msec: 300, obj: this);
291}
292
293void QTipLabel::hideTipImmediately()
294{
295 close(); // to trigger QEvent::Close which stops the animation
296 deleteLater();
297}
298
299void QTipLabel::setTipRect(QWidget *w, const QRect &r)
300{
301 if (Q_UNLIKELY(!r.isNull() && !w)) {
302 qWarning(msg: "QToolTip::setTipRect: Cannot pass null widget if rect is set");
303 return;
304 }
305 widget = w;
306 rect = r;
307}
308
309void QTipLabel::timerEvent(QTimerEvent *e)
310{
311 if (e->timerId() == hideTimer.timerId()
312 || e->timerId() == expireTimer.timerId()){
313 hideTimer.stop();
314 expireTimer.stop();
315 hideTipImmediately();
316 }
317}
318
319bool QTipLabel::eventFilter(QObject *o, QEvent *e)
320{
321 switch (e->type()) {
322#ifdef Q_OS_MACOS
323 case QEvent::KeyPress:
324 case QEvent::KeyRelease: {
325 const int key = static_cast<QKeyEvent *>(e)->key();
326 // Anything except key modifiers or caps-lock, etc.
327 if (key < Qt::Key_Shift || key > Qt::Key_ScrollLock)
328 hideTipImmediately();
329 break;
330 }
331#endif
332 case QEvent::Leave:
333 hideTip();
334 break;
335
336
337#if defined (Q_OS_QNX) // On QNX the window activate and focus events are delayed and will appear
338 // after the window is shown.
339 case QEvent::WindowActivate:
340 case QEvent::FocusIn:
341 return false;
342 case QEvent::WindowDeactivate:
343 if (o != this)
344 return false;
345 hideTipImmediately();
346 break;
347 case QEvent::FocusOut:
348 if (reinterpret_cast<QWindow*>(o) != windowHandle())
349 return false;
350 hideTipImmediately();
351 break;
352#else
353 case QEvent::WindowActivate:
354 case QEvent::WindowDeactivate:
355 case QEvent::FocusIn:
356 case QEvent::FocusOut:
357#endif
358 case QEvent::Close: // For QTBUG-55523 (QQC) specifically: Hide tooltip when windows are closed
359 case QEvent::MouseButtonPress:
360 case QEvent::MouseButtonRelease:
361 case QEvent::MouseButtonDblClick:
362 case QEvent::Wheel:
363 hideTipImmediately();
364 break;
365
366 case QEvent::MouseMove:
367 if (o == widget && !rect.isNull() && !rect.contains(p: static_cast<QMouseEvent*>(e)->pos()))
368 hideTip();
369 default:
370 break;
371 }
372 return false;
373}
374
375int QTipLabel::getTipScreen(const QPoint &pos, QWidget *w)
376{
377 if (QDesktopWidgetPrivate::isVirtualDesktop())
378 return QDesktopWidgetPrivate::screenNumber(pos);
379 else
380 return QDesktopWidgetPrivate::screenNumber(widget: w);
381}
382
383void QTipLabel::placeTip(const QPoint &pos, QWidget *w)
384{
385#ifndef QT_NO_STYLE_STYLESHEET
386 if (testAttribute(attribute: Qt::WA_StyleSheet) || (w && qt_styleSheet(style: w->style()))) {
387 //the stylesheet need to know the real parent
388 QTipLabel::instance->setProperty(name: "_q_stylesheet_parent", value: QVariant::fromValue(value: w));
389 //we force the style to be the QStyleSheetStyle, and force to clear the cache as well.
390 QTipLabel::instance->setStyleSheet(QLatin1String("/* */"));
391
392 // Set up for cleaning up this later...
393 QTipLabel::instance->styleSheetParent = w;
394 if (w) {
395 connect(sender: w, SIGNAL(destroyed()),
396 receiver: QTipLabel::instance, SLOT(styleSheetParentDestroyed()));
397 // QTBUG-64550: A font inherited by the style sheet might change the size,
398 // particular on Windows, where the tip is not parented on a window.
399 QTipLabel::instance->updateSize(pos);
400 }
401 }
402#endif //QT_NO_STYLE_STYLESHEET
403
404 QPoint p = pos;
405 const QScreen *screen = QGuiApplication::screens().value(i: getTipScreen(pos, w),
406 defaultValue: QGuiApplication::primaryScreen());
407 // a QScreen's handle *should* never be null, so this is a bit paranoid
408 if (const QPlatformScreen *platformScreen = screen ? screen->handle() : nullptr) {
409 QPlatformCursor *cursor = platformScreen->cursor();
410 // default implementation of QPlatformCursor::size() returns QSize(16, 16)
411 const QSize nativeSize = cursor ? cursor->size() : QSize(16, 16);
412 const QSize cursorSize = QHighDpi::fromNativePixels(value: nativeSize,
413 context: platformScreen);
414 QPoint offset(2, cursorSize.height());
415 // assuming an arrow shape, we can just move to the side for very large cursors
416 if (cursorSize.height() > 2 * this->height())
417 offset = QPoint(cursorSize.width() / 2, 0);
418
419 p += offset;
420
421 QRect screenRect = screen->geometry();
422 if (p.x() + this->width() > screenRect.x() + screenRect.width())
423 p.rx() -= 4 + this->width();
424 if (p.y() + this->height() > screenRect.y() + screenRect.height())
425 p.ry() -= 24 + this->height();
426 if (p.y() < screenRect.y())
427 p.setY(screenRect.y());
428 if (p.x() + this->width() > screenRect.x() + screenRect.width())
429 p.setX(screenRect.x() + screenRect.width() - this->width());
430 if (p.x() < screenRect.x())
431 p.setX(screenRect.x());
432 if (p.y() + this->height() > screenRect.y() + screenRect.height())
433 p.setY(screenRect.y() + screenRect.height() - this->height());
434 }
435 this->move(p);
436}
437
438bool QTipLabel::tipChanged(const QPoint &pos, const QString &text, QObject *o)
439{
440 if (QTipLabel::instance->text() != text)
441 return true;
442
443 if (o != widget)
444 return true;
445
446 if (!rect.isNull())
447 return !rect.contains(p: pos);
448 else
449 return false;
450}
451
452/*!
453 Shows \a text as a tool tip, with the global position \a pos as
454 the point of interest. The tool tip will be shown with a platform
455 specific offset from this point of interest.
456
457 If you specify a non-empty rect the tip will be hidden as soon
458 as you move your cursor out of this area.
459
460 The \a rect is in the coordinates of the widget you specify with
461 \a w. If the \a rect is not empty you must specify a widget.
462 Otherwise this argument can be \nullptr but it is used to
463 determine the appropriate screen on multi-head systems.
464
465 If \a text is empty the tool tip is hidden. If the text is the
466 same as the currently shown tooltip, the tip will \e not move.
467 You can force moving by first hiding the tip with an empty text,
468 and then showing the new tip at the new position.
469*/
470
471void QToolTip::showText(const QPoint &pos, const QString &text, QWidget *w, const QRect &rect)
472{
473 showText(pos, text, w, rect, msecShowTime: -1);
474}
475
476/*!
477 \since 5.2
478 \overload
479 This is similar to QToolTip::showText(\a pos, \a text, \a w, \a rect) but with an extra parameter \a msecDisplayTime
480 that specifies how long the tool tip will be displayed, in milliseconds.
481*/
482
483void QToolTip::showText(const QPoint &pos, const QString &text, QWidget *w, const QRect &rect, int msecDisplayTime)
484{
485 if (QTipLabel::instance && QTipLabel::instance->isVisible()){ // a tip does already exist
486 if (text.isEmpty()){ // empty text means hide current tip
487 QTipLabel::instance->hideTip();
488 return;
489 }
490 else if (!QTipLabel::instance->fadingOut){
491 // If the tip has changed, reuse the one
492 // that is showing (removes flickering)
493 QPoint localPos = pos;
494 if (w)
495 localPos = w->mapFromGlobal(pos);
496 if (QTipLabel::instance->tipChanged(pos: localPos, text, o: w)){
497 QTipLabel::instance->reuseTip(text, msecDisplayTime, pos);
498 QTipLabel::instance->setTipRect(w, r: rect);
499 QTipLabel::instance->placeTip(pos, w);
500 }
501 return;
502 }
503 }
504
505 if (!text.isEmpty()){ // no tip can be reused, create new tip:
506#ifdef Q_OS_WIN32
507 // On windows, we can't use the widget as parent otherwise the window will be
508 // raised when the tooltip will be shown
509QT_WARNING_PUSH
510QT_WARNING_DISABLE_DEPRECATED
511 new QTipLabel(text, pos, QApplication::desktop()->screen(QTipLabel::getTipScreen(pos, w)), msecDisplayTime);
512QT_WARNING_POP
513#else
514 new QTipLabel(text, pos, w, msecDisplayTime); // sets QTipLabel::instance to itself
515#endif
516 QTipLabel::instance->setTipRect(w, r: rect);
517 QTipLabel::instance->placeTip(pos, w);
518 QTipLabel::instance->setObjectName(QLatin1String("qtooltip_label"));
519
520
521#if QT_CONFIG(effects)
522 if (QApplication::isEffectEnabled(Qt::UI_FadeTooltip))
523 qFadeEffect(QTipLabel::instance);
524 else if (QApplication::isEffectEnabled(Qt::UI_AnimateTooltip))
525 qScrollEffect(QTipLabel::instance);
526 else
527 QTipLabel::instance->showNormal();
528#else
529 QTipLabel::instance->showNormal();
530#endif
531 }
532}
533
534/*!
535 \overload
536
537 This is analogous to calling QToolTip::showText(\a pos, \a text, \a w, QRect())
538*/
539
540void QToolTip::showText(const QPoint &pos, const QString &text, QWidget *w)
541{
542 QToolTip::showText(pos, text, w, rect: QRect());
543}
544
545
546/*!
547 \fn void QToolTip::hideText()
548 \since 4.2
549
550 Hides the tool tip. This is the same as calling showText() with an
551 empty string.
552
553 \sa showText()
554*/
555
556
557/*!
558 \since 4.4
559
560 Returns \c true if this tooltip is currently shown.
561
562 \sa showText()
563 */
564bool QToolTip::isVisible()
565{
566 return (QTipLabel::instance != nullptr && QTipLabel::instance->isVisible());
567}
568
569/*!
570 \since 4.4
571
572 Returns the tooltip text, if a tooltip is visible, or an
573 empty string if a tooltip is not visible.
574 */
575QString QToolTip::text()
576{
577 if (QTipLabel::instance)
578 return QTipLabel::instance->text();
579 return QString();
580}
581
582
583Q_GLOBAL_STATIC(QPalette, tooltip_palette)
584
585/*!
586 Returns the palette used to render tooltips.
587
588 \note Tool tips use the inactive color group of QPalette, because tool
589 tips are not active windows.
590*/
591QPalette QToolTip::palette()
592{
593 return *tooltip_palette();
594}
595
596/*!
597 \since 4.2
598
599 Returns the font used to render tooltips.
600*/
601QFont QToolTip::font()
602{
603 return QApplication::font(className: "QTipLabel");
604}
605
606/*!
607 \since 4.2
608
609 Sets the \a palette used to render tooltips.
610
611 \note Tool tips use the inactive color group of QPalette, because tool
612 tips are not active windows.
613*/
614void QToolTip::setPalette(const QPalette &palette)
615{
616 *tooltip_palette() = palette;
617 if (QTipLabel::instance)
618 QTipLabel::instance->setPalette(palette);
619}
620
621/*!
622 \since 4.2
623
624 Sets the \a font used to render tooltips.
625*/
626void QToolTip::setFont(const QFont &font)
627{
628 QApplication::setFont(font, className: "QTipLabel");
629}
630
631QT_END_NAMESPACE
632
633#include "qtooltip.moc"
634#endif // QT_NO_TOOLTIP
635

source code of qtbase/src/widgets/kernel/qtooltip.cpp