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