Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qtooltip.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5#include <QtWidgets/private/qtwidgetsglobal_p.h>
6
7#include <qapplication.h>
8#include <qevent.h>
9#include <qpointer.h>
10#include <qstyle.h>
11#include <qstyleoption.h>
12#include <qstylepainter.h>
13#if QT_CONFIG(effects)
14#include <private/qeffects_p.h>
15#endif
16#include <qtextdocument.h>
17#include <qdebug.h>
18#include <qpa/qplatformscreen.h>
19#include <qpa/qplatformcursor.h>
20#if QT_CONFIG(style_stylesheet)
21#include <private/qstylesheetstyle_p.h>
22#endif
23
24#include <qlabel.h>
25#include <QtWidgets/private/qlabel_p.h>
26#include <QtGui/private/qhighdpiscaling_p.h>
27#include <qtooltip.h>
28
29#include <QtCore/qbasictimer.h>
30
32
33using namespace Qt::StringLiterals;
34
35/*!
36 \class QToolTip
37
38 \brief The QToolTip class provides tool tips (balloon help) for any
39 widget.
40
41 \ingroup helpsystem
42 \inmodule QtWidgets
43
44 The tip is a short piece of text reminding the user of the
45 widget's function. It is drawn immediately below the given
46 position in a distinctive black-on-yellow color combination. The
47 tip can be any \l{QTextEdit}{rich text} formatted string.
48
49 Rich text displayed in a tool tip is implicitly word-wrapped unless
50 specified differently with \c{<p style='white-space:pre'>}.
51
52 UI elements that are created via \l{QAction} use the tooltip property
53 of the QAction, so for most interactive UI elements, setting that
54 property is the easiest way to provide tool tips.
55
56 \snippet tooltips/main.cpp action_tooltip
57
58 For any other widgets, the simplest and most common way to set
59 a widget's tool tip is by calling its QWidget::setToolTip() function.
60
61 \snippet tooltips/main.cpp static_tooltip
62
63 It is also possible to show different tool tips for different
64 regions of a widget, by using a QHelpEvent of type
65 QEvent::ToolTip. Intercept the help event in your widget's \l
66 {QWidget::}{event()} function and call QToolTip::showText() with
67 the text you want to display.
68
69 \snippet tooltips/main.cpp dynamic_tooltip
70
71 If you are calling QToolTip::hideText(), or QToolTip::showText()
72 with an empty string, as a result of a \l{QEvent::}{ToolTip}-event you
73 should also call \l{QEvent::}{ignore()} on the event, to signal
74 that you don't want to start any tooltip specific modes.
75
76 Note that, if you want to show tooltips in an item view, the
77 model/view architecture provides functionality to set an item's
78 tool tip; e.g., the QTableWidgetItem::setToolTip() function.
79 However, if you want to provide custom tool tips in an item view,
80 you must intercept the help event in the
81 QAbstractItemView::viewportEvent() function and handle it yourself.
82
83 The default tool tip color and font can be customized with
84 setPalette() and setFont(). When a tooltip is currently on
85 display, isVisible() returns \c true and text() the currently visible
86 text.
87
88 \note Tool tips use the inactive color group of QPalette, because tool
89 tips are not active windows.
90
91 \sa QWidget::toolTip, QAction::toolTip
92*/
93
94class QTipLabel : public QLabel
95{
97public:
101
102 void adjustTooltipScreen(const QPoint &pos);
103 void updateSize(const QPoint &pos);
104
105 bool eventFilter(QObject *, QEvent *) override;
106
108
110
111 void reuseTip(const QString &text, int msecDisplayTime, const QPoint &pos);
112 void hideTip();
114 void setTipRect(QWidget *w, const QRect &r);
115 void restartExpireTimer(int msecDisplayTime);
116 bool tipChanged(const QPoint &pos, const QString &text, QObject *o);
117 void placeTip(const QPoint &pos, QWidget *w);
118
119 static QScreen *getTipScreen(const QPoint &pos, QWidget *w);
120protected:
121 void timerEvent(QTimerEvent *e) override;
122 void paintEvent(QPaintEvent *e) override;
123 void mouseMoveEvent(QMouseEvent *e) override;
124 void resizeEvent(QResizeEvent *e) override;
125
126#if QT_CONFIG(style_stylesheet)
127public slots:
128 /** \internal
129 Cleanup the _q_stylesheet_parent property.
130 */
132 setProperty("_q_stylesheet_parent", QVariant());
133 styleSheetParent = nullptr;
134 }
135
136private:
138#endif
139
140private:
141 QWidget *widget;
142 QRect rect;
143};
144
145QTipLabel *QTipLabel::instance = nullptr;
146
147QTipLabel::QTipLabel(const QString &text, const QPoint &pos, QWidget *w, int msecDisplayTime)
148 : QLabel(w, Qt::ToolTip | Qt::BypassGraphicsProxyWidget)
149#if QT_CONFIG(style_stylesheet)
150 , styleSheetParent(nullptr)
151#endif
152 , widget(nullptr)
153{
154 delete instance;
155 instance = this;
156 setForegroundRole(QPalette::ToolTipText);
157 setBackgroundRole(QPalette::ToolTipBase);
158 setPalette(QToolTip::palette());
159 ensurePolished();
160 setMargin(1 + style()->pixelMetric(QStyle::PM_ToolTipLabelFrameWidth, nullptr, this));
161 setFrameStyle(QFrame::NoFrame);
162 setAlignment(Qt::AlignLeft);
163 setIndent(1);
164 qApp->installEventFilter(this);
165 setWindowOpacity(style()->styleHint(QStyle::SH_ToolTipLabel_Opacity, nullptr, this) / 255.0);
166 setMouseTracking(true);
167 fadingOut = false;
168 reuseTip(text, msecDisplayTime, pos);
169}
170
171void QTipLabel::restartExpireTimer(int msecDisplayTime)
172{
173 Q_D(const QLabel);
174 const qsizetype textLength = d->needTextControl() ? d->control->toPlainText().size() : text().size();
175 qsizetype time = 10000 + 40 * qMax(0, textLength - 100);
176 if (msecDisplayTime > 0)
177 time = msecDisplayTime;
178 expireTimer.start(time, this);
179 hideTimer.stop();
180}
181
182void QTipLabel::reuseTip(const QString &text, int msecDisplayTime, const QPoint &pos)
183{
184#if QT_CONFIG(style_stylesheet)
185 if (styleSheetParent){
186 disconnect(styleSheetParent, &QWidget::destroyed,
187 this, &QTipLabel::styleSheetParentDestroyed);
188 styleSheetParent = nullptr;
189 }
190#endif
191
192 setText(text);
193 updateSize(pos);
194 restartExpireTimer(msecDisplayTime);
195}
196
197void QTipLabel::updateSize(const QPoint &pos)
198{
199 d_func()->setScreenForPoint(pos);
200 // Ensure that we get correct sizeHints by placing this window on the right screen.
201 QFontMetrics fm(font());
202 QSize extra(1, 0);
203 // Make it look good with the default ToolTip font on Mac, which has a small descent.
204 if (fm.descent() == 2 && fm.ascent() >= 11)
205 ++extra.rheight();
206 setWordWrap(Qt::mightBeRichText(text()));
207 QSize sh = sizeHint();
208 const QScreen *screen = getTipScreen(pos, this);
209 if (!wordWrap() && sh.width() > screen->geometry().width()) {
210 setWordWrap(true);
211 sh = sizeHint();
212 }
213 resize(sh + extra);
214}
215
216void QTipLabel::paintEvent(QPaintEvent *ev)
217{
218 QStylePainter p(this);
219 QStyleOptionFrame opt;
220 opt.initFrom(this);
221 p.drawPrimitive(QStyle::PE_PanelTipLabel, opt);
222 p.end();
223
224 QLabel::paintEvent(ev);
225}
226
227void QTipLabel::resizeEvent(QResizeEvent *e)
228{
229 QStyleHintReturnMask frameMask;
230 QStyleOption option;
231 option.initFrom(this);
232 if (style()->styleHint(QStyle::SH_ToolTip_Mask, &option, this, &frameMask))
233 setMask(frameMask.region);
234
235 QLabel::resizeEvent(e);
236}
237
238void QTipLabel::mouseMoveEvent(QMouseEvent *e)
239{
240 if (!rect.isNull()) {
241 QPoint pos = e->globalPosition().toPoint();
242 if (widget)
243 pos = widget->mapFromGlobal(pos);
244 if (!rect.contains(pos))
245 hideTip();
246 }
247 QLabel::mouseMoveEvent(e);
248}
249
251{
252 instance = nullptr;
253}
254
256{
257 if (!hideTimer.isActive())
258 hideTimer.start(300, this);
259}
260
262{
263 close(); // to trigger QEvent::Close which stops the animation
264 deleteLater();
265}
266
267void QTipLabel::setTipRect(QWidget *w, const QRect &r)
268{
269 if (Q_UNLIKELY(!r.isNull() && !w)) {
270 qWarning("QToolTip::setTipRect: Cannot pass null widget if rect is set");
271 return;
272 }
273 widget = w;
274 rect = r;
275}
276
277void QTipLabel::timerEvent(QTimerEvent *e)
278{
279 if (e->timerId() == hideTimer.timerId()
280 || e->timerId() == expireTimer.timerId()){
281 hideTimer.stop();
282 expireTimer.stop();
284 }
285}
286
287bool QTipLabel::eventFilter(QObject *o, QEvent *e)
288{
289 switch (e->type()) {
290#ifdef Q_OS_MACOS
291 case QEvent::KeyPress:
292 case QEvent::KeyRelease: {
293 const int key = static_cast<QKeyEvent *>(e)->key();
294 // Anything except key modifiers or caps-lock, etc.
295 if (key < Qt::Key_Shift || key > Qt::Key_ScrollLock)
296 hideTipImmediately();
297 break;
298 }
299#endif
300 case QEvent::Leave:
301 hideTip();
302 break;
303
304
305#if defined (Q_OS_QNX) || defined (Q_OS_WASM) // On QNX the window activate and focus events are delayed and will appear
306 // after the window is shown.
307 case QEvent::WindowActivate:
308 case QEvent::FocusIn:
309 return false;
310 case QEvent::WindowDeactivate:
311 if (o != this)
312 return false;
313 hideTipImmediately();
314 break;
315 case QEvent::FocusOut:
316 if (reinterpret_cast<QWindow*>(o) != windowHandle())
317 return false;
318 hideTipImmediately();
319 break;
320#else
321 case QEvent::WindowActivate:
322 case QEvent::WindowDeactivate:
323 case QEvent::FocusIn:
324 case QEvent::FocusOut:
325#endif
326 case QEvent::MouseButtonPress:
327 case QEvent::MouseButtonRelease:
328 case QEvent::MouseButtonDblClick:
329 case QEvent::Wheel:
331 break;
332
333 case QEvent::MouseMove:
334 if (o == widget && !rect.isNull() && !rect.contains(static_cast<QMouseEvent*>(e)->position().toPoint()))
335 hideTip();
336 break;
337 default:
338 break;
339 }
340 return false;
341}
342
343QScreen *QTipLabel::getTipScreen(const QPoint &pos, QWidget *w)
344{
345 QScreen *guess = w ? w->screen() : QGuiApplication::primaryScreen();
346 QScreen *exact = guess->virtualSiblingAt(pos);
347 return exact ? exact : guess;
348}
349
350void QTipLabel::placeTip(const QPoint &pos, QWidget *w)
351{
352#if QT_CONFIG(style_stylesheet)
353 if (testAttribute(Qt::WA_StyleSheet) || (w && qt_styleSheet(w->style()))) {
354 //the stylesheet need to know the real parent
355 QTipLabel::instance->setProperty("_q_stylesheet_parent", QVariant::fromValue(w));
356 //we force the style to be the QStyleSheetStyle, and force to clear the cache as well.
357 QTipLabel::instance->setStyleSheet("/* */"_L1);
358
359 // Set up for cleaning up this later...
360 QTipLabel::instance->styleSheetParent = w;
361 if (w) {
362 connect(w, &QWidget::destroyed,
363 QTipLabel::instance, &QTipLabel::styleSheetParentDestroyed);
364 }
365 // QTBUG-64550: A font inherited by the style sheet might change the size,
366 // particular on Windows, where the tip is not parented on a window.
367 // The updatesSize() also makes sure that the content size be updated with
368 // correct content margin.
369 QTipLabel::instance->updateSize(pos);
370 }
371#endif //QT_CONFIG(style_stylesheet)
372
373 QPoint p = pos;
374 const QScreen *screen = getTipScreen(pos, w);
375 // a QScreen's handle *should* never be null, so this is a bit paranoid
376 if (const QPlatformScreen *platformScreen = screen ? screen->handle() : nullptr) {
377 QPlatformCursor *cursor = platformScreen->cursor();
378 // default implementation of QPlatformCursor::size() returns QSize(16, 16)
379 const QSize nativeSize = cursor ? cursor->size() : QSize(16, 16);
380 const QSize cursorSize = QHighDpi::fromNativePixels(nativeSize,
381 platformScreen);
382 QPoint offset(2, cursorSize.height());
383 // assuming an arrow shape, we can just move to the side for very large cursors
384 if (cursorSize.height() > 2 * this->height())
385 offset = QPoint(cursorSize.width() / 2, 0);
386
387 p += offset;
388
389 QRect screenRect = screen->geometry();
390 if (p.x() + this->width() > screenRect.x() + screenRect.width())
391 p.rx() -= 4 + this->width();
392 if (p.y() + this->height() > screenRect.y() + screenRect.height())
393 p.ry() -= 24 + this->height();
394 if (p.y() < screenRect.y())
395 p.setY(screenRect.y());
396 if (p.x() + this->width() > screenRect.x() + screenRect.width())
397 p.setX(screenRect.x() + screenRect.width() - this->width());
398 if (p.x() < screenRect.x())
399 p.setX(screenRect.x());
400 if (p.y() + this->height() > screenRect.y() + screenRect.height())
401 p.setY(screenRect.y() + screenRect.height() - this->height());
402 }
403 this->move(p);
404}
405
406bool QTipLabel::tipChanged(const QPoint &pos, const QString &text, QObject *o)
407{
408 if (QTipLabel::instance->text() != text)
409 return true;
410
411 if (o != widget)
412 return true;
413
414 if (!rect.isNull())
415 return !rect.contains(pos);
416 else
417 return false;
418}
419
420/*!
421 Shows \a text as a tool tip, with the global position \a pos as
422 the point of interest. The tool tip will be shown with a platform
423 specific offset from this point of interest.
424
425 If you specify a non-empty rect the tip will be hidden as soon
426 as you move your cursor out of this area.
427
428 The \a rect is in the coordinates of the widget you specify with
429 \a w. If the \a rect is not empty you must specify a widget.
430 Otherwise this argument can be \nullptr but it is used to
431 determine the appropriate screen on multi-head systems.
432
433 The \a msecDisplayTime parameter specifies for how long the tool tip
434 will be displayed, in milliseconds. With the default value of -1, the
435 time is based on the length of the text.
436
437 If \a text is empty the tool tip is hidden. If the text is the
438 same as the currently shown tooltip, the tip will \e not move.
439 You can force moving by first hiding the tip with an empty text,
440 and then showing the new tip at the new position.
441*/
442
443void QToolTip::showText(const QPoint &pos, const QString &text, QWidget *w, const QRect &rect, int msecDisplayTime)
444{
445 if (QTipLabel::instance && QTipLabel::instance->isVisible()) { // a tip does already exist
446 if (text.isEmpty()){ // empty text means hide current tip
447 QTipLabel::instance->hideTip();
448 return;
449 } else if (!QTipLabel::instance->fadingOut) {
450 // If the tip has changed, reuse the one
451 // that is showing (removes flickering)
452 QPoint localPos = pos;
453 if (w)
454 localPos = w->mapFromGlobal(pos);
455 if (QTipLabel::instance->tipChanged(localPos, text, w)){
456 QTipLabel::instance->reuseTip(text, msecDisplayTime, pos);
457 QTipLabel::instance->setTipRect(w, rect);
458 QTipLabel::instance->placeTip(pos, w);
459 }
460 return;
461 }
462 }
463
464 if (!text.isEmpty()) { // no tip can be reused, create new tip:
465 QWidget *tipLabelParent = [w]() -> QWidget* {
466#ifdef Q_OS_WIN32
467 // On windows, we can't use the widget as parent otherwise the window will be
468 // raised when the tooltip will be shown
469 Q_UNUSED(w);
470 return nullptr;
471#else
472 return w;
473#endif
474 }();
475 new QTipLabel(text, pos, tipLabelParent, msecDisplayTime); // sets QTipLabel::instance to itself
476 QWidgetPrivate::get(QTipLabel::instance)->setScreen(QTipLabel::getTipScreen(pos, w));
477 QTipLabel::instance->setTipRect(w, rect);
478 QTipLabel::instance->placeTip(pos, w);
479 QTipLabel::instance->setObjectName("qtooltip_label"_L1);
480
481#if QT_CONFIG(effects)
482 if (QApplication::isEffectEnabled(Qt::UI_FadeTooltip))
483 qFadeEffect(QTipLabel::instance);
484 else if (QApplication::isEffectEnabled(Qt::UI_AnimateTooltip))
485 qScrollEffect(QTipLabel::instance);
486 else
487 QTipLabel::instance->showNormal();
488#else
489 QTipLabel::instance->showNormal();
490#endif
491 }
492}
493
494/*!
495 \fn void QToolTip::hideText()
496 \since 4.2
497
498 Hides the tool tip. This is the same as calling showText() with an
499 empty string.
500
501 \sa showText()
502*/
503
504
505/*!
506 \since 4.4
507
508 Returns \c true if a tooltip is currently shown.
509
510 \sa showText()
511 */
512bool QToolTip::isVisible()
513{
514 return (QTipLabel::instance != nullptr && QTipLabel::instance->isVisible());
515}
516
517/*!
518 \since 4.4
519
520 Returns the tooltip text, if a tooltip is visible, or an
521 empty string if a tooltip is not visible.
522 */
523QString QToolTip::text()
524{
525 if (QTipLabel::instance)
526 return QTipLabel::instance->text();
527 return QString();
528}
529
530
531Q_GLOBAL_STATIC(QPalette, tooltip_palette)
532
533/*!
534 Returns the palette used to render tooltips.
535
536 \note Tool tips use the inactive color group of QPalette, because tool
537 tips are not active windows.
538*/
539QPalette QToolTip::palette()
540{
541 return *tooltip_palette();
542}
543
544/*!
545 \since 4.2
546
547 Returns the font used to render tooltips.
548*/
549QFont QToolTip::font()
550{
551 return QApplication::font("QTipLabel");
552}
553
554/*!
555 \since 4.2
556
557 Sets the \a palette used to render tooltips.
558
559 \note Tool tips use the inactive color group of QPalette, because tool
560 tips are not active windows.
561*/
562void QToolTip::setPalette(const QPalette &palette)
563{
564 *tooltip_palette() = palette;
565 if (QTipLabel::instance)
566 QTipLabel::instance->setPalette(palette);
567}
568
569/*!
570 \since 4.2
571
572 Sets the \a font used to render tooltips.
573*/
574void QToolTip::setFont(const QFont &font)
575{
576 QApplication::setFont(font, "QTipLabel");
577}
578
579QT_END_NAMESPACE
580
581#include "qtooltip.moc"
\inmodule QtCore\reentrant
Definition qpoint.h:29
void reuseTip(const QString &text, int msecDisplayTime, const QPoint &pos)
Definition qtooltip.cpp:182
void restartExpireTimer(int msecDisplayTime)
Definition qtooltip.cpp:171
void hideTip()
Definition qtooltip.cpp:255
void resizeEvent(QResizeEvent *e) override
This event handler can be reimplemented in a subclass to receive widget resize events which are passe...
Definition qtooltip.cpp:227
void mouseMoveEvent(QMouseEvent *e) override
\reimp
Definition qtooltip.cpp:238
void hideTipImmediately()
Definition qtooltip.cpp:261
bool tipChanged(const QPoint &pos, const QString &text, QObject *o)
Definition qtooltip.cpp:406
void setTipRect(QWidget *w, const QRect &r)
Definition qtooltip.cpp:267
bool fadingOut
Definition qtooltip.cpp:109
QBasicTimer expireTimer
Definition qtooltip.cpp:107
void paintEvent(QPaintEvent *e) override
\reimp
Definition qtooltip.cpp:216
QBasicTimer hideTimer
Definition qtooltip.cpp:107
void timerEvent(QTimerEvent *e) override
This event handler can be reimplemented in a subclass to receive timer events for the object.
Definition qtooltip.cpp:277
void placeTip(const QPoint &pos, QWidget *w)
Definition qtooltip.cpp:350
static QScreen * getTipScreen(const QPoint &pos, QWidget *w)
Definition qtooltip.cpp:343
static QTipLabel * instance
Definition qtooltip.cpp:100
bool eventFilter(QObject *, QEvent *) override
Filters events if this object has been installed as an event filter for the watched object.
Definition qtooltip.cpp:287
void adjustTooltipScreen(const QPoint &pos)
void updateSize(const QPoint &pos)
Definition qtooltip.cpp:197
#define qApp