src/gui/kernel/qtooltip.cpp
changeset 0 1918ee327afb
child 4 3b1da2848fc7
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/gui/kernel/qtooltip.cpp	Mon Jan 11 14:00:40 2010 +0000
@@ -0,0 +1,614 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file.  Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights.  These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#ifdef Q_WS_MAC
+# include <private/qcore_mac_p.h>
+#endif
+
+#include <qapplication.h>
+#include <qdesktopwidget.h>
+#include <qevent.h>
+#include <qhash.h>
+#include <qlabel.h>
+#include <qpointer.h>
+#include <qstyle.h>
+#include <qstyleoption.h>
+#include <qstylepainter.h>
+#include <qtimer.h>
+#include <qtooltip.h>
+#include <private/qeffects_p.h>
+#include <qtextdocument.h>
+#include <qdebug.h>
+#include <private/qstylesheetstyle_p.h>
+#ifndef QT_NO_TOOLTIP
+
+#ifdef Q_WS_MAC
+# include <private/qcore_mac_p.h>
+#include <private/qt_cocoa_helpers_mac_p.h>
+#endif
+
+QT_BEGIN_NAMESPACE
+
+/*!
+    \class QToolTip
+
+    \brief The QToolTip class provides tool tips (balloon help) for any
+    widget.
+
+    \ingroup helpsystem
+
+
+    The tip is a short piece of text reminding the user of the
+    widget's function. It is drawn immediately below the given
+    position in a distinctive black-on-yellow color combination. The
+    tip can be any \l{QTextEdit}{rich text} formatted string.
+
+    Rich text displayed in a tool tip is implicitly word-wrapped unless
+    specified differently with \c{<p style='white-space:pre'>}.
+
+    The simplest and most common way to set a widget's tool tip is by
+    calling its QWidget::setToolTip() function.
+
+    It is also possible to show different tool tips for different
+    regions of a widget, by using a QHelpEvent of type
+    QEvent::ToolTip. Intercept the help event in your widget's \l
+    {QWidget::}{event()} function and call QToolTip::showText() with
+    the text you want to display. The \l{widgets/tooltips}{Tooltips}
+    example illustrates this technique.
+
+    If you are calling QToolTip::hideText(), or QToolTip::showText()
+    with an empty string, as a result of a \l{QEvent::}{ToolTip}-event you
+    should also call \l{QEvent::}{ignore()} on the event, to signal
+    that you don't want to start any tooltip specific modes.
+
+    Note that, if you want to show tooltips in an item view, the
+    model/view architecture provides functionality to set an item's
+    tool tip; e.g., the QTableWidgetItem::setToolTip() function.
+    However, if you want to provide custom tool tips in an item view,
+    you must intercept the help event in the
+    QAbstractItemView::viewportEvent() function and handle it yourself.
+
+    The default tool tip color and font can be customized with
+    setPalette() and setFont(). When a tooltip is currently on
+    display, isVisible() returns true and text() the currently visible
+    text.
+
+    \note Tool tips use the inactive color group of QPalette, because tool
+    tips are not active windows.
+
+    \sa QWidget::toolTip, QAction::toolTip, {Tool Tips Example}
+*/
+
+class QTipLabel : public QLabel
+{
+    Q_OBJECT
+public:
+    QTipLabel(const QString &text, QWidget *w);
+    ~QTipLabel();
+    static QTipLabel *instance;
+
+    bool eventFilter(QObject *, QEvent *);
+
+    QBasicTimer hideTimer, expireTimer;
+
+    bool fadingOut;
+
+    void reuseTip(const QString &text);
+    void hideTip();
+    void hideTipImmediately();
+    void setTipRect(QWidget *w, const QRect &r);
+    void restartExpireTimer();
+    bool tipChanged(const QPoint &pos, const QString &text, QObject *o);
+    void placeTip(const QPoint &pos, QWidget *w);
+
+    static int getTipScreen(const QPoint &pos, QWidget *w);
+protected:
+    void timerEvent(QTimerEvent *e);
+    void paintEvent(QPaintEvent *e);
+    void mouseMoveEvent(QMouseEvent *e);
+    void resizeEvent(QResizeEvent *e);
+
+#ifndef QT_NO_STYLE_STYLESHEET
+public slots:
+    /** \internal
+      Cleanup the _q_stylesheet_parent propery.
+     */
+    void styleSheetParentDestroyed() {
+        setProperty("_q_stylesheet_parent", QVariant());
+        styleSheetParent = 0;
+    }
+
+private:
+    QWidget *styleSheetParent;
+#endif
+
+private:
+    QWidget *widget;
+    QRect rect;
+};
+
+QTipLabel *QTipLabel::instance = 0;
+
+QTipLabel::QTipLabel(const QString &text, QWidget *w)
+#ifndef QT_NO_STYLE_STYLESHEET
+    : QLabel(w, Qt::ToolTip), styleSheetParent(0), widget(0)
+#else
+    : QLabel(w, Qt::ToolTip), widget(0)
+#endif
+{
+    delete instance;
+    instance = this;
+    setForegroundRole(QPalette::ToolTipText);
+    setBackgroundRole(QPalette::ToolTipBase);
+    setPalette(QToolTip::palette());
+    ensurePolished();
+    setMargin(1 + style()->pixelMetric(QStyle::PM_ToolTipLabelFrameWidth, 0, this));
+    setFrameStyle(QFrame::NoFrame);
+    setAlignment(Qt::AlignLeft);
+    setIndent(1);
+    qApp->installEventFilter(this);
+    setWindowOpacity(style()->styleHint(QStyle::SH_ToolTipLabel_Opacity, 0, this) / 255.0);
+    setMouseTracking(true);
+    fadingOut = false;
+    reuseTip(text);
+}
+
+void QTipLabel::restartExpireTimer()
+{
+    int time = 10000 + 40 * qMax(0, text().length()-100);
+    expireTimer.start(time, this);
+    hideTimer.stop();
+}
+
+void QTipLabel::reuseTip(const QString &text)
+{
+#ifndef QT_NO_STYLE_STYLESHEET
+    if (styleSheetParent){
+        disconnect(styleSheetParent, SIGNAL(destroyed()),
+                   QTipLabel::instance, SLOT(styleSheetParentDestroyed()));
+        styleSheetParent = 0;
+    }
+#endif
+
+    setWordWrap(Qt::mightBeRichText(text));
+    setText(text);
+    QFontMetrics fm(font());
+    QSize extra(1, 0);
+    // Make it look good with the default ToolTip font on Mac, which has a small descent.
+    if (fm.descent() == 2 && fm.ascent() >= 11)
+        ++extra.rheight();
+    resize(sizeHint() + extra);
+    restartExpireTimer();
+}
+
+void QTipLabel::paintEvent(QPaintEvent *ev)
+{
+    QStylePainter p(this);
+    QStyleOptionFrame opt;
+    opt.init(this);
+    p.drawPrimitive(QStyle::PE_PanelTipLabel, opt);
+    p.end();
+
+    QLabel::paintEvent(ev);
+}
+
+void QTipLabel::resizeEvent(QResizeEvent *e)
+{
+    QStyleHintReturnMask frameMask;
+    QStyleOption option;
+    option.init(this);
+    if (style()->styleHint(QStyle::SH_ToolTip_Mask, &option, this, &frameMask))
+        setMask(frameMask.region);
+
+    QLabel::resizeEvent(e);
+}
+
+void QTipLabel::mouseMoveEvent(QMouseEvent *e)
+{
+    if (rect.isNull())
+        return;
+    QPoint pos = e->globalPos();
+    if (widget)
+        pos = widget->mapFromGlobal(pos);
+    if (!rect.contains(pos))
+        hideTip();
+    QLabel::mouseMoveEvent(e);
+}
+
+QTipLabel::~QTipLabel()
+{
+    instance = 0;
+}
+
+void QTipLabel::hideTip()
+{
+    if (!hideTimer.isActive())
+        hideTimer.start(300, this);
+}
+
+void QTipLabel::hideTipImmediately()
+{
+    close(); // to trigger QEvent::Close which stops the animation
+    deleteLater();
+}
+
+void QTipLabel::setTipRect(QWidget *w, const QRect &r)
+{
+    if (!rect.isNull() && !w)
+        qWarning("QToolTip::setTipRect: Cannot pass null widget if rect is set");
+    else{
+        widget = w;
+        rect = r;
+    }
+}
+
+void QTipLabel::timerEvent(QTimerEvent *e)
+{
+    if (e->timerId() == hideTimer.timerId()
+        || e->timerId() == expireTimer.timerId()){
+        hideTimer.stop();
+        expireTimer.stop();
+#if defined(Q_WS_MAC) && !defined(QT_NO_EFFECTS)
+        if (QApplication::isEffectEnabled(Qt::UI_FadeTooltip)){
+            // Fade out tip on mac (makes it invisible).
+            // The tip will not be deleted until a new tip is shown.
+
+                        // DRSWAT - Cocoa
+                        macWindowFade(qt_mac_window_for(this));
+            QTipLabel::instance->fadingOut = true; // will never be false again.
+        }
+        else
+            hideTipImmediately();
+#else
+        hideTipImmediately();
+#endif
+    }
+}
+
+bool QTipLabel::eventFilter(QObject *o, QEvent *e)
+{
+    switch (e->type()) {
+#ifdef Q_WS_MAC
+    case QEvent::KeyPress:
+    case QEvent::KeyRelease: {
+        int key = static_cast<QKeyEvent *>(e)->key();
+        Qt::KeyboardModifiers mody = static_cast<QKeyEvent *>(e)->modifiers();
+        if (!(mody & Qt::KeyboardModifierMask)
+            && key != Qt::Key_Shift && key != Qt::Key_Control
+            && key != Qt::Key_Alt && key != Qt::Key_Meta)
+            hideTip();
+        break;
+    }
+#endif
+    case QEvent::Leave:
+        hideTip();
+        break;
+    case QEvent::WindowActivate:
+    case QEvent::WindowDeactivate:
+    case QEvent::MouseButtonPress:
+    case QEvent::MouseButtonRelease:
+    case QEvent::MouseButtonDblClick:
+    case QEvent::FocusIn:
+    case QEvent::FocusOut:
+    case QEvent::Wheel:
+        hideTipImmediately();
+        break;
+
+    case QEvent::MouseMove:
+        if (o == widget && !rect.isNull() && !rect.contains(static_cast<QMouseEvent*>(e)->pos()))
+            hideTip();
+    default:
+        break;
+    }
+    return false;
+}
+
+int QTipLabel::getTipScreen(const QPoint &pos, QWidget *w)
+{
+    if (QApplication::desktop()->isVirtualDesktop())
+        return QApplication::desktop()->screenNumber(pos);
+    else
+        return QApplication::desktop()->screenNumber(w);
+}
+
+void QTipLabel::placeTip(const QPoint &pos, QWidget *w)
+{
+#ifndef QT_NO_STYLE_STYLESHEET
+    if (testAttribute(Qt::WA_StyleSheet) || (w && qobject_cast<QStyleSheetStyle *>(w->style()))) {
+        //the stylesheet need to know the real parent
+        QTipLabel::instance->setProperty("_q_stylesheet_parent", qVariantFromValue(w));
+        //we force the style to be the QStyleSheetStyle, and force to clear the cache as well.
+        QTipLabel::instance->setStyleSheet(QLatin1String("/* */"));
+
+        // Set up for cleaning up this later...
+        QTipLabel::instance->styleSheetParent = w;
+        if (w) {
+            connect(w, SIGNAL(destroyed()),
+                QTipLabel::instance, SLOT(styleSheetParentDestroyed()));
+        }
+    }
+#endif //QT_NO_STYLE_STYLESHEET
+
+
+#ifdef Q_WS_MAC
+    QRect screen = QApplication::desktop()->availableGeometry(getTipScreen(pos, w));
+#else
+    QRect screen = QApplication::desktop()->screenGeometry(getTipScreen(pos, w));
+#endif
+
+    QPoint p = pos;
+    p += QPoint(2,
+#ifdef Q_WS_WIN
+                21
+#else
+                16
+#endif
+        );
+    if (p.x() + this->width() > screen.x() + screen.width())
+        p.rx() -= 4 + this->width();
+    if (p.y() + this->height() > screen.y() + screen.height())
+        p.ry() -= 24 + this->height();
+    if (p.y() < screen.y())
+        p.setY(screen.y());
+    if (p.x() + this->width() > screen.x() + screen.width())
+        p.setX(screen.x() + screen.width() - this->width());
+    if (p.x() < screen.x())
+        p.setX(screen.x());
+    if (p.y() + this->height() > screen.y() + screen.height())
+        p.setY(screen.y() + screen.height() - this->height());
+    this->move(p);
+}
+
+bool QTipLabel::tipChanged(const QPoint &pos, const QString &text, QObject *o)
+{
+    if (QTipLabel::instance->text() != text)
+        return true;
+
+    if (o != widget)
+        return true;
+
+    if (!rect.isNull())
+        return !rect.contains(pos);
+    else
+       return false;
+}
+
+/*!
+    Shows \a text as a tool tip, with the global position \a pos as
+    the point of interest. The tool tip will be shown with a platform
+    specific offset from this point of interest.
+
+    If you specify a non-empty rect the tip will be hidden as soon
+    as you move your cursor out of this area.
+
+    The \a rect is in the coordinates of the widget you specify with
+    \a w. If the \a rect is not empty you must specify a widget.
+    Otherwise this argument can be 0 but it is used to determine the
+    appropriate screen on multi-head systems.
+
+    If \a text is empty the tool tip is hidden. If the text is the
+    same as the currently shown tooltip, the tip will \e not move.
+    You can force moving by first hiding the tip with an empty text,
+    and then showing the new tip at the new position.
+*/
+
+void QToolTip::showText(const QPoint &pos, const QString &text, QWidget *w, const QRect &rect)
+{
+    if (QTipLabel::instance && QTipLabel::instance->isVisible()){ // a tip does already exist
+        if (text.isEmpty()){ // empty text means hide current tip
+            QTipLabel::instance->hideTip();
+            return;
+        }
+        else if (!QTipLabel::instance->fadingOut){
+            // If the tip has changed, reuse the one
+            // that is showing (removes flickering)
+            QPoint localPos = pos;
+            if (w)
+                localPos = w->mapFromGlobal(pos);
+            if (QTipLabel::instance->tipChanged(localPos, text, w)){
+                QTipLabel::instance->reuseTip(text);
+                QTipLabel::instance->setTipRect(w, rect);
+                QTipLabel::instance->placeTip(pos, w);
+            }
+            return;
+        }
+    }
+
+    if (!text.isEmpty()){ // no tip can be reused, create new tip:
+#ifndef Q_WS_WIN
+        new QTipLabel(text, w); // sets QTipLabel::instance to itself
+#else
+        // On windows, we can't use the widget as parent otherwise the window will be
+        // raised when the tooltip will be shown
+        new QTipLabel(text, QApplication::desktop()->screen(QTipLabel::getTipScreen(pos, w)));
+#endif
+        QTipLabel::instance->setTipRect(w, rect);
+        QTipLabel::instance->placeTip(pos, w);
+        QTipLabel::instance->setObjectName(QLatin1String("qtooltip_label"));
+
+
+#if !defined(QT_NO_EFFECTS) && !defined(Q_WS_MAC)
+        if (QApplication::isEffectEnabled(Qt::UI_FadeTooltip))
+            qFadeEffect(QTipLabel::instance);
+        else if (QApplication::isEffectEnabled(Qt::UI_AnimateTooltip))
+            qScrollEffect(QTipLabel::instance);
+        else
+            QTipLabel::instance->show();
+#else
+        QTipLabel::instance->show();
+#endif
+    }
+}
+
+/*!
+    \overload
+
+    This is analogous to calling QToolTip::showText(\a pos, \a text, \a w, QRect())
+*/
+
+void QToolTip::showText(const QPoint &pos, const QString &text, QWidget *w)
+{
+    QToolTip::showText(pos, text, w, QRect());
+}
+
+
+/*!
+    \fn void QToolTip::hideText()
+    \since 4.2
+
+    Hides the tool tip. This is the same as calling showText() with an
+    empty string.
+
+    \sa showText()
+*/
+
+
+/*!
+  \since 4.4
+
+  Returns true if this tooltip is currently shown.
+
+  \sa showText()
+ */
+bool QToolTip::isVisible()
+{
+    return (QTipLabel::instance != 0 && QTipLabel::instance->isVisible());
+}
+
+/*!
+  \since 4.4
+
+  Returns the tooltip text, if a tooltip is visible, or an
+  empty string if a tooltip is not visible.
+ */
+QString QToolTip::text()
+{
+    if (QTipLabel::instance)
+        return QTipLabel::instance->text();
+    return QString();
+}
+
+
+Q_GLOBAL_STATIC(QPalette, tooltip_palette)
+
+/*!
+    Returns the palette used to render tooltips.
+
+    \note Tool tips use the inactive color group of QPalette, because tool
+    tips are not active windows.
+*/
+QPalette QToolTip::palette()
+{
+    return *tooltip_palette();
+}
+
+/*!
+    \since 4.2
+
+    Returns the font used to render tooltips.
+*/
+QFont QToolTip::font()
+{
+    return QApplication::font("QTipLabel");
+}
+
+/*!
+    \since 4.2
+
+    Sets the \a palette used to render tooltips.
+
+    \note Tool tips use the inactive color group of QPalette, because tool
+    tips are not active windows.
+*/
+void QToolTip::setPalette(const QPalette &palette)
+{
+    *tooltip_palette() = palette;
+    if (QTipLabel::instance)
+        QTipLabel::instance->setPalette(palette);
+}
+
+/*!
+    \since 4.2
+
+    Sets the \a font used to render tooltips.
+*/
+void QToolTip::setFont(const QFont &font)
+{
+    QApplication::setFont(font, "QTipLabel");
+}
+
+
+/*!
+    \fn void QToolTip::add(QWidget *widget, const QString &text)
+
+    Use QWidget::setToolTip() instead.
+
+    \oldcode
+    tip->add(widget, text);
+    \newcode
+    widget->setToolTip(text);
+    \endcode
+*/
+
+/*!
+    \fn void QToolTip::add(QWidget *widget, const QRect &rect, const QString &text)
+
+    Intercept the QEvent::ToolTip events in your widget's
+    QWidget::event() function and call QToolTip::showText() with the
+    text you want to display. The \l{widgets/tooltips}{Tooltips}
+    example illustrates this technique.
+*/
+
+/*!
+    \fn void QToolTip::remove(QWidget *widget)
+
+    Use QWidget::setToolTip() instead.
+
+    \oldcode
+    tip->remove(widget);
+    \newcode
+    widget->setToolTip("");
+    \endcode
+*/
+
+QT_END_NAMESPACE
+
+#include "qtooltip.moc"
+#endif // QT_NO_TOOLTIP