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