1// Copyright (C) 2017 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
4#include "qquicktooltip_p.h"
5#include "qquickpopup_p_p.h"
6#include "qquickpopupitem_p_p.h"
7#include "qquickcontrol_p_p.h"
8
9#include <QtCore/qbasictimer.h>
10#include <QtQml/qqmlinfo.h>
11#include <QtQml/qqmlengine.h>
12#include <QtQml/qqmlcontext.h>
13#include <QtQml/qqmlcomponent.h>
14#include <QtQuick/qquickwindow.h>
15
16QT_BEGIN_NAMESPACE
17
18/*!
19 \qmltype ToolTip
20 \inherits Popup
21//! \instantiates QQuickToolTip
22 \inqmlmodule QtQuick.Controls
23 \since 5.7
24 \ingroup qtquickcontrols-popups
25 \brief Provides tool tips for any control.
26
27 A tool tip is a short piece of text that informs the user of a control's
28 function. It is typically placed above or below the parent control. The
29 tip text can be any \l{Rich Text Processing}{rich text} formatted string.
30
31 \image qtquickcontrols-tooltip.png
32
33 \section2 Attached Tool Tips
34
35 The most straight-forward way to setup tool tips for controls is to
36 specify \l text and \l {visible}{visibility} via attached properties.
37 The following example illustrates this approach:
38
39 \snippet qtquickcontrols-tooltip.qml 1
40
41 Under normal circumstances, there is only one tool tip visible at a time.
42 In order to save resources, all items that use the ToolTip attached property
43 share the same visual tool tip label instance. Even though the visuals are
44 shared, \c text, \c timeout and \c delay are stored individually for each item
45 that uses the respective attached property. However, multiple items cannot
46 make the shared tool tip visible at the same time. The shared tool tip is only
47 shown for the last item that made it visible. The position of the shared tool
48 tip is determined by the framework.
49
50 \include qquicktooltip.qdocinc customize-note
51
52 \section2 Delay and Timeout
53
54 Tool tips are typically transient in a sense that they are shown as a
55 result of a certain external event or user interaction, and they usually
56 hide after a certain timeout. It is possible to control the delay when
57 a tool tip is shown, and the timeout when it is hidden. This makes it
58 possible to implement varying strategies for showing and hiding tool tips.
59
60 For example, on touch screens, it is a common pattern to show a tool tip
61 as a result of pressing and holding down a button. The following example
62 demonstrates how to delay showing a tool tip until the press-and-hold
63 interval is reached. In this example, the tool tip hides as soon as the
64 button is released.
65
66 \snippet qtquickcontrols-tooltip-pressandhold.qml 1
67
68 With pointer devices, however, it might be desired to show a tool tip as
69 a result of hovering a button for a while. The following example presents
70 how to show a tool tip after hovering a button for a second, and hide it
71 after a timeout of five seconds.
72
73 \snippet qtquickcontrols-tooltip-hover.qml 1
74
75 \section2 Custom Tool Tips
76
77 Should one need more fine-grained control over the tool tip position, or
78 multiple simultaneous tool tip instances are needed, it is also possible
79 to create local tool tip instances. This way, it is possible to
80 \l {Customizing ToolTip}{customize} the tool tip, and the whole \l Popup
81 API is available. The following example presents a tool tip that presents
82 the value of a slider when the handle is dragged.
83
84 \image qtquickcontrols-tooltip-slider.png
85
86 \snippet qtquickcontrols-tooltip-slider.qml 1
87
88 \sa {Customizing ToolTip}, {Popup Controls},
89 {QtQuick.Controls::Popup::closePolicy}{closePolicy}
90*/
91
92class QQuickToolTipPrivate : public QQuickPopupPrivate
93{
94 Q_DECLARE_PUBLIC(QQuickToolTip)
95
96public:
97 void startDelay();
98 void stopDelay();
99
100 void startTimeout();
101 void stopTimeout();
102
103 void opened() override;
104
105 QPalette defaultPalette() const override { return QQuickTheme::palette(scope: QQuickTheme::ToolTip); }
106
107 int delay = 0;
108 int timeout = -1;
109 QString text;
110 QBasicTimer delayTimer;
111 QBasicTimer timeoutTimer;
112};
113
114void QQuickToolTipPrivate::startDelay()
115{
116 Q_Q(QQuickToolTip);
117 if (delay > 0)
118 delayTimer.start(msec: delay, obj: q);
119}
120
121void QQuickToolTipPrivate::stopDelay()
122{
123 delayTimer.stop();
124}
125
126void QQuickToolTipPrivate::startTimeout()
127{
128 Q_Q(QQuickToolTip);
129 if (timeout > 0)
130 timeoutTimer.start(msec: timeout, obj: q);
131}
132
133void QQuickToolTipPrivate::stopTimeout()
134{
135 timeoutTimer.stop();
136}
137
138void QQuickToolTipPrivate::opened()
139{
140 QQuickPopupPrivate::opened();
141 startTimeout();
142}
143
144QQuickToolTip::QQuickToolTip(QQuickItem *parent)
145 : QQuickPopup(*(new QQuickToolTipPrivate), parent)
146{
147 Q_D(QQuickToolTip);
148 d->allowVerticalFlip = true;
149 d->allowHorizontalFlip = true;
150 d->popupItem->setHoverEnabled(false); // QTBUG-63644
151}
152
153/*!
154 \qmlproperty string QtQuick.Controls::ToolTip::text
155
156 This property holds the text shown on the tool tip.
157*/
158QString QQuickToolTip::text() const
159{
160 Q_D(const QQuickToolTip);
161 return d->text;
162}
163
164void QQuickToolTip::setText(const QString &text)
165{
166 Q_D(QQuickToolTip);
167 if (d->text == text)
168 return;
169
170 d->text = text;
171 maybeSetAccessibleName(name: text);
172 emit textChanged();
173}
174
175/*!
176 \qmlproperty int QtQuick.Controls::ToolTip::delay
177
178 This property holds the delay (milliseconds) after which the tool tip is
179 shown. A tooltip with a negative delay is shown immediately. The default
180 value is \c 0.
181
182 \sa {Delay and Timeout}
183*/
184int QQuickToolTip::delay() const
185{
186 Q_D(const QQuickToolTip);
187 return d->delay;
188}
189
190void QQuickToolTip::setDelay(int delay)
191{
192 Q_D(QQuickToolTip);
193 if (d->delay == delay)
194 return;
195
196 d->delay = delay;
197 emit delayChanged();
198}
199
200/*!
201 \qmlproperty int QtQuick.Controls::ToolTip::timeout
202
203 This property holds the timeout (milliseconds) after which the tool tip is
204 hidden. A tooltip with a negative timeout does not hide automatically. The
205 default value is \c -1.
206
207 \sa {Delay and Timeout}
208*/
209int QQuickToolTip::timeout() const
210{
211 Q_D(const QQuickToolTip);
212 return d->timeout;
213}
214
215void QQuickToolTip::setTimeout(int timeout)
216{
217 Q_D(QQuickToolTip);
218 if (d->timeout == timeout)
219 return;
220
221 d->timeout = timeout;
222
223 if (timeout <= 0)
224 d->stopTimeout();
225 else if (isOpened())
226 d->startTimeout();
227
228 emit timeoutChanged();
229}
230
231void QQuickToolTip::setVisible(bool visible)
232{
233 Q_D(QQuickToolTip);
234 if (visible) {
235 if (!d->visible) {
236 // We are being made visible, and we weren't before.
237 if (d->delay > 0) {
238 d->startDelay();
239 return;
240 }
241 }
242 } else {
243 d->stopDelay();
244 }
245 QQuickPopup::setVisible(visible);
246}
247
248QQuickToolTipAttached *QQuickToolTip::qmlAttachedProperties(QObject *object)
249{
250 QQuickItem *item = qobject_cast<QQuickItem *>(o: object);
251 if (!item)
252 qmlWarning(me: object) << "ToolTip must be attached to an Item";
253
254 return new QQuickToolTipAttached(object);
255}
256
257/*!
258 \since QtQuick.Controls 2.5 (Qt 5.12)
259 \qmlmethod void QtQuick.Controls::ToolTip::show(string text, int timeout)
260
261 This method shows the \a text as a tooltip, which times out in
262 \a timeout (milliseconds).
263*/
264void QQuickToolTip::show(const QString &text, int ms)
265{
266 if (ms >= 0)
267 setTimeout(ms);
268 setText(text);
269 open();
270}
271
272/*!
273 \since QtQuick.Controls 2.5 (Qt 5.12)
274 \qmlmethod void QtQuick.Controls::ToolTip::hide()
275
276 This method hides the tooltip.
277*/
278void QQuickToolTip::hide()
279{
280 close();
281}
282
283QFont QQuickToolTip::defaultFont() const
284{
285 return QQuickTheme::font(scope: QQuickTheme::ToolTip);
286}
287
288void QQuickToolTip::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &data)
289{
290 Q_D(QQuickToolTip);
291 QQuickPopup::itemChange(change, data);
292 if (change == QQuickItem::ItemVisibleHasChanged) {
293 if (!data.boolValue)
294 d->stopTimeout();
295
296 QQuickToolTipAttached *attached = qobject_cast<QQuickToolTipAttached *>(object: qmlAttachedPropertiesObject<QQuickToolTip>(obj: d->parentItem, create: false));
297 if (attached)
298 emit attached->visibleChanged();
299 }
300}
301
302void QQuickToolTip::timerEvent(QTimerEvent *event)
303{
304 Q_D(QQuickToolTip);
305 if (event->timerId() == d->timeoutTimer.timerId()) {
306 d->stopTimeout();
307 QQuickPopup::setVisible(false);
308 return;
309 }
310 if (event->timerId() == d->delayTimer.timerId()) {
311 d->stopDelay();
312 QQuickPopup::setVisible(true);
313 return;
314 }
315 QQuickPopup::timerEvent(event);
316}
317
318#if QT_CONFIG(accessibility)
319QAccessible::Role QQuickToolTip::accessibleRole() const
320{
321 return QAccessible::ToolTip;
322}
323
324void QQuickToolTip::accessibilityActiveChanged(bool active)
325{
326 Q_D(QQuickToolTip);
327 QQuickPopup::accessibilityActiveChanged(active);
328
329 if (active)
330 maybeSetAccessibleName(name: d->text);
331}
332#endif
333
334class QQuickToolTipAttachedPrivate : public QObjectPrivate
335{
336 Q_DECLARE_PUBLIC(QQuickToolTipAttached)
337
338public:
339 QQuickToolTip *instance(bool create) const;
340
341 int delay = 0;
342 int timeout = -1;
343 QString text;
344};
345
346QQuickToolTip *QQuickToolTipAttachedPrivate::instance(bool create) const
347{
348 QQmlEngine *engine = qmlEngine(parent);
349 if (!engine)
350 return nullptr;
351
352 static const char *name = "_q_QQuickToolTip";
353
354 QQuickToolTip *tip = engine->property(name).value<QQuickToolTip *>();
355 if (!tip && create) {
356 // TODO: a cleaner way to create the instance? QQml(Meta)Type?
357 QQmlComponent component(engine);
358 component.setData("import QtQuick.Controls; ToolTip { }", baseUrl: QUrl());
359
360 QObject *object = component.create();
361 if (object)
362 object->setParent(engine);
363
364 tip = qobject_cast<QQuickToolTip *>(object);
365 if (!tip)
366 delete object;
367 else
368 engine->setProperty(name, value: QVariant::fromValue(value: object));
369 }
370 return tip;
371}
372
373QQuickToolTipAttached::QQuickToolTipAttached(QObject *parent)
374 : QObject(*(new QQuickToolTipAttachedPrivate), parent)
375{
376}
377
378/*!
379 \qmlattachedproperty string QtQuick.Controls::ToolTip::text
380
381 This attached property holds the text of the shared tool tip.
382 The property can be attached to any item.
383
384 \sa {Attached Tool Tips}
385*/
386QString QQuickToolTipAttached::text() const
387{
388 Q_D(const QQuickToolTipAttached);
389 return d->text;
390}
391
392void QQuickToolTipAttached::setText(const QString &text)
393{
394 Q_D(QQuickToolTipAttached);
395 if (d->text == text)
396 return;
397
398 d->text = text;
399 emit textChanged();
400
401 if (isVisible())
402 d->instance(create: true)->setText(text);
403}
404
405/*!
406 \qmlattachedproperty int QtQuick.Controls::ToolTip::delay
407
408 This attached property holds the delay (milliseconds) of the shared tool tip.
409 The property can be attached to any item.
410
411 \sa {Attached Tool Tips}, {Delay and Timeout}
412*/
413int QQuickToolTipAttached::delay() const
414{
415 Q_D(const QQuickToolTipAttached);
416 return d->delay;
417}
418
419void QQuickToolTipAttached::setDelay(int delay)
420{
421 Q_D(QQuickToolTipAttached);
422 if (d->delay == delay)
423 return;
424
425 d->delay = delay;
426 emit delayChanged();
427
428 if (isVisible())
429 d->instance(create: true)->setDelay(delay);
430}
431
432/*!
433 \qmlattachedproperty int QtQuick.Controls::ToolTip::timeout
434
435 This attached property holds the timeout (milliseconds) of the shared tool tip.
436 The property can be attached to any item.
437
438 \sa {Attached Tool Tips}, {Delay and Timeout}
439*/
440int QQuickToolTipAttached::timeout() const
441{
442 Q_D(const QQuickToolTipAttached);
443 return d->timeout;
444}
445
446void QQuickToolTipAttached::setTimeout(int timeout)
447{
448 Q_D(QQuickToolTipAttached);
449 if (d->timeout == timeout)
450 return;
451
452 d->timeout = timeout;
453 emit timeoutChanged();
454
455 if (isVisible())
456 d->instance(create: true)->setTimeout(timeout);
457}
458
459/*!
460 \qmlattachedproperty bool QtQuick.Controls::ToolTip::visible
461
462 This attached property holds whether the shared tool tip is visible.
463 The property can be attached to any item.
464
465 \sa {Attached Tool Tips}
466*/
467bool QQuickToolTipAttached::isVisible() const
468{
469 Q_D(const QQuickToolTipAttached);
470 QQuickToolTip *tip = d->instance(create: false);
471 if (!tip)
472 return false;
473
474 return tip->isVisible() && tip->parentItem() == parent();
475}
476
477void QQuickToolTipAttached::setVisible(bool visible)
478{
479 Q_D(QQuickToolTipAttached);
480 if (visible)
481 show(text: d->text);
482 else
483 hide();
484}
485
486/*!
487 \qmlattachedproperty ToolTip QtQuick.Controls::ToolTip::toolTip
488
489 This attached property holds the shared tool tip instance. The property
490 can be attached to any item.
491
492 \sa {Attached Tool Tips}
493*/
494QQuickToolTip *QQuickToolTipAttached::toolTip() const
495{
496 Q_D(const QQuickToolTipAttached);
497 return d->instance(create: true);
498}
499
500/*!
501 \qmlattachedmethod void QtQuick.Controls::ToolTip::show(string text, int timeout = -1)
502
503 This attached method shows the shared tooltip with \a text and \a timeout (milliseconds).
504 The method can be attached to any item.
505
506 \sa {Attached Tool Tips}
507*/
508void QQuickToolTipAttached::show(const QString &text, int ms)
509{
510 Q_D(QQuickToolTipAttached);
511 QQuickToolTip *tip = d->instance(create: true);
512 if (!tip)
513 return;
514
515 tip->resetWidth();
516 tip->resetHeight();
517 tip->setParentItem(qobject_cast<QQuickItem *>(o: parent()));
518 tip->setDelay(d->delay);
519 tip->setTimeout(ms >= 0 ? ms : d->timeout);
520 tip->show(text);
521}
522
523/*!
524 \qmlattachedmethod void QtQuick.Controls::ToolTip::hide()
525
526 This attached method hides the shared tooltip. The method can be attached to any item.
527
528 \sa {Attached Tool Tips}
529*/
530void QQuickToolTipAttached::hide()
531{
532 Q_D(QQuickToolTipAttached);
533 QQuickToolTip *tip = d->instance(create: false);
534 if (!tip)
535 return;
536 // check the parent item to prevent unexpectedly closing tooltip by new created invisible tooltip
537 if (parent() == tip->parentItem())
538 tip->close();
539}
540
541QT_END_NAMESPACE
542
543#include "moc_qquicktooltip_p.cpp"
544

source code of qtdeclarative/src/quicktemplates/qquicktooltip.cpp