source: trunk/src/gui/kernel/qtooltip.cpp@ 603

Last change on this file since 603 was 561, checked in by Dmitry A. Kuminov, 15 years ago

trunk: Merged in qt 4.6.1 sources.

File size: 17.4 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation ([email protected])
6**
7** This file is part of the QtGui module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
15**
16** GNU Lesser General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU Lesser
18** General Public License version 2.1 as published by the Free Software
19** Foundation and appearing in the file LICENSE.LGPL included in the
20** packaging of this file. Please review the following information to
21** ensure the GNU Lesser General Public License version 2.1 requirements
22** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23**
24** In addition, as a special exception, Nokia gives you certain additional
25** rights. These rights are described in the Nokia Qt LGPL Exception
26** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you have questions regarding the use of this file, please contact
37** Nokia at [email protected].
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
67QT_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
119class QTipLabel : public QLabel
120{
121 Q_OBJECT
122public:
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);
142protected:
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
149public slots:
150 /** \internal
151 Cleanup the _q_stylesheet_parent propery.
152 */
153 void styleSheetParentDestroyed() {
154 setProperty("_q_stylesheet_parent", QVariant());
155 styleSheetParent = 0;
156 }
157
158private:
159 QWidget *styleSheetParent;
160#endif
161
162private:
163 QWidget *widget;
164 QRect rect;
165};
166
167QTipLabel *QTipLabel::instance = 0;
168
169QTipLabel::QTipLabel(const QString &text, QWidget *w)
170#ifndef QT_NO_STYLE_STYLESHEET
171 : QLabel(w, Qt::ToolTip), styleSheetParent(0), widget(0)
172#else
173 : QLabel(w, Qt::ToolTip), 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) / 255.0);
188 setMouseTracking(true);
189 fadingOut = false;
190 reuseTip(text);
191}
192
193void QTipLabel::restartExpireTimer()
194{
195 int time = 10000 + 40 * qMax(0, text().length()-100);
196 expireTimer.start(time, this);
197 hideTimer.stop();
198}
199
200void 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
221void 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
232void 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
243void 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
255QTipLabel::~QTipLabel()
256{
257 instance = 0;
258}
259
260void QTipLabel::hideTip()
261{
262 if (!hideTimer.isActive())
263 hideTimer.start(300, this);
264}
265
266void QTipLabel::hideTipImmediately()
267{
268 close(); // to trigger QEvent::Close which stops the animation
269 deleteLater();
270}
271
272void 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
282void 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
305bool 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
343int 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
351void 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", qVariantFromValue(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 QRect screen = QApplication::desktop()->availableGeometry(getTipScreen(pos, w));
372#else
373 QRect screen = QApplication::desktop()->screenGeometry(getTipScreen(pos, w));
374#endif
375
376 QPoint p = pos;
377 p += QPoint(2,
378#ifdef Q_WS_WIN
379 21
380#else
381 16
382#endif
383 );
384 if (p.x() + this->width() > screen.x() + screen.width())
385 p.rx() -= 4 + this->width();
386 if (p.y() + this->height() > screen.y() + screen.height())
387 p.ry() -= 24 + this->height();
388 if (p.y() < screen.y())
389 p.setY(screen.y());
390 if (p.x() + this->width() > screen.x() + screen.width())
391 p.setX(screen.x() + screen.width() - this->width());
392 if (p.x() < screen.x())
393 p.setX(screen.x());
394 if (p.y() + this->height() > screen.y() + screen.height())
395 p.setY(screen.y() + screen.height() - this->height());
396 this->move(p);
397}
398
399bool QTipLabel::tipChanged(const QPoint &pos, const QString &text, QObject *o)
400{
401 if (QTipLabel::instance->text() != text)
402 return true;
403
404 if (o != widget)
405 return true;
406
407 if (!rect.isNull())
408 return !rect.contains(pos);
409 else
410 return false;
411}
412
413/*!
414 Shows \a text as a tool tip, with the global position \a pos as
415 the point of interest. The tool tip will be shown with a platform
416 specific offset from this point of interest.
417
418 If you specify a non-empty rect the tip will be hidden as soon
419 as you move your cursor out of this area.
420
421 The \a rect is in the coordinates of the widget you specify with
422 \a w. If the \a rect is not empty you must specify a widget.
423 Otherwise this argument can be 0 but it is used to determine the
424 appropriate screen on multi-head systems.
425
426 If \a text is empty the tool tip is hidden. If the text is the
427 same as the currently shown tooltip, the tip will \e not move.
428 You can force moving by first hiding the tip with an empty text,
429 and then showing the new tip at the new position.
430*/
431
432void QToolTip::showText(const QPoint &pos, const QString &text, QWidget *w, const QRect &rect)
433{
434 if (QTipLabel::instance && QTipLabel::instance->isVisible()){ // a tip does already exist
435 if (text.isEmpty()){ // empty text means hide current tip
436 QTipLabel::instance->hideTip();
437 return;
438 }
439 else if (!QTipLabel::instance->fadingOut){
440 // If the tip has changed, reuse the one
441 // that is showing (removes flickering)
442 QPoint localPos = pos;
443 if (w)
444 localPos = w->mapFromGlobal(pos);
445 if (QTipLabel::instance->tipChanged(localPos, text, w)){
446 QTipLabel::instance->reuseTip(text);
447 QTipLabel::instance->setTipRect(w, rect);
448 QTipLabel::instance->placeTip(pos, w);
449 }
450 return;
451 }
452 }
453
454 if (!text.isEmpty()){ // no tip can be reused, create new tip:
455#ifndef Q_WS_WIN
456 new QTipLabel(text, w); // sets QTipLabel::instance to itself
457#else
458 // On windows, we can't use the widget as parent otherwise the window will be
459 // raised when the tooltip will be shown
460 new QTipLabel(text, QApplication::desktop()->screen(QTipLabel::getTipScreen(pos, w)));
461#endif
462 QTipLabel::instance->setTipRect(w, rect);
463 QTipLabel::instance->placeTip(pos, w);
464 QTipLabel::instance->setObjectName(QLatin1String("qtooltip_label"));
465
466
467#if !defined(QT_NO_EFFECTS) && !defined(Q_WS_MAC)
468 if (QApplication::isEffectEnabled(Qt::UI_FadeTooltip))
469 qFadeEffect(QTipLabel::instance);
470 else if (QApplication::isEffectEnabled(Qt::UI_AnimateTooltip))
471 qScrollEffect(QTipLabel::instance);
472 else
473 QTipLabel::instance->show();
474#else
475 QTipLabel::instance->show();
476#endif
477 }
478}
479
480/*!
481 \overload
482
483 This is analogous to calling QToolTip::showText(\a pos, \a text, \a w, QRect())
484*/
485
486void QToolTip::showText(const QPoint &pos, const QString &text, QWidget *w)
487{
488 QToolTip::showText(pos, text, w, QRect());
489}
490
491
492/*!
493 \fn void QToolTip::hideText()
494 \since 4.2
495
496 Hides the tool tip. This is the same as calling showText() with an
497 empty string.
498
499 \sa showText()
500*/
501
502
503/*!
504 \since 4.4
505
506 Returns true if this tooltip is currently shown.
507
508 \sa showText()
509 */
510bool QToolTip::isVisible()
511{
512 return (QTipLabel::instance != 0 && QTipLabel::instance->isVisible());
513}
514
515/*!
516 \since 4.4
517
518 Returns the tooltip text, if a tooltip is visible, or an
519 empty string if a tooltip is not visible.
520 */
521QString QToolTip::text()
522{
523 if (QTipLabel::instance)
524 return QTipLabel::instance->text();
525 return QString();
526}
527
528
529Q_GLOBAL_STATIC(QPalette, tooltip_palette)
530
531/*!
532 Returns the palette used to render tooltips.
533
534 \note Tool tips use the inactive color group of QPalette, because tool
535 tips are not active windows.
536*/
537QPalette QToolTip::palette()
538{
539 return *tooltip_palette();
540}
541
542/*!
543 \since 4.2
544
545 Returns the font used to render tooltips.
546*/
547QFont QToolTip::font()
548{
549 return QApplication::font("QTipLabel");
550}
551
552/*!
553 \since 4.2
554
555 Sets the \a palette used to render tooltips.
556
557 \note Tool tips use the inactive color group of QPalette, because tool
558 tips are not active windows.
559*/
560void QToolTip::setPalette(const QPalette &palette)
561{
562 *tooltip_palette() = palette;
563 if (QTipLabel::instance)
564 QTipLabel::instance->setPalette(palette);
565}
566
567/*!
568 \since 4.2
569
570 Sets the \a font used to render tooltips.
571*/
572void QToolTip::setFont(const QFont &font)
573{
574 QApplication::setFont(font, "QTipLabel");
575}
576
577
578/*!
579 \fn void QToolTip::add(QWidget *widget, const QString &text)
580
581 Use QWidget::setToolTip() instead.
582
583 \oldcode
584 tip->add(widget, text);
585 \newcode
586 widget->setToolTip(text);
587 \endcode
588*/
589
590/*!
591 \fn void QToolTip::add(QWidget *widget, const QRect &rect, const QString &text)
592
593 Intercept the QEvent::ToolTip events in your widget's
594 QWidget::event() function and call QToolTip::showText() with the
595 text you want to display. The \l{widgets/tooltips}{Tooltips}
596 example illustrates this technique.
597*/
598
599/*!
600 \fn void QToolTip::remove(QWidget *widget)
601
602 Use QWidget::setToolTip() instead.
603
604 \oldcode
605 tip->remove(widget);
606 \newcode
607 widget->setToolTip("");
608 \endcode
609*/
610
611QT_END_NAMESPACE
612
613#include "qtooltip.moc"
614#endif // QT_NO_TOOLTIP
Note: See TracBrowser for help on using the repository browser.