From 0ac879e39f677267b9b3e99ebb6376e0cf97f0fd Mon Sep 17 00:00:00 2001
From: Friedemann Kleint <Friedemann.Kleint@nokia.com>
Date: Fri, 11 Feb 2011 15:00:13 +0100
Subject: [PATCH] Debugger: Make tooltips pinnable.

Replace old debugger tooltip by a new ToolTipManager which
has a list of AbstractDebuggerToolTipWidget with the functionality
to 'acquire' an engine (display its data) and 'release' it
(store engine data and display them as 'previous') and serialization
to XML session data.
DebuggerTreeViewToolTipWidget implements AbstractDebuggerToolTipWidget
for tree model acting as  a filter on watch models.

Rubber-stamped-by: hjk
---
 src/plugins/debugger/cdb/cdbengine.cpp        |   30 +-
 src/plugins/debugger/cdb/cdbengine.h          |    5 +-
 src/plugins/debugger/debugger.pro             |    8 +-
 src/plugins/debugger/debugger.qrc             |    1 +
 src/plugins/debugger/debuggercore.h           |    3 +
 src/plugins/debugger/debuggerengine.cpp       |   16 +-
 src/plugins/debugger/debuggerengine.h         |    7 +-
 src/plugins/debugger/debuggerplugin.cpp       |   28 +-
 src/plugins/debugger/debuggerrunner.cpp       |    5 +-
 src/plugins/debugger/debuggertooltip.cpp      |  221 ---
 src/plugins/debugger/debuggertooltip.h        |   56 -
 .../debugger/debuggertooltipmanager.cpp       | 1242 +++++++++++++++++
 src/plugins/debugger/debuggertooltipmanager.h |  278 ++++
 src/plugins/debugger/gdb/classicgdbengine.cpp |    2 +-
 src/plugins/debugger/gdb/gdbengine.cpp        |  131 +-
 src/plugins/debugger/gdb/gdbengine.h          |   12 +-
 src/plugins/debugger/gdb/pythongdbengine.cpp  |   23 +-
 src/plugins/debugger/images/pin.xpm           |   19 +
 src/plugins/debugger/pdb/pdbengine.cpp        |    6 +-
 src/plugins/debugger/pdb/pdbengine.h          |    2 +-
 src/plugins/debugger/qml/qmlcppengine.cpp     |    4 +-
 src/plugins/debugger/qml/qmlcppengine.h       |    2 +-
 src/plugins/debugger/qml/qmlengine.cpp        |    6 +-
 src/plugins/debugger/qml/qmlengine.h          |    2 +-
 src/plugins/debugger/script/scriptengine.cpp  |    6 +-
 src/plugins/debugger/script/scriptengine.h    |    2 +-
 src/plugins/debugger/tcf/tcfengine.cpp        |    3 +-
 src/plugins/debugger/tcf/tcfengine.h          |    2 +-
 src/plugins/debugger/watchhandler.h           |    4 +-
 29 files changed, 1717 insertions(+), 409 deletions(-)
 delete mode 100644 src/plugins/debugger/debuggertooltip.cpp
 delete mode 100644 src/plugins/debugger/debuggertooltip.h
 create mode 100644 src/plugins/debugger/debuggertooltipmanager.cpp
 create mode 100644 src/plugins/debugger/debuggertooltipmanager.h
 create mode 100644 src/plugins/debugger/images/pin.xpm

diff --git a/src/plugins/debugger/cdb/cdbengine.cpp b/src/plugins/debugger/cdb/cdbengine.cpp
index d72e5747fe8..469fe6fccf2 100644
--- a/src/plugins/debugger/cdb/cdbengine.cpp
+++ b/src/plugins/debugger/cdb/cdbengine.cpp
@@ -50,7 +50,7 @@
 #include "disassembleragent.h"
 #include "memoryagent.h"
 #include "debuggerrunner.h"
-#include "debuggertooltip.h"
+#include "debuggertooltipmanager.h"
 #include "cdbparsehelpers.h"
 #include "watchutils.h"
 #include "gdb/gdbmi.h"
@@ -471,7 +471,9 @@ void CdbEngine::syncOperateByInstruction(bool operateByInstruction)
     postCommand(m_operateByInstruction ? QByteArray("l-s") : QByteArray("l+s"), 0);
 }
 
-void CdbEngine::setToolTipExpression(const QPoint &mousePos, TextEditor::ITextEditor *editor, int cursorPos)
+void CdbEngine::setToolTipExpression(const QPoint &mousePos,
+                                     TextEditor::ITextEditor *editor,
+                                     const DebuggerToolTipContext &contextIn)
 {
     if (debug)
         qDebug() << Q_FUNC_INFO;
@@ -481,10 +483,10 @@ void CdbEngine::setToolTipExpression(const QPoint &mousePos, TextEditor::ITextEd
     // Determine expression and function
     int line;
     int column;
-    QString function;
-    const QString exp = cppExpressionAt(editor, cursorPos, &line, &column, &function);
+    DebuggerToolTipContext context = contextIn;
+    const QString exp = cppExpressionAt(editor, context.position, &line, &column, &context.function);
     // Are we in the current stack frame
-    if (function.isEmpty() || exp.isEmpty() || function != stackHandler()->currentFrame().function)
+    if (context.function.isEmpty() || exp.isEmpty() || context.function != stackHandler()->currentFrame().function)
         return;
     // No numerical or any other expressions [yet]
     if (!(exp.at(0).isLetter() || exp.at(0) == QLatin1Char('_')))
@@ -492,9 +494,12 @@ void CdbEngine::setToolTipExpression(const QPoint &mousePos, TextEditor::ITextEd
     const QByteArray iname = QByteArray(localsPrefixC) + exp.toAscii();
     const QModelIndex index = watchHandler()->itemIndex(iname);
     if (index.isValid()) {
-        showDebuggerToolTip(mousePos, watchHandler()->modelForIName(iname), index.row());
-    } else {
-        hideDebuggerToolTip();
+        DebuggerTreeViewToolTipWidget *tw = new DebuggerTreeViewToolTipWidget;
+        tw->setContext(context);
+        tw->setDebuggerModel(LocalsWatch);
+        tw->setExpression(exp);
+        tw->acquireEngine(this);
+        DebuggerToolTipManager::instance()->add(mousePos, tw);
     }
 }
 
@@ -1295,11 +1300,11 @@ void CdbEngine::activateFrame(int index)
         }
     } else {
         gotoLocation(frame);
-        updateLocals();
+        updateLocals(true);
     }
 }
 
-void CdbEngine::updateLocals()
+void CdbEngine::updateLocals(bool forNewStackFrame)
 {
     typedef QHash<QByteArray, int> WatcherHash;
 
@@ -1362,7 +1367,7 @@ void CdbEngine::updateLocals()
     // Required arguments: frame
     str << blankSeparator << frameIndex;
     watchHandler()->beginCycle();
-    postExtensionCommand("locals", arguments, 0, &CdbEngine::handleLocals);
+    postExtensionCommand("locals", arguments, 0, &CdbEngine::handleLocals, 0, QVariant(forNewStackFrame));
 }
 
 void CdbEngine::selectThread(int index)
@@ -1571,6 +1576,9 @@ void CdbEngine::handleLocals(const CdbExtensionCommandPtr &reply)
             foreach (const WatchData &wd, watchData)
                 nsp << wd.toString() <<'\n';
         }
+        const bool forNewStackFrame = reply->cookie.toBool();
+        if (forNewStackFrame)
+            emit stackFrameCompleted();
     } else {
         showMessage(QString::fromLatin1(reply->errorMessage), LogError);
     }
diff --git a/src/plugins/debugger/cdb/cdbengine.h b/src/plugins/debugger/cdb/cdbengine.h
index 2d4e02f02b0..5fa6f8bfb04 100644
--- a/src/plugins/debugger/cdb/cdbengine.h
+++ b/src/plugins/debugger/cdb/cdbengine.h
@@ -85,7 +85,8 @@ public:
     virtual ~CdbEngine();
     // Factory function that returns 0 if the debug engine library cannot be found.
 
-    virtual void setToolTipExpression(const QPoint &mousePos, TextEditor::ITextEditor *editor, int cursorPos);
+    virtual void setToolTipExpression(const QPoint &mousePos, TextEditor::ITextEditor *editor,
+                                      const DebuggerToolTipContext &ctx);
     virtual void setupEngine();
     virtual void setupInferior();
     virtual void runEngine();
@@ -215,7 +216,7 @@ private:
 
     QString normalizeFileName(const QString &f);
     void updateLocalVariable(const QByteArray &iname);
-    void updateLocals();
+    void updateLocals(bool forNewStackFrame = false);
     int elapsedLogTime() const;
     void addLocalsOptions(ByteArrayInputStream &s) const;
     unsigned parseStackTrace(const GdbMi &data, bool sourceStepInto);
diff --git a/src/plugins/debugger/debugger.pro b/src/plugins/debugger/debugger.pro
index d8e85d72dca..3e0d32ba6c2 100644
--- a/src/plugins/debugger/debugger.pro
+++ b/src/plugins/debugger/debugger.pro
@@ -33,7 +33,6 @@ HEADERS += breakhandler.h \
     debuggerstartparameters.h \
     debuggerstreamops.h \
     debuggerstringutils.h \
-    debuggertooltip.h \
     disassembleragent.h \
     disassemblerlines.h \
     logwindow.h \
@@ -60,7 +59,8 @@ HEADERS += breakhandler.h \
     threaddata.h \
     threadshandler.h \
     watchdelegatewidgets.h \
-    debuggerruncontrolfactory.h
+    debuggerruncontrolfactory.h \
+    debuggertooltipmanager.h
 
 SOURCES += breakhandler.cpp \
     breakpoint.cpp \
@@ -74,7 +74,6 @@ SOURCES += breakhandler.cpp \
     debuggerplugin.cpp \
     debuggerrunner.cpp \
     debuggerstreamops.cpp \
-    debuggertooltip.cpp \
     disassembleragent.cpp \
     disassemblerlines.cpp \
     logwindow.cpp \
@@ -100,7 +99,8 @@ SOURCES += breakhandler.cpp \
     watchutils.cpp \
     watchwindow.cpp \
     stackframe.cpp \
-    watchdelegatewidgets.cpp
+    watchdelegatewidgets.cpp \
+    debuggertooltipmanager.cpp
 
 FORMS += attachexternaldialog.ui \
     attachcoredialog.ui \
diff --git a/src/plugins/debugger/debugger.qrc b/src/plugins/debugger/debugger.qrc
index c5aa9cc39ef..0f7ebee1ab5 100644
--- a/src/plugins/debugger/debugger.qrc
+++ b/src/plugins/debugger/debugger.qrc
@@ -29,5 +29,6 @@
         <file>images/breakpoint_pending_24.png</file>
         <file>images/location_16.png</file>
         <file>images/location_24.png</file>
+        <file>images/pin.xpm</file>
     </qresource>
 </RCC>
diff --git a/src/plugins/debugger/debuggercore.h b/src/plugins/debugger/debuggercore.h
index b3126fc88a0..76e491b7ab1 100644
--- a/src/plugins/debugger/debuggercore.h
+++ b/src/plugins/debugger/debuggercore.h
@@ -61,6 +61,7 @@ namespace Internal {
 class BreakHandler;
 class SnapshotHandler;
 class Symbol;
+class DebuggerToolTipManager;
 
 class DebuggerCore : public QObject
 {
@@ -109,6 +110,8 @@ public:
     virtual Utils::SavedAction *action(int code) const = 0;
     virtual bool boolSetting(int code) const = 0;
     virtual QString stringSetting(int code) const = 0;
+
+    virtual DebuggerToolTipManager *toolTipManager() const = 0;
 };
 
 // This is the only way to access the global object.
diff --git a/src/plugins/debugger/debuggerengine.cpp b/src/plugins/debugger/debuggerengine.cpp
index 38fc254ce4e..3ba45997611 100644
--- a/src/plugins/debugger/debuggerengine.cpp
+++ b/src/plugins/debugger/debuggerengine.cpp
@@ -38,7 +38,6 @@
 #include "debuggerplugin.h"
 #include "debuggerrunner.h"
 #include "debuggerstringutils.h"
-#include "debuggertooltip.h"
 #include "debuggerstartparameters.h"
 
 #include "memoryagent.h"
@@ -357,11 +356,6 @@ void DebuggerEngine::showStatusMessage(const QString &msg, int timeout) const
     showMessage(msg, StatusBar, timeout);
 }
 
-void DebuggerEngine::removeTooltip()
-{
-    watchHandler()->removeTooltip();
-    hideDebuggerToolTip();
-}
 
 void DebuggerEngine::frameUp()
 {
@@ -474,6 +468,14 @@ QAbstractItemModel *DebuggerEngine::returnModel() const
     return model;
 }
 
+QAbstractItemModel *DebuggerEngine::toolTipsModel() const
+{
+    QAbstractItemModel *model = watchHandler()->model(TooltipsWatch);
+    if (model->objectName().isEmpty()) // Make debugging easier.
+        model->setObjectName(objectName() + QLatin1String("TooltipsModel"));
+    return model;
+}
+
 QAbstractItemModel *DebuggerEngine::sourceFilesModel() const
 {
     QAbstractItemModel *model = sourceFilesHandler()->model();
@@ -1238,7 +1240,7 @@ DebuggerRunControl *DebuggerEngine::runControl() const
 }
 
 void DebuggerEngine::setToolTipExpression
-    (const QPoint &, TextEditor::ITextEditor *, int)
+    (const QPoint &, TextEditor::ITextEditor *, const DebuggerToolTipContext &)
 {
 }
 
diff --git a/src/plugins/debugger/debuggerengine.h b/src/plugins/debugger/debuggerengine.h
index c70766b8828..c3b13daeed9 100644
--- a/src/plugins/debugger/debuggerengine.h
+++ b/src/plugins/debugger/debuggerengine.h
@@ -81,6 +81,7 @@ class ThreadsHandler;
 class WatchHandler;
 class BreakpointParameters;
 class QmlCppEngine;
+class DebuggerToolTipContext;
 
 struct WatchUpdateFlags
 {
@@ -137,7 +138,7 @@ public:
     DebuggerStartParameters &startParameters();
 
     virtual void setToolTipExpression(const QPoint & mousePos,
-        TextEditor::ITextEditor *editor, int cursorPos);
+        TextEditor::ITextEditor *editor, const Internal::DebuggerToolTipContext &);
 
     virtual void updateWatchData(const Internal::WatchData &data,
         const Internal::WatchUpdateFlags & flags = Internal::WatchUpdateFlags());
@@ -183,7 +184,6 @@ public:
 
     virtual void assignValueInDebugger(const Internal::WatchData *data,
         const QString &expr, const QVariant &value);
-    virtual void removeTooltip();
     virtual void selectThread(int index);
 
     virtual void handleRemoteSetupDone(int gdbServerPort, int qmlPort);
@@ -204,6 +204,7 @@ public:
     virtual QAbstractItemModel *localsModel() const;
     virtual QAbstractItemModel *watchersModel() const;
     virtual QAbstractItemModel *returnModel() const;
+    virtual QAbstractItemModel *toolTipsModel() const;
     virtual QAbstractItemModel *sourceFilesModel() const;
 
     void progressPing();
@@ -253,6 +254,8 @@ public:
 
 signals:
     void stateChanged(const Debugger::DebuggerState &state);
+    // A new stack frame is on display including locals.
+    void stackFrameCompleted();
     void updateViewsRequested();
     /*
      * For "external" clients of a debugger run control that needs to do
diff --git a/src/plugins/debugger/debuggerplugin.cpp b/src/plugins/debugger/debuggerplugin.cpp
index 7af762a428b..263fcd3f98e 100644
--- a/src/plugins/debugger/debuggerplugin.cpp
+++ b/src/plugins/debugger/debuggerplugin.cpp
@@ -43,7 +43,6 @@
 #include "debuggerrunner.h"
 #include "debuggerruncontrolfactory.h"
 #include "debuggerstringutils.h"
-#include "debuggertooltip.h"
 
 #include "breakpoint.h"
 #include "breakhandler.h"
@@ -62,6 +61,7 @@
 #include "watchhandler.h"
 #include "watchwindow.h"
 #include "watchutils.h"
+#include "debuggertooltipmanager.h"
 
 #include "snapshothandler.h"
 #include "threadshandler.h"
@@ -1170,6 +1170,8 @@ public slots:
     bool parseArguments(const QStringList &args,
         unsigned *enabledEngines, QString *errorMessage);
 
+    DebuggerToolTipManager *toolTipManager() const { return m_toolTipManager; }
+
 public:
     DebuggerMainWindow *m_mainWindow;
     DebuggerRunControlFactory *m_debuggerRunControlFactory;
@@ -1246,9 +1248,11 @@ public:
     QSettings *m_coreSettings;
     bool m_gdbBinariesChanged;
     uint m_cmdLineEnabledEngines;
+    DebuggerToolTipManager *m_toolTipManager;
 };
 
-DebuggerPluginPrivate::DebuggerPluginPrivate(DebuggerPlugin *plugin)
+DebuggerPluginPrivate::DebuggerPluginPrivate(DebuggerPlugin *plugin) :
+    m_toolTipManager(new DebuggerToolTipManager(this))
 {
     qRegisterMetaType<WatchData>("WatchData");
     qRegisterMetaType<ContextData>("ContextData");
@@ -1956,8 +1960,12 @@ void DebuggerPluginPrivate::showToolTip(ITextEditor *editor,
     if (!currentEngine()->canDisplayTooltip())
         return;
     QTC_ASSERT(handled, return);
-    *handled = true;
-    currentEngine()->setToolTipExpression(point, editor, pos);
+
+    const DebuggerToolTipContext context = DebuggerToolTipContext::fromEditor(editor, pos);
+    if (context.isValid()) {
+        *handled = true;
+        currentEngine()->setToolTipExpression(point, editor, context);
+    }
 }
 
 DebuggerRunControl *DebuggerPluginPrivate::createDebugger
@@ -2036,7 +2044,7 @@ void DebuggerPluginPrivate::cleanupViews()
 {
     m_reverseDirectionAction->setChecked(false);
     m_reverseDirectionAction->setEnabled(false);
-    hideDebuggerToolTip();
+    m_toolTipManager->closeUnpinnedToolTips();
 
     if (!boolSetting(CloseBuffersOnExit))
         return;
@@ -2089,7 +2097,7 @@ void DebuggerPluginPrivate::setInitialState()
     setBusyCursor(false);
     m_reverseDirectionAction->setChecked(false);
     m_reverseDirectionAction->setEnabled(false);
-    hideDebuggerToolTip();
+    m_toolTipManager->closeAllToolTips();
 
     m_startExternalAction->setEnabled(true);
     m_attachExternalAction->setEnabled(true);
@@ -2286,12 +2294,15 @@ void DebuggerPluginPrivate::onModeChanged(IMode *mode)
 
     m_mainWindow->onModeChanged(mode);
 
-    if (mode->id() != Constants::MODE_DEBUG)
+    if (mode->id() != Constants::MODE_DEBUG) {
+        m_toolTipManager->leavingDebugMode();
         return;
+    }
 
     EditorManager *editorManager = EditorManager::instance();
     if (editorManager->currentEditor())
         editorManager->currentEditor()->widget()->setFocus();
+    m_toolTipManager->debugModeEntered();
 }
 
 void DebuggerPluginPrivate::showSettingsDialog()
@@ -2345,11 +2356,13 @@ void DebuggerPluginPrivate::sessionLoaded()
 {
     m_breakHandler->loadSessionData();
     dummyEngine()->watchHandler()->loadSessionData();
+    m_toolTipManager->loadSessionData();
 }
 
 void DebuggerPluginPrivate::aboutToUnloadSession()
 {
     m_breakHandler->removeSessionData();
+    m_toolTipManager->sessionAboutToChange();
     // Stop debugging the active project when switching sessions.
     // Note that at startup, session switches may occur, which interfere
     // with command-line debugging startup.
@@ -2362,6 +2375,7 @@ void DebuggerPluginPrivate::aboutToUnloadSession()
 void DebuggerPluginPrivate::aboutToSaveSession()
 {
     dummyEngine()->watchHandler()->loadSessionData();
+    m_toolTipManager->saveSessionData();
     m_breakHandler->saveSessionData();
 }
 
diff --git a/src/plugins/debugger/debuggerrunner.cpp b/src/plugins/debugger/debuggerrunner.cpp
index 0ccb78e7d05..6ad856cff50 100644
--- a/src/plugins/debugger/debuggerrunner.cpp
+++ b/src/plugins/debugger/debuggerrunner.cpp
@@ -43,6 +43,7 @@
 #include "debuggerstartparameters.h"
 #include "gdb/gdboptionspage.h"
 #include "lldb/lldbenginehost.h"
+#include "debuggertooltipmanager.h"
 
 #ifdef Q_OS_WIN
 #  include "peutils.h"
@@ -344,7 +345,9 @@ DebuggerRunControl::DebuggerRunControl(RunConfiguration *runConfiguration,
             break;
     }
 
-    if (!d->m_engine) {
+    if (d->m_engine) {
+        DebuggerToolTipManager::instance()->registerEngine(d->m_engine);
+    } else {
         // Could not find anything suitable.
         debuggingFinished();
         // Create Message box with possibility to go to settings.
diff --git a/src/plugins/debugger/debuggertooltip.cpp b/src/plugins/debugger/debuggertooltip.cpp
deleted file mode 100644
index fb1790e6127..00000000000
--- a/src/plugins/debugger/debuggertooltip.cpp
+++ /dev/null
@@ -1,221 +0,0 @@
-/**************************************************************************
-**
-** This file is part of Qt Creator
-**
-** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
-**
-** Contact: Nokia Corporation (qt-info@nokia.com)
-**
-** 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.
-**
-**************************************************************************/
-
-#include "debuggertooltip.h"
-
-#include <utils/qtcassert.h>
-
-#include <QtCore/QtDebug>
-#include <QtCore/QPointer>
-
-#include <QtGui/QApplication>
-#include <QtGui/QDesktopWidget>
-#include <QtGui/QHeaderView>
-#include <QtGui/QScrollBar>
-#include <QtGui/QTreeView>
-#include <QtGui/QSortFilterProxyModel>
-
-namespace Debugger {
-namespace Internal {
-
-class ToolTipWidget : public QTreeView
-{
-    Q_OBJECT
-
-public:
-    ToolTipWidget(QWidget *parent);
-
-    QSize sizeHint() const { return m_size; }
-
-    void done();
-    void run(const QPoint &point, const QModelIndex &index);
-    int computeHeight(const QModelIndex &index) const;
-    Q_SLOT void computeSize();
-
-    void leaveEvent(QEvent *ev);
-
-private:
-    QSize m_size;
-};
-
-static QPointer<ToolTipWidget> theToolTipWidget;
-
-
-ToolTipWidget::ToolTipWidget(QWidget *parent)
-    : QTreeView(parent)
-{
-    setWindowFlags(Qt::ToolTip | Qt::WindowStaysOnTopHint);
-    setFocusPolicy(Qt::NoFocus);
-
-    header()->hide();
-
-    setUniformRowHeights(true);
-    setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
-    setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
-
-    connect(this, SIGNAL(collapsed(QModelIndex)), this, SLOT(computeSize()),
-        Qt::QueuedConnection);
-    connect(this, SIGNAL(expanded(QModelIndex)), this, SLOT(computeSize()),
-        Qt::QueuedConnection);
-}
-
-int ToolTipWidget::computeHeight(const QModelIndex &index) const
-{
-    int s = rowHeight(index);
-    for (int i = 0; i < model()->rowCount(index); ++i)
-        s += computeHeight(model()->index(i, 0, index));
-    return s;
-}
-
-void ToolTipWidget::computeSize()
-{
-    int columns = 0;
-    for (int i = 0; i < 3; ++i) {
-        resizeColumnToContents(i);
-        columns += sizeHintForColumn(i);
-    }
-    int rows = computeHeight(QModelIndex());
-
-    // Fit tooltip to screen, showing/hiding scrollbars as needed.
-    // Add a bit of space to account for tooltip border, and not
-    // touch the border of the screen.
-    QPoint pos(x(), y());
-    QRect desktopRect = QApplication::desktop()->availableGeometry(pos);
-    const int maxWidth = desktopRect.right() - pos.x() - 5 - 5;
-    const int maxHeight = desktopRect.bottom() - pos.y() - 5 - 5;
-
-    if (columns > maxWidth)
-        rows += horizontalScrollBar()->height();
-
-    if (rows > maxHeight) {
-        setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
-        rows = maxHeight;
-        columns += verticalScrollBar()->width();
-    } else {
-        setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
-    }
-
-    if (columns > maxWidth) {
-        setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
-        columns = maxWidth;
-    } else {
-        setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
-    }
-
-    m_size = QSize(columns + 5, rows + 5);
-    setMinimumSize(m_size);
-    setMaximumSize(m_size);
-}
-
-void ToolTipWidget::done()
-{
-    deleteLater();
-}
-
-void ToolTipWidget::run(const QPoint &point, const QModelIndex &index)
-{
-    QAbstractItemModel *model = const_cast<QAbstractItemModel *>(index.model());
-    move(point);
-    setModel(model);
-    // Track changes in filter models.
-    connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)),
-            this, SLOT(computeSize()), Qt::QueuedConnection);
-    computeSize();
-    setRootIsDecorated(model->hasChildren(index));
-}
-
-void ToolTipWidget::leaveEvent(QEvent *ev)
-{
-    Q_UNUSED(ev);
-    if (QApplication::keyboardModifiers() == Qt::NoModifier)
-        hide();
-}
-
-void showDebuggerToolTip(const QPoint &point, const QModelIndex &index)
-{
-    if (index.model()) {
-        if (!theToolTipWidget)
-            theToolTipWidget = new ToolTipWidget(0);
-        theToolTipWidget->run(point, index);
-        theToolTipWidget->show();
-    } else if (theToolTipWidget) {
-        theToolTipWidget->done();
-        theToolTipWidget = 0;
-    }
-}
-
-// Model for tooltips filtering a local variable using the locals model.
-class ToolTipRowFilterModel : public QSortFilterProxyModel
-{
-public:
-    // Index points the variable to be filtered.
-    explicit ToolTipRowFilterModel(QAbstractItemModel *model, int row, QObject *parent = 0);
-    virtual bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const;
-
-private:
-    const int m_row;
-};
-
-ToolTipRowFilterModel::ToolTipRowFilterModel(QAbstractItemModel *model, int row, QObject *parent) :
-    QSortFilterProxyModel(parent), m_row(row)
-{
-    setSourceModel(model);
-}
-
-bool ToolTipRowFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
-{
-    // Match on row for top level, else pass through.
-    return sourceParent.isValid() || sourceRow == m_row;
-}
-
-// Show tooltip filtering a row of a source model.
-void showDebuggerToolTip(const QPoint &point, QAbstractItemModel *model, int row)
-{
-    // Create a filter model parented on the widget to display column
-    ToolTipRowFilterModel *filterModel = new ToolTipRowFilterModel(model, row);
-    showDebuggerToolTip(point, filterModel->index(0, 0));
-    QTC_ASSERT(theToolTipWidget, return; )
-    filterModel->setParent(theToolTipWidget);
-}
-
-void hideDebuggerToolTip(int delay)
-{
-    Q_UNUSED(delay)
-    if (theToolTipWidget)
-        theToolTipWidget->done();
-}
-
-} // namespace Internal
-} // namespace Debugger
-
-#include "debuggertooltip.moc"
diff --git a/src/plugins/debugger/debuggertooltip.h b/src/plugins/debugger/debuggertooltip.h
deleted file mode 100644
index 9e7fec0ca0e..00000000000
--- a/src/plugins/debugger/debuggertooltip.h
+++ /dev/null
@@ -1,56 +0,0 @@
-/**************************************************************************
-**
-** This file is part of Qt Creator
-**
-** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
-**
-** Contact: Nokia Corporation (qt-info@nokia.com)
-**
-** 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.
-**
-**************************************************************************/
-
-#ifndef DEBUGGER_DEBUGGERTOOLTIP_H
-#define DEBUGGER_DEBUGGERTOOLTIP_H
-
-#include <QtCore/QtGlobal>
-
-QT_BEGIN_NAMESPACE
-class QModelIndex;
-class QPoint;
-class QAbstractItemModel;
-QT_END_NAMESPACE
-
-namespace Debugger {
-namespace Internal {
-
-void showDebuggerToolTip(const QPoint &point, const QModelIndex &rootIndex);
-// Show tooltip filtering a row of a source model.
-void showDebuggerToolTip(const QPoint &point, QAbstractItemModel *model, int row);
-void hideDebuggerToolTip(int delay = 0);
-
-} // namespace Internal
-} // namespace Debugger
-
-#endif // DEBUGGER_DEBUGGERTOOLTIP_H
diff --git a/src/plugins/debugger/debuggertooltipmanager.cpp b/src/plugins/debugger/debuggertooltipmanager.cpp
new file mode 100644
index 00000000000..65aab92d06c
--- /dev/null
+++ b/src/plugins/debugger/debuggertooltipmanager.cpp
@@ -0,0 +1,1242 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** 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.
+**
+**************************************************************************/
+
+#include "debuggertooltipmanager.h"
+#include "watchutils.h"
+#include "debuggerengine.h"
+#include "watchhandler.h"
+#include "stackhandler.h"
+#include "debuggercore.h"
+
+#include <coreplugin/icore.h>
+#include <coreplugin/modemanager.h>
+#include <coreplugin/imode.h>
+#include <coreplugin/editormanager/editormanager.h>
+#include <texteditor/itexteditor.h>
+
+#include <utils/qtcassert.h>
+
+#include <QtGui/QToolButton>
+#include <QtGui/QToolBar>
+#include <QtGui/QVBoxLayout>
+#include <QtGui/QStyle>
+#include <QtGui/QIcon>
+#include <QtGui/QApplication>
+#include <QtGui/QMainWindow>
+#include <QtGui/QMoveEvent>
+#include <QtGui/QDesktopWidget>
+#include <QtGui/QScrollBar>
+#include <QtGui/QSortFilterProxyModel>
+#include <QtGui/QStandardItemModel>
+#include <QtGui/QStandardItem>
+#include <QtGui/QPlainTextEdit>
+#include <QtGui/QTextCursor>
+#include <QtGui/QTextDocument>
+#include <QtGui/QLabel>
+#include <QtGui/QMenu>
+#include <QtGui/QAction>
+
+#include <QtCore/QVariant>
+#include <QtCore/QStack>
+#include <QtCore/QDebug>
+#include <QtCore/QTimer>
+
+enum { debugToolTips = 0 };
+
+// Expire tooltips after n days on (no longer load them) in order
+// to avoid them piling up.
+enum { toolTipsExpiryDays = 6 };
+
+static const char sessionSettingsKeyC[] = "DebuggerToolTips";
+static const char sessionDocumentC[] = "DebuggerToolTips";
+static const char sessionVersionAttributeC[] = "version";
+static const char toolTipElementC[] = "DebuggerToolTip";
+static const char toolTipClassAttributeC[] = "class";
+static const char fileNameAttributeC[] = "name";
+static const char functionAttributeC[] = "function";
+static const char textPositionAttributeC[] = "position";
+static const char textLineAttributeC[] = "line";
+static const char textColumnAttributeC[] = "column";
+static const char engineTypeAttributeC[] = "engine";
+static const char dateAttributeC[] = "date";
+static const char treeElementC[] = "tree";
+static const char treeModelAttributeC[] = "model"; // Locals/Watches
+static const char treeExpressionAttributeC[] = "expression"; // Locals/Watches
+static const char modelElementC[] = "model";
+static const char modelColumnCountAttributeC[] = "columncount";
+static const char modelRowElementC[] = "row";
+static const char modelItemElementC[] = "item";
+
+// Forward a stream reader across end elements looking for the
+// next start element of a desired type.
+static inline bool readStartElement(QXmlStreamReader &r, const char *name)
+{
+    if (debugToolTips > 1)
+        qDebug("readStartElement: looking for '%s', currently at: %s/%s",
+               name, qPrintable(r.tokenString()), qPrintable(r.name().toString()));
+    while (r.tokenType() != QXmlStreamReader::StartElement || r.name() != QLatin1String(name))
+        switch (r.readNext()) {
+        case QXmlStreamReader::EndDocument:
+            return false;
+        case QXmlStreamReader::NoToken:
+        case QXmlStreamReader::Invalid:
+            qWarning("'%s'/'%s' encountered while looking for start element '%s'.",
+                     qPrintable(r.tokenString()), qPrintable(r.name().toString()), name);
+            return false;
+        default:
+            break;
+        }
+    return true;
+}
+
+static inline void debugMode(const QAbstractItemModel *model)
+{
+    QDebug nospace = qDebug().nospace();
+    nospace << model << '\n';
+    for (int r = 0; r < model->rowCount(); r++)
+        nospace << '#' << r << ' ' << model->data(model->index(r, 0)).toString() << '\n';
+}
+
+static inline QPlainTextEdit *plainTextEditor(Core::IEditor *ie, QString *fileName = 0)
+{
+    if (const Core::IFile *file = ie->file())
+        if (qobject_cast<TextEditor::ITextEditor *>(ie))
+            if (QPlainTextEdit *pe = qobject_cast<QPlainTextEdit *>(ie->widget())) {
+                if (fileName)
+                    *fileName = file->fileName();
+                return pe;
+            }
+    return 0;
+}
+
+static inline QPlainTextEdit *currentPlainTextEditor(QString *fileName = 0)
+{
+    if (Core::IEditor *ie = Core::EditorManager::instance()->currentEditor())
+        return plainTextEditor(ie, fileName);
+    return 0;
+}
+
+namespace Debugger {
+namespace Internal {
+
+/* Helper for building a QStandardItemModel of a tree form (see TreeModelVisitor).
+ * The recursion/building is based on the scheme: \code
+<row><item1><item2>
+    <row><item11><item12></row>
+</row>
+\endcode */
+
+class StandardItemTreeModelBuilder {
+public:
+    typedef QList<QStandardItem *> StandardItemRow;
+
+    explicit StandardItemTreeModelBuilder(QStandardItemModel *m, Qt::ItemFlags f = Qt::ItemIsSelectable);
+
+    inline void addItem(const QString &);
+    inline void startRow();
+    inline void endRow();
+
+private:
+    inline void pushRow();
+
+    QStandardItemModel *m_model;
+    const Qt::ItemFlags m_flags;
+    StandardItemRow m_row;
+    QStack<QStandardItem *> m_rowParents;
+};
+
+StandardItemTreeModelBuilder::StandardItemTreeModelBuilder(QStandardItemModel *m, Qt::ItemFlags f) :
+    m_model(m), m_flags(f)
+{
+    m_model->removeRows(0, m_model->rowCount());
+}
+
+void StandardItemTreeModelBuilder::addItem(const QString &s)
+{
+    QStandardItem *item = new QStandardItem(s);
+    item->setFlags(m_flags);
+    m_row.push_back(item);
+}
+
+void StandardItemTreeModelBuilder::pushRow()
+{
+    if (m_rowParents.isEmpty()) {
+        m_model->appendRow(m_row);
+    } else {
+        m_rowParents.top()->appendRow(m_row);
+    }
+    m_rowParents.push(m_row.front());
+    m_row.clear();
+}
+
+void StandardItemTreeModelBuilder::startRow()
+{
+    // Push parent in case rows are nested. This is a Noop for the very first row.
+    if (!m_row.isEmpty())
+        pushRow();
+}
+
+void StandardItemTreeModelBuilder::endRow()
+{
+    if (!m_row.isEmpty()) // Push row if no child rows have been encountered
+        pushRow();
+    m_rowParents.pop();
+}
+
+/* Helper visitor base class for recursing over a tree model
+ * (see StandardItemTreeModelBuilder for the scheme). */
+class TreeModelVisitor {
+public:
+    inline virtual void run() { run(QModelIndex()); }
+
+protected:
+    TreeModelVisitor(const QAbstractItemModel *model) : m_model(model) {}
+
+    virtual void rowStarted() {}
+    virtual void handleItem(const QModelIndex &m) = 0;
+    virtual void rowEnded() {}
+
+    const QAbstractItemModel *model() const { return m_model; }
+
+private:
+    void run(const QModelIndex &parent);
+
+    const QAbstractItemModel *m_model;
+};
+
+void TreeModelVisitor::run(const QModelIndex &parent)
+{
+    const int columnCount = m_model->columnCount(parent);
+    const int rowCount = m_model->rowCount(parent);
+    for (int r = 0; r < rowCount; r++) {
+        rowStarted();
+        QModelIndex left;
+        for (int c = 0; c < columnCount; c++) {
+            const QModelIndex index = m_model->index(r, c, parent);
+            handleItem(index);
+            if (!c)
+                left = index;
+        }
+        if (left.isValid())
+            run(left);
+        rowEnded();
+    }
+}
+
+// Visitor writing out a tree model in XML format.
+class XmlWriterTreeModelVisitor : public TreeModelVisitor {
+public:
+    explicit XmlWriterTreeModelVisitor(const QAbstractItemModel *model, QXmlStreamWriter &w);
+
+    virtual void run();
+
+protected:
+    virtual void rowStarted() { m_writer.writeStartElement(QLatin1String(modelRowElementC)); }
+    virtual void handleItem(const QModelIndex &m);
+    virtual void rowEnded() { m_writer.writeEndElement(); }
+
+private:
+    const QString m_modelItemElement;
+    QXmlStreamWriter &m_writer;
+};
+
+XmlWriterTreeModelVisitor::XmlWriterTreeModelVisitor(const QAbstractItemModel *model, QXmlStreamWriter &w) :
+    TreeModelVisitor(model), m_modelItemElement(QLatin1String(modelItemElementC)), m_writer(w)
+{
+}
+
+void XmlWriterTreeModelVisitor::run()
+{
+    m_writer.writeStartElement(QLatin1String(modelElementC));
+    const int columnCount = model()->columnCount();
+    m_writer.writeAttribute(QLatin1String(modelColumnCountAttributeC), QString::number(columnCount));
+   TreeModelVisitor::run();
+    m_writer.writeEndElement();
+}
+
+void XmlWriterTreeModelVisitor::handleItem(const QModelIndex &m)
+{
+    const QString value = m.data(Qt::DisplayRole).toString();
+    if (value.isEmpty()) {
+        m_writer.writeEmptyElement(m_modelItemElement);
+    } else {
+        m_writer.writeTextElement(m_modelItemElement, value);
+    }
+}
+
+// TreeModelVisitor for debugging models
+class DumpTreeModelVisitor : public TreeModelVisitor {
+public:
+    explicit DumpTreeModelVisitor(const QAbstractItemModel *model, QTextStream &s);
+
+protected:
+    virtual void rowStarted();
+    virtual void handleItem(const QModelIndex &m);
+    virtual void rowEnded();
+
+private:
+    QTextStream &m_stream;
+    int m_level;
+};
+
+DumpTreeModelVisitor::DumpTreeModelVisitor(const QAbstractItemModel *model, QTextStream &s) :
+    TreeModelVisitor(model), m_stream(s), m_level(0)
+{
+    m_stream << model->metaObject()->className() << '/' << model->objectName();
+}
+
+void DumpTreeModelVisitor::rowStarted()
+{
+    m_level++;
+    m_stream << '\n' << QString(2 * m_level, QLatin1Char(' '));
+}
+
+void DumpTreeModelVisitor::handleItem(const QModelIndex &m)
+{
+    if (m.column())
+        m_stream << '|';
+    m_stream << '\'' << m.data().toString() << '\'';
+}
+
+void DumpTreeModelVisitor::rowEnded()
+{
+    m_level--;
+}
+
+} // namespace Internal
+} // namespace Debugger
+
+static inline QDebug operator<<(QDebug d, const QAbstractItemModel &model)
+{
+    QString s;
+    QTextStream str(&s);
+    Debugger::Internal::DumpTreeModelVisitor v(&model, str);
+    v.run();
+    qDebug().nospace() << s;
+    return d;
+}
+
+namespace Debugger {
+namespace Internal {
+
+// Visitor building a QStandardItem from a tree model (copy).
+class TreeModelCopyVisitor : public TreeModelVisitor {
+public:
+    explicit TreeModelCopyVisitor(const QAbstractItemModel *source, QStandardItemModel *target) :
+        TreeModelVisitor(source), m_builder(target) {}
+
+protected:
+    virtual void rowStarted() { m_builder.startRow(); }
+    virtual void handleItem(const QModelIndex &m) { m_builder.addItem(m.data().toString()); }
+    virtual void rowEnded() { m_builder.endRow(); }
+
+private:
+    StandardItemTreeModelBuilder m_builder;
+};
+
+/*!
+    \class PinnableToolTipWidget
+
+    A pinnable tooltip that goes from State 'Unpinned' (button showing
+    'Pin') to 'Pinned' (button showing 'Close').
+    It consists of a title toolbar and a vertical main layout.
+*/
+
+PinnableToolTipWidget::PinnableToolTipWidget(QWidget *parent) :
+    QWidget(parent),
+    m_pinState(Unpinned),
+    m_mainVBoxLayout(new QVBoxLayout),
+    m_toolBar(new QToolBar),
+    m_toolButton(new QToolButton),
+    m_menu(new QMenu)
+{
+    setWindowFlags(Qt::ToolTip | Qt::WindowStaysOnTopHint);
+    setAttribute(Qt::WA_DeleteOnClose);
+
+    m_mainVBoxLayout->setSizeConstraint(QLayout::SetFixedSize);
+    m_mainVBoxLayout->setContentsMargins(0, 0, 0, 0);
+
+    const QIcon pinIcon(QLatin1String(":/debugger/images/pin.xpm"));
+    const QList<QSize> pinIconSizes = pinIcon.availableSizes();
+
+    m_toolButton->setIcon(pinIcon);
+    m_menu->addAction(tr("Close All"), this, SIGNAL(closeAllRequested()));
+    m_toolButton->setMenu(m_menu);
+    m_toolButton->setPopupMode(QToolButton::MenuButtonPopup);
+    connect(m_toolButton, SIGNAL(clicked()), this, SLOT(toolButtonClicked()));
+
+    m_toolBar->setProperty("_q_custom_style_disabled", QVariant(true));
+    if (!pinIconSizes.isEmpty())
+        m_toolBar->setIconSize(pinIconSizes.front());
+    m_toolBar->addWidget(m_toolButton);
+
+    m_mainVBoxLayout->addWidget(m_toolBar);
+
+    setLayout(m_mainVBoxLayout);
+}
+
+void PinnableToolTipWidget::addWidget(QWidget *w)
+{
+    w->setFocusPolicy(Qt::NoFocus);
+    m_mainVBoxLayout->addWidget(w);
+}
+
+void PinnableToolTipWidget::addToolBarWidget(QWidget *w)
+{
+    m_toolBar->addWidget(w);
+}
+
+void PinnableToolTipWidget::pin()
+{
+    if (m_pinState == Unpinned) {
+        m_pinState = Pinned;
+        m_toolButton->setIcon(style()->standardIcon(QStyle::SP_DockWidgetCloseButton));
+    }
+}
+
+void PinnableToolTipWidget::toolButtonClicked()
+{
+    switch (m_pinState) {
+    case Unpinned:
+        pin();
+        break;
+    case Pinned:
+        close();
+        break;
+    }
+}
+
+void PinnableToolTipWidget::leaveEvent(QEvent *)
+{
+    if (m_pinState == Unpinned && QApplication::keyboardModifiers() == Qt::NoModifier) {
+        if (debugToolTips)
+            qDebug("ToolTipWidget::leaveEvent: closing %p", this);
+        close();
+    }
+}
+
+/*!
+    \class DebuggerToolTipContext
+
+    File name and position where the tooltip is anchored. Redundant position/line column
+    information is used to detect if the underlying file has been changed
+    on restoring.
+*/
+
+DebuggerToolTipContext::DebuggerToolTipContext() : position(0), line(0), column(0)
+{
+}
+
+DebuggerToolTipContext DebuggerToolTipContext::fromEditor(Core::IEditor *ie, int pos)
+{
+    DebuggerToolTipContext rc;
+    if (const Core::IFile *file = ie->file()) {
+        if (const TextEditor::ITextEditor *te = qobject_cast<const TextEditor::ITextEditor *>(ie)) {
+            rc.fileName = file->fileName();
+            rc.position = pos;
+            te->convertPosition(pos, &rc.line, &rc.column);
+        }
+    }
+    return rc;
+}
+
+QDebug operator<<(QDebug d, const DebuggerToolTipContext &c)
+{
+    QDebug nsp = d.nospace();
+    nsp << c.fileName << '@' << c.line << ',' << c.column << " (" << c.position << ')';
+    if (!c.function.isEmpty())
+        nsp << ' ' << c.function << "()";
+    return d;
+}
+
+/*!
+    \class AbstractDebuggerToolTipWidget
+
+    Base class for a tool tip widget associated with file name
+    and position with functionality to
+    \list
+    \o Save and restore values (session data) in XML form
+    \o Acquire and release the engine: When the debugger stops at a file, all
+       matching tooltips acquire the engine, that is, display the engine data.
+       When continuing or switching away from the frame, the tooltips release the
+       engine, that is, store the data internally and keep displaying them
+       marked as 'previous'.
+    \endlist
+    When restoring the data from a session, all tooltips start in 'released' mode.
+
+    Stored tooltips expire after toolTipsExpiryDays while loading to prevent
+    them accumulating.
+
+    In addition, if the stored line number diverges too much from the current line
+    number in positionShow(), the tooltip is also closed/discarded.
+*/
+
+static inline QString msgReleasedText() { return AbstractDebuggerToolTipWidget::tr("Previous"); }
+
+AbstractDebuggerToolTipWidget::AbstractDebuggerToolTipWidget(QWidget *parent) :
+    PinnableToolTipWidget(parent),
+    m_titleLabel(new QLabel), m_engineAcquired(false),
+    m_creationDate(QDate::currentDate())
+{
+    m_titleLabel->setText(msgReleasedText());
+    addToolBarWidget(m_titleLabel);
+}
+
+bool AbstractDebuggerToolTipWidget::matches(const QString &fileName,
+                                            const QString &engineType,
+                                            const QString &function) const
+{
+    if (fileName.isEmpty() || m_context.fileName != fileName)
+        return false;
+    // Optional.
+    if (!engineType.isEmpty() && engineType != m_engineType)
+        return false;
+    if (function.isEmpty() || m_context.function.isEmpty())
+        return true;
+    return function == m_context.function;
+}
+
+void AbstractDebuggerToolTipWidget::acquireEngine(Debugger::DebuggerEngine *engine)
+{
+    QTC_ASSERT(engine, return; )
+
+
+    if (debugToolTips)
+        qDebug() << this << " acquiring" << engine << m_engineAcquired;
+    if (m_engineAcquired)
+        return;
+    doAcquireEngine(engine);
+    m_engineType = engine->objectName();
+    m_engineAcquired = true;
+    m_titleLabel->setText(QString());
+}
+
+void AbstractDebuggerToolTipWidget::releaseEngine()
+{
+    // Release engine of same type
+    if (!m_engineAcquired)
+        return;
+    if (debugToolTips)
+        qDebug() << "releaseEngine" << this;
+    doReleaseEngine();
+    m_titleLabel->setText(msgReleasedText());
+    m_engineAcquired = false;
+}
+
+bool AbstractDebuggerToolTipWidget::positionShow(const QPlainTextEdit *pe)
+{
+    // Figure out new position of tooltip using the text edit.
+    // If the line changed too much, close this tip.
+    QTC_ASSERT(pe, return false; )
+    QTextCursor cursor(pe->document());
+    cursor.setPosition(m_context.position);
+    const int line = cursor.blockNumber();
+    if (qAbs(m_context.line - line) > 2) {
+        if (debugToolTips)
+            qDebug() << "Closing " << this << " in positionShow() lines "
+                     << line << m_context.line;
+        close();
+        return false;
+    }
+    const QRect plainTextToolTipArea = QRect(pe->cursorRect(cursor).topLeft(), QSize(sizeHint()));
+    const QRect plainTextArea = QRect(QPoint(0, 0), QPoint(pe->width(), pe->height()));
+    const QPoint screenPos = pe->mapToGlobal(plainTextToolTipArea.topLeft());
+    const bool visible = plainTextArea.contains(plainTextToolTipArea);
+    if (debugToolTips)
+        qDebug() << "DebuggerToolTipWidget::positionShow() " << m_context
+                 << " line: " << line << " plainTextPos " << plainTextToolTipArea
+                 << " Area: " << plainTextArea << " Screen pos: "
+                 << screenPos << pe << " visible=" << visible
+                 << " on " << pe->parentWidget()
+                 << " at " << pe->mapToGlobal(QPoint(0, 0));
+
+    if (!visible) {
+        hide();
+        return false;
+    }
+
+    move(screenPos);
+    show();
+    return true;
+}
+
+ // Parse a 'yyyyMMdd' date
+static inline QDate dateFromString(const QString &date)
+{
+    return date.size() == 8 ?
+        QDate(date.left(4).toInt(), date.mid(4, 2).toInt(), date.mid(6, 2).toInt()) :
+        QDate();
+}
+
+AbstractDebuggerToolTipWidget *AbstractDebuggerToolTipWidget::loadSessionData(QXmlStreamReader &r)
+{
+    if (debugToolTips)
+        qDebug() << ">DebuggerToolTipWidget::loadSessionData" << r.tokenString() << r.name();
+    AbstractDebuggerToolTipWidget *rc = AbstractDebuggerToolTipWidget::loadSessionDataI(r);
+    if (debugToolTips)
+        qDebug() << "<DebuggerToolTipWidget::loadSessionData" << r.tokenString() << r.name() << " returns  " << rc;
+    return rc;
+}
+
+AbstractDebuggerToolTipWidget *AbstractDebuggerToolTipWidget::loadSessionDataI(QXmlStreamReader &r)
+{
+    if (!readStartElement(r, toolTipElementC))
+        return 0;
+    const QXmlStreamAttributes attributes = r.attributes();
+    DebuggerToolTipContext context;
+    context.fileName = attributes.value(QLatin1String(fileNameAttributeC)).toString();
+    context.position = attributes.value(QLatin1String(textPositionAttributeC)).toString().toInt();
+    context.line = attributes.value(QLatin1String(textLineAttributeC)).toString().toInt();
+    context.column = attributes.value(QLatin1String(textColumnAttributeC)).toString().toInt();
+    context.function = attributes.value(QLatin1String(functionAttributeC)).toString();
+    const QString className = attributes.value(QLatin1String(toolTipClassAttributeC)).toString();
+    const QString engineType = attributes.value(QLatin1String(engineTypeAttributeC)).toString();
+    const QDate creationDate = dateFromString(attributes.value(QLatin1String(dateAttributeC)).toString());
+    if (!creationDate.isValid() || creationDate.daysTo(QDate::currentDate()) >  toolTipsExpiryDays) {
+        if (debugToolTips)
+            qDebug() << "Expiring tooltip " << context.fileName << '@' << context.position << " from " << creationDate;
+        r.readElementText(QXmlStreamReader::SkipChildElements); // Skip
+        return 0;
+    }
+    if (debugToolTips)
+        qDebug() << "Creating tooltip " << context <<  " from " << creationDate;
+    AbstractDebuggerToolTipWidget *rc = 0;
+    if (className == "Debugger::Internal::DebuggerTreeViewToolTipWidget")
+        rc = new DebuggerTreeViewToolTipWidget;
+    if (rc) {
+        rc->setContext(context);
+        rc->setEngineType(engineType);
+        rc->doLoadSessionData(r);
+        rc->setCreationDate(creationDate);
+        rc->pin();
+    } else {
+        qWarning("Unable to create debugger tool tip widget of class %s", qPrintable(className));
+        r.readElementText(QXmlStreamReader::SkipChildElements); // Skip
+    }
+    return rc;
+}
+
+void AbstractDebuggerToolTipWidget::saveSessionData(QXmlStreamWriter &w) const
+{
+    w.writeStartElement(QLatin1String(toolTipElementC));
+    QXmlStreamAttributes attributes;
+    attributes.append(QLatin1String(toolTipClassAttributeC), QString::fromAscii(metaObject()->className()));
+    attributes.append(QLatin1String(fileNameAttributeC), m_context.fileName);
+    if (!m_context.function.isEmpty())
+        attributes.append(QLatin1String(functionAttributeC), m_context.function);
+    attributes.append(QLatin1String(textPositionAttributeC), QString::number(m_context.position));
+    attributes.append(QLatin1String(textLineAttributeC), QString::number(m_context.line));
+    attributes.append(QLatin1String(textColumnAttributeC), QString::number(m_context.column));
+    attributes.append(QLatin1String(dateAttributeC), m_creationDate.toString(QLatin1String("yyyyMMdd")));
+    if (!m_engineType.isEmpty())
+        attributes.append(QLatin1String(engineTypeAttributeC), m_engineType);
+    w.writeAttributes(attributes);
+    doSaveSessionData(w);
+    w.writeEndElement();
+}
+
+// Model for tooltips filtering a local variable using the locals model,
+// taking the expression.
+class DebuggerToolTipExpressionFilterModel : public QSortFilterProxyModel
+{
+public:
+    explicit DebuggerToolTipExpressionFilterModel(QAbstractItemModel *model, const QString &exp, QObject *parent = 0);
+    virtual bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const;
+
+private:
+    const QString m_expression;
+};
+
+DebuggerToolTipExpressionFilterModel::DebuggerToolTipExpressionFilterModel(QAbstractItemModel *model,
+                                                                           const QString &exp,
+                                                                           QObject *parent) :
+    QSortFilterProxyModel(parent), m_expression(exp)
+{
+    setSourceModel(model);
+}
+
+bool DebuggerToolTipExpressionFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
+{
+    // Match on expression for top level, else pass through.
+    if (sourceParent.isValid())
+        return true;
+    const QModelIndex expIndex = sourceModel()->index(sourceRow, 0, sourceParent);
+    return expIndex.data().toString() == m_expression;
+}
+
+/*!
+    \class DebuggerToolTipTreeView
+
+    A treeview that adapts its size to the model contents (also while expanding)
+    to be used within DebuggerTreeViewToolTipWidget.
+
+*/
+
+DebuggerToolTipTreeView::DebuggerToolTipTreeView(QWidget *parent) :
+    QTreeView(parent)
+{
+    setHeaderHidden(true);
+
+    setUniformRowHeights(true);
+    setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+    setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+
+    connect(this, SIGNAL(collapsed(QModelIndex)), this, SLOT(computeSize()),
+        Qt::QueuedConnection);
+    connect(this, SIGNAL(expanded(QModelIndex)), this, SLOT(computeSize()),
+        Qt::QueuedConnection);
+}
+
+QAbstractItemModel *DebuggerToolTipTreeView::swapModel(QAbstractItemModel *newModel)
+{
+    QAbstractItemModel *previousModel = model();
+    if (previousModel != newModel) {
+        if (previousModel)
+            previousModel->disconnect(SIGNAL(rowsInserted(QModelIndex,int,int)), this);
+        setModel(newModel);
+        connect(newModel, SIGNAL(rowsInserted(QModelIndex,int,int)),
+                this, SLOT(computeSize()), Qt::QueuedConnection);
+        computeSize();
+    }
+    return previousModel;
+}
+
+int DebuggerToolTipTreeView::computeHeight(const QModelIndex &index) const
+{
+    int s = rowHeight(index);
+    const int rowCount = model()->rowCount(index);
+    for (int i = 0; i < rowCount; ++i)
+        s += computeHeight(model()->index(i, 0, index));
+    return s;
+}
+
+void DebuggerToolTipTreeView::computeSize()
+{
+    int columns = 30; // Decoration
+    int rows = 0;
+    bool rootDecorated = false;
+
+    if (model()) {
+        const int columnCount = model()->columnCount();
+        rootDecorated = model()->rowCount() > 0;
+        if (rootDecorated)
+        for (int i = 0; i < columnCount; ++i) {
+            resizeColumnToContents(i);
+            columns += sizeHintForColumn(i);
+        }
+        if (columns < 100)
+            columns = 100; // Prevent toolbar from shrinking when displaying 'Previous'
+        rows += computeHeight(QModelIndex());
+
+        // Fit tooltip to screen, showing/hiding scrollbars as needed.
+        // Add a bit of space to account for tooltip border, and not
+        // touch the border of the screen.
+        QPoint pos(x(), y());
+        QRect desktopRect = QApplication::desktop()->availableGeometry(pos);
+        const int maxWidth = desktopRect.right() - pos.x() - 5 - 5;
+        const int maxHeight = desktopRect.bottom() - pos.y() - 5 - 5;
+
+        if (columns > maxWidth)
+            rows += horizontalScrollBar()->height();
+
+        if (rows > maxHeight) {
+            setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
+            rows = maxHeight;
+            columns += verticalScrollBar()->width();
+        } else {
+            setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+        }
+
+        if (columns > maxWidth) {
+            setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
+            columns = maxWidth;
+        } else {
+            setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+        }
+    }
+
+    m_size = QSize(columns + 5, rows + 5);
+    setMinimumSize(m_size);
+    setMaximumSize(m_size);
+    setRootIsDecorated(rootDecorated);
+}
+
+/*!
+    \class DebuggerTreeViewToolTipWidget
+
+    Tool tip widget for tree views with functionality to save/restore tree
+    model contents to XML.
+    With the engine acquired, it sets a filter model (by expression) on
+    one of the engine's models (debuggerModel).
+    On release, it serializes and restores the data to a QStandardItemModel
+    (defaultModel) and displays that.
+
+*/
+
+DebuggerTreeViewToolTipWidget::DebuggerTreeViewToolTipWidget(QWidget *parent) :
+    AbstractDebuggerToolTipWidget(parent),
+    m_debuggerModel(TooltipsWatch),
+    m_treeView(new DebuggerToolTipTreeView),
+    m_defaultModel(new QStandardItemModel(this))
+{
+    addWidget(m_treeView);
+}
+
+void DebuggerTreeViewToolTipWidget::doAcquireEngine(Debugger::DebuggerEngine *engine)
+{
+    // Create a filter model on the debugger's model and switch to it.
+    QAbstractItemModel *model = 0;
+    switch (m_debuggerModel) {
+    case LocalsWatch:
+        model = engine->localsModel();
+        break;
+    case WatchersWatch:
+        model = engine->watchersModel();
+        break;
+    case TooltipsWatch:
+        model = engine->toolTipsModel();
+        break;
+    }
+    QTC_ASSERT(model, return ;)
+    DebuggerToolTipExpressionFilterModel *filterModel =
+            new DebuggerToolTipExpressionFilterModel(model, m_expression);
+    m_treeView->swapModel(filterModel);
+}
+
+void DebuggerTreeViewToolTipWidget::doReleaseEngine()
+{
+    // Save data to stream and restore to the  m_defaultModel (QStandardItemModel)
+    m_defaultModel->removeRows(0, m_defaultModel->rowCount());
+    if (const QAbstractItemModel *model = m_treeView->model()) {
+        TreeModelCopyVisitor v(model, m_defaultModel);
+        v.run();
+    }
+    delete m_treeView->swapModel(m_defaultModel);
+}
+
+void DebuggerTreeViewToolTipWidget::restoreTreeModel(QXmlStreamReader &r, QStandardItemModel *m)
+{
+    StandardItemTreeModelBuilder builder(m);
+    int columnCount = 1;
+    bool withinModel = true;
+    while (withinModel && !r.atEnd()) {
+        const QXmlStreamReader::TokenType token = r.readNext();
+        switch (token) {
+        case QXmlStreamReader::StartElement: {
+            const QStringRef element = r.name();
+            // Root model element with column count.
+            if (element == QLatin1String(modelElementC)) {
+                if (const int cc = r.attributes().value(QLatin1String(modelColumnCountAttributeC)).toString().toInt())
+                    columnCount = cc;
+                m->setColumnCount(columnCount);
+            } else if (element == QLatin1String(modelRowElementC)) {
+                builder.startRow();
+            } else if (element == QLatin1String(modelItemElementC)) {
+                builder.addItem(r.readElementText());
+            }
+        }
+            break; // StartElement
+        case QXmlStreamReader::EndElement: {
+            const QStringRef element = r.name();
+            // Row closing: pop off parent.
+            if (element == QLatin1String(modelRowElementC)) {
+                builder.endRow();
+            } else if (element == QLatin1String(modelElementC)) {
+                withinModel = false;
+            }
+        }
+            break; // EndElement
+        default:
+            break;
+        } // switch
+    } // while
+}
+
+void DebuggerTreeViewToolTipWidget::doSaveSessionData(QXmlStreamWriter &w) const
+{
+    w.writeStartElement(QLatin1String(treeElementC));
+    QXmlStreamAttributes attributes;
+    attributes.append(QLatin1String(treeModelAttributeC), QString::number(m_debuggerModel));
+    attributes.append(QLatin1String(treeExpressionAttributeC), m_expression);
+    w.writeAttributes(attributes);
+    if (QAbstractItemModel *model = m_treeView->model()) {
+        XmlWriterTreeModelVisitor v(model, w);
+        v.run();
+    }
+    w.writeEndElement();
+}
+
+void DebuggerTreeViewToolTipWidget::doLoadSessionData(QXmlStreamReader &r)
+{
+    if (!readStartElement(r, treeElementC))
+        return;
+    // Restore data to default model and show that.
+    const QXmlStreamAttributes attributes = r.attributes();
+    m_debuggerModel = attributes.value(QLatin1String(treeModelAttributeC)).toString().toInt();
+    m_expression = attributes.value(QLatin1String(treeExpressionAttributeC)).toString();
+    if (debugToolTips)
+        qDebug() << "DebuggerTreeViewToolTipWidget::doLoadSessionData() " << m_debuggerModel << m_expression;
+    setObjectName(QLatin1String("DebuggerTreeViewToolTipWidget: ") + m_expression);
+    restoreTreeModel(r, m_defaultModel);
+    r.readNext(); // Skip </tree>
+    m_treeView->swapModel(m_defaultModel);
+}
+
+/*!
+    \class DebuggerToolTipManager
+
+    Manages the tooltip widgets, listens on editor scroll and main window move
+    events and takes care of repositioning the tooltips.
+
+    Listens to editor change and mode change. In debug mode, if there tooltips
+    for the current editor (by file name), position and show them.
+
+    In addition, listen on state and stack frame change of the engine.
+    If a stack frame is activated, have all matching tooltips (by file name)
+    acquire the engine, other release.
+
+*/
+
+DebuggerToolTipManager *DebuggerToolTipManager::m_instance = 0;
+
+DebuggerToolTipManager::DebuggerToolTipManager(QObject *parent) :
+    QObject(parent), m_debugModeActive(false)
+{
+    DebuggerToolTipManager::m_instance = this;
+}
+
+DebuggerToolTipManager::~DebuggerToolTipManager()
+{
+    DebuggerToolTipManager::m_instance = 0;
+}
+
+void DebuggerToolTipManager::registerEngine(DebuggerEngine *engine)
+{
+    connect(engine, SIGNAL(stateChanged(Debugger::DebuggerState)),
+            this, SLOT(slotDebuggerStateChanged(Debugger::DebuggerState)));
+    connect(engine, SIGNAL(stackFrameCompleted()), this, SLOT(slotStackFrameCompleted()));
+}
+
+void DebuggerToolTipManager::add(const QPoint &p, AbstractDebuggerToolTipWidget *toolTipWidget)
+{
+    closeUnpinnedToolTips();
+    toolTipWidget->move(p);
+    toolTipWidget->show();
+    add(toolTipWidget);
+}
+
+void DebuggerToolTipManager::add(AbstractDebuggerToolTipWidget *toolTipWidget)
+{
+    QTC_ASSERT(toolTipWidget->context().isValid(), return; )
+    connect(toolTipWidget, SIGNAL(closeAllRequested()), this, SLOT(closeAllToolTips()));
+    m_tooltips.push_back(toolTipWidget);
+}
+
+DebuggerToolTipManager::DebuggerToolTipWidgetList &DebuggerToolTipManager::purgeClosedToolTips()
+{
+    if (!m_tooltips.isEmpty()) {
+        for (DebuggerToolTipWidgetList::iterator it = m_tooltips.begin(); it != m_tooltips.end() ; ) {
+            if (it->isNull()) {
+                it = m_tooltips.erase(it);
+            } else {
+                ++it;
+            }
+        }
+    }
+    return m_tooltips;
+}
+
+void DebuggerToolTipManager::moveToolTipsBy(const QPoint &distance)
+{
+    foreach (const QPointer<AbstractDebuggerToolTipWidget> &tw, purgeClosedToolTips())
+        if (tw->isVisible())
+            tw->move (tw->pos() + distance);
+}
+
+bool DebuggerToolTipManager::eventFilter(QObject *, QEvent *e)
+{
+    // Move along with parent (toplevel)
+    if (e->type() == QEvent::Move && isActive()) {
+        const QMoveEvent *me = static_cast<const QMoveEvent *>(e);
+        moveToolTipsBy(me->pos() - me->oldPos());
+    }
+    return false;
+}
+
+void DebuggerToolTipManager::sessionAboutToChange()
+{
+    closeAllToolTips();
+}
+
+void DebuggerToolTipManager::loadSessionData()
+{
+    const QString data = debuggerCore()->sessionValue(QLatin1String(sessionSettingsKeyC)).toString();
+    if (data.isEmpty())
+        return;
+    QXmlStreamReader r(data);
+    r.readNextStartElement();
+    if (r.tokenType() != QXmlStreamReader::StartElement || r.name() != QLatin1String(sessionDocumentC))
+        return;
+    const double version = r.attributes().value(QLatin1String(sessionVersionAttributeC)).toString().toDouble();
+    while (!r.atEnd())
+        if (AbstractDebuggerToolTipWidget *tw = AbstractDebuggerToolTipWidget::loadSessionData(r))
+            add(tw);
+
+    if (debugToolTips)
+        qDebug() << "DebuggerToolTipManager::loadSessionData version " << version << " restored " << m_tooltips.size();
+    slotUpdateVisibleToolTips();
+}
+
+void DebuggerToolTipManager::saveSessionData()
+{
+    QString data;
+    if (!purgeClosedToolTips().isEmpty()) {
+        QXmlStreamWriter w(&data);
+        w.writeStartDocument();
+        w.writeStartElement(QLatin1String(sessionDocumentC));
+        w.writeAttribute(QLatin1String(sessionVersionAttributeC), QLatin1String("1.0"));
+        foreach (const QPointer<AbstractDebuggerToolTipWidget> &tw, m_tooltips)
+            tw->saveSessionData(w);
+        w.writeEndDocument();
+    }
+    if (debugToolTips)
+        qDebug() << "DebuggerToolTipManager::saveSessionData" << m_tooltips.size() << data ;
+    debuggerCore()->setSessionValue(QLatin1String(sessionSettingsKeyC), QVariant(data));
+}
+
+void DebuggerToolTipManager::closeAllToolTips()
+{
+    if (debugToolTips)
+        qDebug() << "DebuggerToolTipManager::closeAllToolTips";
+
+    foreach (const QPointer<AbstractDebuggerToolTipWidget> &tw, purgeClosedToolTips())
+        tw->close();
+    m_tooltips.clear();
+}
+
+void DebuggerToolTipManager::closeUnpinnedToolTips()
+{
+    if (debugToolTips)
+        qDebug() << "DebuggerToolTipManager::closeUnpinnedToolTips";
+
+    // Filter out unpinned ones, purge null on that occasion
+    for (DebuggerToolTipWidgetList::iterator it = m_tooltips.begin(); it != m_tooltips.end() ; ) {
+        if (it->isNull()) {
+            it = m_tooltips.erase(it);
+        } else if ((*it)->pinState() == AbstractDebuggerToolTipWidget::Unpinned) {
+            (*it)->close();
+            it = m_tooltips.erase(it);
+        } else {
+            ++it;
+        }
+    }
+}
+
+void DebuggerToolTipManager::hide()
+{
+    foreach (const QPointer<AbstractDebuggerToolTipWidget> &tw, purgeClosedToolTips())
+        tw->hide();
+}
+
+void DebuggerToolTipManager::slotUpdateVisibleToolTips()
+{
+    if (purgeClosedToolTips().isEmpty())
+        return;
+    if (!m_debugModeActive) {
+        hide();
+        return;
+    }
+
+    QString fileName;
+    QPlainTextEdit *plainTextEdit = currentPlainTextEditor(&fileName);
+
+    if (debugToolTips)
+        qDebug() << "DebuggerToolTipManager::slotUpdateVisibleToolTips() " << fileName << sender();
+
+    if (fileName.isEmpty() || !plainTextEdit) {
+        hide();
+        return;
+    }
+
+    // Reposition and show all tooltips of that file.
+    foreach (const QPointer<AbstractDebuggerToolTipWidget> &tw, m_tooltips) {
+        if (tw->fileName() == fileName) {
+            tw->positionShow(plainTextEdit);
+        } else {
+            tw->hide();
+        }
+    }
+}
+
+void DebuggerToolTipManager::slotDebuggerStateChanged(Debugger::DebuggerState state)
+{
+    const QObject *engine = sender();
+    QTC_ASSERT(engine, return; )
+
+    const QString name = engine->objectName();
+    if (debugToolTips)
+        qDebug() << "DebuggerToolTipWidget::debuggerStateChanged"
+                 << engine << Debugger::DebuggerEngine::stateName(state);
+
+    // Release at earliest possible convenience.
+    switch (state) {
+    case InferiorRunRequested:
+    case DebuggerNotReady:
+    case InferiorShutdownRequested:
+    case EngineShutdownRequested:
+    case DebuggerFinished:
+    case EngineShutdownOk:
+        foreach (const QPointer<AbstractDebuggerToolTipWidget> &tw, purgeClosedToolTips())
+            if (tw->engineType() == name)
+                tw->releaseEngine();
+        break;
+    default:
+        break;
+    }
+}
+
+void DebuggerToolTipManager::slotStackFrameCompleted()
+{
+    if (purgeClosedToolTips().isEmpty())
+        return;
+    DebuggerEngine *engine = qobject_cast<DebuggerEngine *>(sender());
+    QTC_ASSERT(engine, return ; )
+
+    // Stack frame changed: All tooltips of that file acquire the engine,
+    // all others release (arguable, this could be more precise?)
+    QString fileName;
+    int lineNumber = 0;
+    // Get the current frame
+    const QString engineName = engine->objectName();
+    QString function;
+    const int index = engine->stackHandler()->currentIndex();
+    if (index >= 0) {
+        const StackFrame frame = engine->stackHandler()->currentFrame();
+        if (frame.usable) {
+            fileName = frame.file;
+            lineNumber = frame.line;
+            function = frame.function;
+        }
+    }
+    if (debugToolTips)
+        qDebug("DebuggerToolTipWidget::slotStackFrameCompleted(%s, %s@%d, %s())",
+               qPrintable(engineName), qPrintable(fileName), lineNumber,
+               qPrintable(function));
+    unsigned acquiredCount = 0;
+    foreach (const QPointer<AbstractDebuggerToolTipWidget> &tw, m_tooltips) {
+        if (tw->matches(fileName, engineName, function)) {
+            tw->acquireEngine(engine);
+            acquiredCount++;
+        } else {
+            tw->releaseEngine();
+        }
+    }
+    slotUpdateVisibleToolTips(); // Move out when stepping in same file.
+    if (debugToolTips)
+        qDebug() << "DebuggerToolTipWidget::slotStackFrameCompleted()"
+                 << engineName << fileName << lineNumber << " acquired " << acquiredCount;
+}
+
+bool DebuggerToolTipManager::debug()
+{
+    return debugToolTips;
+}
+
+void DebuggerToolTipManager::slotEditorOpened(Core::IEditor *e)
+{
+    // Move tooltip along when scrolled.
+    if (QPlainTextEdit *plainTextEdit = plainTextEditor(e)) {
+        connect(plainTextEdit->verticalScrollBar(), SIGNAL(valueChanged(int)),
+                this, SLOT(slotUpdateVisibleToolTips()));
+    }
+}
+
+void DebuggerToolTipManager::debugModeEntered()
+{
+    if (debugToolTips)
+        qDebug("DebuggerToolTipManager::debugModeEntered");
+
+    // Hook up all signals in debug mode.
+    if (!m_debugModeActive) {
+        m_debugModeActive = true;
+        QWidget *topLevel = Core::ICore::instance()->mainWindow()->topLevelWidget();
+        topLevel->installEventFilter(this);
+        Core::EditorManager *em = Core::EditorManager::instance();
+        connect(em, SIGNAL(currentEditorChanged(Core::IEditor*)),
+                this, SLOT(slotUpdateVisibleToolTips()));
+        connect(em, SIGNAL(editorOpened(Core::IEditor*)),
+                this, SLOT(slotEditorOpened(Core::IEditor*)));
+        foreach (Core::IEditor *e, em->openedEditors())
+            slotEditorOpened(e);
+        // Position tooltips delayed once all the editor placeholder layouting is done.
+        if (!m_tooltips.isEmpty())
+            QTimer::singleShot(0, this, SLOT(slotUpdateVisibleToolTips()));
+    }
+}
+
+void DebuggerToolTipManager::leavingDebugMode()
+{
+    if (debugToolTips)
+            qDebug("DebuggerToolTipManager::leavingDebugMode");
+
+    // Remove all signals in debug mode.
+    if (m_debugModeActive) {
+        m_debugModeActive = false;
+        hide();
+        QWidget *topLevel = Core::ICore::instance()->mainWindow()->topLevelWidget();
+        topLevel->removeEventFilter(this);
+        Core::EditorManager *em = Core::EditorManager::instance();
+        foreach (Core::IEditor *e, em->openedEditors())
+            if (QPlainTextEdit *plainTextEdit = plainTextEditor(e))
+                plainTextEdit->verticalScrollBar()->disconnect(this);
+        em->disconnect(this);
+    }
+}
+
+QStringList DebuggerToolTipManager::treeWidgetExpressions(const QString &fileName,
+                                                          const QString &engineType,
+                                                          const QString &function) const
+{
+    QStringList rc;
+    foreach (const QPointer<AbstractDebuggerToolTipWidget> &tw, m_tooltips)
+        if (!tw.isNull() && tw->matches(fileName, engineType, function))
+            if (const DebuggerTreeViewToolTipWidget *ttw = qobject_cast<const DebuggerTreeViewToolTipWidget *>(tw.data()))
+                rc.push_back(ttw->expression());
+    if (debugToolTips)
+        qDebug() << "DebuggerToolTipManager::treeWidgetExpressions"
+                 << fileName << engineType << function << rc;
+    return rc;
+}
+
+} // namespace Internal
+} // namespace Debugger
diff --git a/src/plugins/debugger/debuggertooltipmanager.h b/src/plugins/debugger/debuggertooltipmanager.h
new file mode 100644
index 00000000000..c50dd02d145
--- /dev/null
+++ b/src/plugins/debugger/debuggertooltipmanager.h
@@ -0,0 +1,278 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** 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.
+**
+**************************************************************************/
+
+#ifndef DEBUGGER_INTERNAL_DEBUGGERTOOLTIPMANAGER_H
+#define DEBUGGER_INTERNAL_DEBUGGERTOOLTIPMANAGER_H
+
+#include "debuggerconstants.h"
+
+#include <QtGui/QTreeView>
+
+#include <QtCore/QPointer>
+#include <QtCore/QList>
+#include <QtCore/QXmlStreamWriter>
+#include <QtCore/QXmlStreamReader>
+#include <QtCore/QDate>
+
+QT_BEGIN_NAMESPACE
+class QVBoxLayout;
+class QToolButton;
+class QSortFilterProxyModel;
+class QStandardItemModel;
+class QPlainTextEdit;
+class QLabel;
+class QToolBar;
+class QMenu;
+class QDebug;
+QT_END_NAMESPACE
+
+namespace Core {
+class IEditor;
+class IMode;
+}
+
+namespace Debugger {
+class DebuggerEngine;
+
+namespace Internal {
+
+class PinnableToolTipWidget : public QWidget
+{
+    Q_OBJECT
+
+public:
+    enum PinState
+    {
+        Unpinned,
+        Pinned
+    };
+
+    explicit PinnableToolTipWidget(QWidget *parent = 0);
+    PinState pinState() const  { return m_pinState; }
+
+    void addWidget(QWidget *w);
+    void addToolBarWidget(QWidget *w);
+
+public slots:
+    void pin();
+
+signals:
+    void closeAllRequested();
+
+protected:
+    virtual void leaveEvent(QEvent *ev);
+
+private slots:
+    void toolButtonClicked();
+
+private:
+    PinState m_pinState;
+    QVBoxLayout *m_mainVBoxLayout;
+    QToolBar *m_toolBar;
+    QToolButton *m_toolButton;
+    QMenu *m_menu;
+};
+
+class DebuggerToolTipContext
+{
+public:
+    DebuggerToolTipContext();
+    static DebuggerToolTipContext fromEditor(Core::IEditor *ed, int pos);
+    bool isValid() const { return !fileName.isEmpty(); }
+
+    QString fileName;
+    int position;
+    int line;
+    int column;
+    QString function; //!< Optional function. This must be set by the engine as it is language-specific.
+};
+
+QDebug operator<<(QDebug, const DebuggerToolTipContext &);
+
+class AbstractDebuggerToolTipWidget : public PinnableToolTipWidget
+{
+    Q_OBJECT
+
+public:
+    explicit AbstractDebuggerToolTipWidget(QWidget *parent = 0);
+    bool engineAcquired() const { return m_engineAcquired; }
+
+    QString fileName() const { return m_context.fileName; }
+    QString function() const { return m_context.function; }
+    int position() const { return m_context.position; }
+    // Check for a match at position.
+    bool matches(const QString &fileName,
+                 const QString &engineType = QString(),
+                 const QString &function= QString()) const;
+
+    const DebuggerToolTipContext &context() const { return m_context; }
+    void setContext(const DebuggerToolTipContext &c) { m_context = c; }
+
+    QString engineType() const { return m_engineType; }
+    void setEngineType(const QString &e) { m_engineType = e; }
+
+    QDate creationDate() const { return m_creationDate; }
+    void setCreationDate(const QDate &d) { m_creationDate = d; }
+
+    static AbstractDebuggerToolTipWidget *loadSessionData(QXmlStreamReader &r);
+
+public slots:
+    void saveSessionData(QXmlStreamWriter &w) const;
+
+    void acquireEngine(Debugger::DebuggerEngine *engine);
+    void releaseEngine();
+    bool positionShow(const QPlainTextEdit *pe);
+
+protected:
+    virtual void doAcquireEngine(Debugger::DebuggerEngine *engine) = 0;
+    virtual void doReleaseEngine() = 0;
+    virtual void doSaveSessionData(QXmlStreamWriter &w) const = 0;
+    virtual void doLoadSessionData(QXmlStreamReader &r) = 0;
+
+private:
+    static AbstractDebuggerToolTipWidget *loadSessionDataI(QXmlStreamReader &r);
+    QLabel *m_titleLabel;
+    bool m_engineAcquired;
+    QString m_engineType;
+    DebuggerToolTipContext m_context;
+    QDate m_creationDate;
+};
+
+class DebuggerToolTipTreeView : public QTreeView
+{
+    Q_OBJECT
+
+public:
+    explicit DebuggerToolTipTreeView(QWidget *parent = 0);
+
+    QAbstractItemModel *swapModel(QAbstractItemModel *model);
+
+    QSize sizeHint() const { return m_size; }
+
+    int computeHeight(const QModelIndex &index) const;
+
+public slots:
+    void computeSize();
+
+private:
+    void init(QAbstractItemModel *model);
+
+    QSize m_size;
+};
+
+class DebuggerTreeViewToolTipWidget : public AbstractDebuggerToolTipWidget
+{
+    Q_OBJECT
+
+public:
+    explicit DebuggerTreeViewToolTipWidget(QWidget *parent = 0);
+
+    int debuggerModel() const { return m_debuggerModel; }
+    void setDebuggerModel(int m) { m_debuggerModel = m; }
+    QString expression() const { return m_expression; }
+    void setExpression(const QString &e) { m_expression = e; }
+
+protected:
+    virtual void doAcquireEngine(Debugger::DebuggerEngine *engine);
+    virtual void doReleaseEngine();
+    virtual void doSaveSessionData(QXmlStreamWriter &w) const;
+    virtual void doLoadSessionData(QXmlStreamReader &r);
+
+private:
+    static void restoreTreeModel(QXmlStreamReader &r, QStandardItemModel *m);
+
+    int m_debuggerModel;
+    QString m_expression;
+
+    DebuggerToolTipTreeView *m_treeView;
+    QStandardItemModel *m_defaultModel;
+};
+
+class DebuggerToolTipManager : public QObject
+{
+    Q_OBJECT
+public:
+    explicit DebuggerToolTipManager(QObject *parent = 0);
+    virtual ~DebuggerToolTipManager();
+
+    static DebuggerToolTipManager *instance() { return m_instance; }
+    void registerEngine(DebuggerEngine *engine);
+
+    // Collect all expressions of DebuggerTreeViewToolTipWidget
+    QStringList treeWidgetExpressions(const QString &fileName,
+                                      const QString &engineType = QString(),
+                                      const QString &function= QString()) const;
+
+    void add(const QPoint &p, AbstractDebuggerToolTipWidget *w);
+
+    virtual bool eventFilter(QObject *, QEvent *);
+
+    static bool debug();
+
+signals:
+
+public slots:
+    void debugModeEntered();
+    void leavingDebugMode();
+    void sessionAboutToChange();
+    void loadSessionData();
+    void saveSessionData();
+    void closeAllToolTips();
+    void closeUnpinnedToolTips();
+    void hide();
+
+private slots:
+    void slotUpdateVisibleToolTips();
+    void slotDebuggerStateChanged(Debugger::DebuggerState);
+    void slotStackFrameCompleted();
+    void slotEditorOpened(Core::IEditor *);
+
+private:
+    typedef QList<QPointer<AbstractDebuggerToolTipWidget> > DebuggerToolTipWidgetList;
+
+    inline bool isActive() const { return !m_tooltips.isEmpty(); }
+    void add(AbstractDebuggerToolTipWidget *toolTipWidget);
+    void moveToolTipsBy(const QPoint &distance);
+    // Purge out closed (null) tooltips and return list for convenience
+    DebuggerToolTipWidgetList &purgeClosedToolTips();
+
+    static DebuggerToolTipManager *m_instance;
+
+    DebuggerToolTipWidgetList m_tooltips;
+    bool m_debugModeActive;
+};
+
+} // namespace Internal
+} // namespace Debugger
+
+#endif // DEBUGGER_INTERNAL_DEBUGGERTOOLTIPMANAGER_H
diff --git a/src/plugins/debugger/gdb/classicgdbengine.cpp b/src/plugins/debugger/gdb/classicgdbengine.cpp
index 6e12e6b665f..ad9bad466ba 100644
--- a/src/plugins/debugger/gdb/classicgdbengine.cpp
+++ b/src/plugins/debugger/gdb/classicgdbengine.cpp
@@ -119,7 +119,7 @@ void GdbEngine::updateLocalsClassic(const QVariant &cookie)
 
     //PENDING_DEBUG("\nRESET PENDING");
     //m_toolTipCache.clear();
-    m_toolTipExpression.clear();
+    clearToolTip();
     watchHandler()->beginCycle();
 
     QByteArray level = QByteArray::number(currentFrame());
diff --git a/src/plugins/debugger/gdb/gdbengine.cpp b/src/plugins/debugger/gdb/gdbengine.cpp
index a4054c79c04..7ff42ed1aa5 100644
--- a/src/plugins/debugger/gdb/gdbengine.cpp
+++ b/src/plugins/debugger/gdb/gdbengine.cpp
@@ -52,7 +52,7 @@
 #include "debuggerplugin.h"
 #include "debuggerrunner.h"
 #include "debuggerstringutils.h"
-#include "debuggertooltip.h"
+#include "debuggertooltipmanager.h"
 #include "disassembleragent.h"
 #include "gdbmi.h"
 #include "gdboptionspage.h"
@@ -74,6 +74,7 @@
 #include "logwindow.h"
 
 #include <coreplugin/icore.h>
+#include <coreplugin/ifile.h>
 #include <projectexplorer/toolchain.h>
 #include <texteditor/itexteditor.h>
 #include <utils/qtcassert.h>
@@ -101,10 +102,18 @@
 #endif
 #include <ctype.h>
 
-
 namespace Debugger {
 namespace Internal {
 
+class GdbToolTipContext : public DebuggerToolTipContext
+{
+public:
+    GdbToolTipContext(const DebuggerToolTipContext &c) : DebuggerToolTipContext(c) {}
+
+    QPoint mousePosition;
+    QString expression;
+};
+
 static const char winPythonVersionC[] = "python2.5";
 
 enum { debugPending = 0 };
@@ -1103,7 +1112,7 @@ void GdbEngine::handleQuerySources(const GdbResponse &response)
 void GdbEngine::handleExecuteJumpToLine(const GdbResponse &response)
 {
     if (response.resultClass == GdbResultRunning) {
-        notifyInferiorRunOk();
+        doNotifyInferiorRunOk();
         // All is fine. Waiting for the temporary breakpoint to be hit.
     } else if (response.resultClass == GdbResultDone) {
         // This happens on old gdb. Trigger the effect of a '*stopped'.
@@ -1116,7 +1125,7 @@ void GdbEngine::handleExecuteJumpToLine(const GdbResponse &response)
 void GdbEngine::handleExecuteRunToLine(const GdbResponse &response)
 {
     if (response.resultClass == GdbResultRunning) {
-        notifyInferiorRunOk();
+        doNotifyInferiorRunOk();
         // All is fine. Waiting for the temporary breakpoint to be hit.
     } else if (response.resultClass == GdbResultDone) {
         // This happens on old gdb. Trigger the effect of a '*stopped'.
@@ -1275,7 +1284,7 @@ void GdbEngine::handleStopResponse(const GdbMi &data)
         // be handled in the result handler.
         // -- or --
         // *stopped arriving earlier than ^done response to an -exec-step
-        notifyInferiorRunOk();
+        doNotifyInferiorRunOk();
         notifyInferiorSpontaneousStop();
     } else if (state() == InferiorStopOk && isQmlStepBreakpoint2(bkptno)) {
         // That's expected.
@@ -1630,7 +1639,7 @@ void GdbEngine::handleExecuteContinue(const GdbResponse &response)
 {
     QTC_ASSERT(state() == InferiorRunRequested, qDebug() << state());
     if (response.resultClass == GdbResultRunning) {
-        notifyInferiorRunOk();
+        doNotifyInferiorRunOk();
         return;
     }
     QByteArray msg = response.data.findChild("msg").data();
@@ -1871,6 +1880,12 @@ void GdbEngine::continueInferiorInternal()
     postCommand("-exec-continue", RunRequest, CB(handleExecuteContinue));
 }
 
+void GdbEngine::doNotifyInferiorRunOk()
+{
+    clearToolTip();
+    notifyInferiorRunOk();
+}
+
 void GdbEngine::autoContinueInferior()
 {
     resetLocation();
@@ -1909,7 +1924,7 @@ void GdbEngine::handleExecuteStep(const GdbResponse &response)
     }
     QTC_ASSERT(state() == InferiorRunRequested, qDebug() << state());
     if (response.resultClass == GdbResultRunning) {
-        notifyInferiorRunOk();
+        doNotifyInferiorRunOk();
         return;
     }
     QByteArray msg = response.data.findChild("msg").data();
@@ -1972,7 +1987,7 @@ void GdbEngine::handleExecuteNext(const GdbResponse &response)
     }
     QTC_ASSERT(state() == InferiorRunRequested, qDebug() << state());
     if (response.resultClass == GdbResultRunning) {
-        notifyInferiorRunOk();
+        doNotifyInferiorRunOk();
         return;
     }
     QTC_ASSERT(state() == InferiorStopOk, qDebug() << state());
@@ -3366,7 +3381,12 @@ bool GdbEngine::supportsThreads() const
 
 bool GdbEngine::showToolTip()
 {
-    const QByteArray iname = tooltipIName(m_toolTipExpression);
+    if (m_toolTipContext.isNull())
+        return false;
+    const QString expression = m_toolTipContext->expression;
+    const QByteArray iname = tooltipIName(m_toolTipContext->expression);
+    if (DebuggerToolTipManager::debug())
+        qDebug() << "GdbEngine::showToolTip " << expression << iname << (*m_toolTipContext);
 
     if (!debuggerCore()->boolSetting(UseToolTipsInMainEditor)) {
         watchHandler()->removeData(iname);
@@ -3376,15 +3396,29 @@ bool GdbEngine::showToolTip()
     const QModelIndex index = watchHandler()->itemIndex(iname);
     if (!index.isValid()) {
         watchHandler()->removeData(iname);
-        hideDebuggerToolTip();
         return false;
     }
-    showDebuggerToolTip(m_toolTipPos, index);
+    DebuggerTreeViewToolTipWidget *tw = new DebuggerTreeViewToolTipWidget;
+    tw->setDebuggerModel(TooltipsWatch);
+    tw->setExpression(expression);
+    tw->setContext(*m_toolTipContext);
+    tw->acquireEngine(this);
+    DebuggerToolTipManager::instance()->add(m_toolTipContext->mousePosition, tw);
     return true;
 }
 
+QString GdbEngine::tooltipExpression() const
+{
+    return m_toolTipContext.isNull() ? QString() : m_toolTipContext->expression;
+}
+
+void GdbEngine::clearToolTip()
+{
+    m_toolTipContext.reset();
+}
+
 void GdbEngine::setToolTipExpression(const QPoint &mousePos,
-    TextEditor::ITextEditor *editor, int cursorPos)
+    TextEditor::ITextEditor *editor, const DebuggerToolTipContext &contextIn)
 {
     if (state() != InferiorStopOk || !isCppEditor(editor)) {
         //qDebug() << "SUPPRESSING DEBUGGER TOOLTIP, INFERIOR NOT STOPPED "
@@ -3392,9 +3426,13 @@ void GdbEngine::setToolTipExpression(const QPoint &mousePos,
         return;
     }
 
-    m_toolTipPos = mousePos;
+    DebuggerToolTipContext context = contextIn;
     int line, column;
-    QString exp = cppExpressionAt(editor, cursorPos, &line, &column);
+    QString exp = cppExpressionAt(editor, context.position, &line, &column, &context.function);
+    if (DebuggerToolTipManager::debug())
+        qDebug() << "GdbEngine::setToolTipExpression1 " << exp << context;
+    if (exp.isEmpty())
+        return;
 
     // Extract the first identifier, everything else is considered
     // too dangerous.
@@ -3414,36 +3452,12 @@ void GdbEngine::setToolTipExpression(const QPoint &mousePos,
     }
     exp = exp.mid(pos1, pos2 - pos1);
 
-    if (!exp.isEmpty() && exp == m_toolTipExpression) {
-        showToolTip();
+    if (exp.isEmpty() || exp.startsWith(_c('#')) || !hasLetterOrNumber(exp) || isKeyWord(exp))
         return;
-    }
-
-    m_toolTipExpression = exp;
-
-    // FIXME: enable caching
-    //if (showToolTip())
-    //    return;
 
-    if (exp.isEmpty() || exp.startsWith(_c('#')))  {
-        //QToolTip::hideText();
-        return;
-    }
-
-    if (!hasLetterOrNumber(exp)) {
-        //QToolTip::showText(m_toolTipPos,
-        //    tr("'%1' contains no identifier").arg(exp));
-        return;
-    }
-
-    if (isKeyWord(exp))
+    if (exp.startsWith(_c('"')) && exp.endsWith(_c('"')))
         return;
 
-    if (exp.startsWith(_c('"')) && exp.endsWith(_c('"')))  {
-        //QToolTip::showText(m_toolTipPos, tr("String literal %1").arg(exp));
-        return;
-    }
-
     if (exp.startsWith(__("++")) || exp.startsWith(__("--")))
         exp = exp.mid(2);
 
@@ -3453,29 +3467,19 @@ void GdbEngine::setToolTipExpression(const QPoint &mousePos,
     if (exp.startsWith(_c('<')) || exp.startsWith(_c('[')))
         return;
 
-    if (hasSideEffects(exp)) {
-        //QToolTip::showText(m_toolTipPos,
-        //    tr("Cowardly refusing to evaluate expression '%1' "
-        //       "with potential side effects").arg(exp));
+    if (hasSideEffects(exp) || exp.isEmpty())
         return;
-    }
 
-    // Gdb crashes when creating a variable object with the name
-    // of the type of 'this'
-/*
-    for (int i = 0; i != m_currentLocals.childCount(); ++i) {
-        if (m_currentLocals.childAt(i).exp == "this") {
-            qDebug() << "THIS IN ROW" << i;
-            if (m_currentLocals.childAt(i).type.startsWith(exp)) {
-                QToolTip::showText(m_toolTipPos,
-                    tr("%1: type of current 'this'").arg(exp));
-                qDebug() << " TOOLTIP CRASH SUPPRESSED";
-                return;
-            }
-            break;
-        }
+    if (!m_toolTipContext.isNull() && m_toolTipContext->expression == exp) {
+        showToolTip();
+        return;
     }
-*/
+
+    m_toolTipContext.reset(new GdbToolTipContext(context));
+    m_toolTipContext->mousePosition = mousePos;
+    m_toolTipContext->expression = exp;
+    if (DebuggerToolTipManager::debug())
+        qDebug() << "GdbEngine::setToolTipExpression2 " << exp << (*m_toolTipContext);
 
     if (isSynchronous()) {
         updateLocals(QVariant());
@@ -4587,13 +4591,6 @@ void GdbEngine::resetCommandQueue()
     }
 }
 
-void GdbEngine::removeTooltip()
-{
-    m_toolTipExpression.clear();
-    m_toolTipPos = QPoint();
-    DebuggerEngine::removeTooltip();
-}
-
 void GdbEngine::handleRemoteSetupDone(int gdbServerPort, int qmlPort)
 {
     m_gdbAdapter->handleRemoteSetupDone(gdbServerPort, qmlPort);
diff --git a/src/plugins/debugger/gdb/gdbengine.h b/src/plugins/debugger/gdb/gdbengine.h
index d452c74cb1d..3a1af99c3f5 100644
--- a/src/plugins/debugger/gdb/gdbengine.h
+++ b/src/plugins/debugger/gdb/gdbengine.h
@@ -60,6 +60,7 @@ class AbstractGdbAdapter;
 class AbstractGdbProcess;
 class GdbResponse;
 class GdbMi;
+class GdbToolTipContext;
 
 class WatchData;
 class DisassemblerAgentCookie;
@@ -325,6 +326,7 @@ private: ////////// Inferior Management //////////
     void executeNextI();
 
     void continueInferiorInternal();
+    void doNotifyInferiorRunOk();
     void autoContinueInferior();
     void continueInferior();
     void interruptInferior();
@@ -474,8 +476,7 @@ private: ////////// View & Data Stuff //////////
     // Watch specific stuff
     //
     virtual void setToolTipExpression(const QPoint &mousePos,
-        TextEditor::ITextEditor *editor, int cursorPos);
-
+        TextEditor::ITextEditor *editor, const DebuggerToolTipContext &);
     virtual void assignValueInDebugger(const WatchData *data,
         const QString &expr, const QVariant &value);
 
@@ -555,10 +556,11 @@ private: ////////// View & Data Stuff //////////
     AbstractGdbProcess *gdbProc() const;
     void showExecutionError(const QString &message);
 
-    void removeTooltip();
     static QByteArray tooltipIName(const QString &exp);
-    QString m_toolTipExpression;
-    QPoint m_toolTipPos;
+    QString tooltipExpression() const;
+    void clearToolTip();
+    QScopedPointer<GdbToolTipContext> m_toolTipContext;
+
 
     // For short-circuiting stack and thread list evaluation.
     bool m_stackNeeded;
diff --git a/src/plugins/debugger/gdb/pythongdbengine.cpp b/src/plugins/debugger/gdb/pythongdbengine.cpp
index cdf97a364bc..dff647e17c5 100644
--- a/src/plugins/debugger/gdb/pythongdbengine.cpp
+++ b/src/plugins/debugger/gdb/pythongdbengine.cpp
@@ -37,6 +37,7 @@
 #include "debuggeractions.h"
 #include "debuggercore.h"
 #include "debuggerstringutils.h"
+#include "debuggertooltipmanager.h"
 
 #include "breakhandler.h"
 #include "watchhandler.h"
@@ -64,10 +65,21 @@ void GdbEngine::updateLocalsPython(bool tryPartial, const QByteArray &varList)
     expanded += "formats:" + handler->individualFormatRequests();
 
     QByteArray watchers;
-    if (!m_toolTipExpression.isEmpty()) {
-        watchers += m_toolTipExpression.toLatin1();
-        watchers += '#';
-        watchers += tooltipIName(m_toolTipExpression);
+    const QString fileName = stackHandler()->currentFrame().file;
+    const QString function = stackHandler()->currentFrame().function;
+    if (!fileName.isEmpty()) {
+        QStringList expressions =
+            DebuggerToolTipManager::instance()->treeWidgetExpressions(fileName, objectName(), function);
+        const QString currentExpression = tooltipExpression();
+        if (!currentExpression.isEmpty() && !expressions.contains(currentExpression))
+            expressions.push_back(currentExpression);
+        foreach (const QString &te, expressions) {
+            if (!watchers.isEmpty())
+                watchers += "##";
+            watchers += te.toLatin1();
+            watchers += '#';
+            watchers += tooltipIName(te);
+        }
     }
 
     QHash<QByteArray, int> watcherNames = handler->watcherNames();
@@ -120,7 +132,6 @@ void GdbEngine::handleStackFramePython(const GdbResponse &response)
     PRECONDITION;
     if (response.resultClass == GdbResultDone) {
         const bool partial = response.cookie.toBool();
-        Q_UNUSED(partial);
         //qDebug() << "READING " << (partial ? "PARTIAL" : "FULL");
         QByteArray out = response.data.findChild("consolestreamoutput").data();
         while (out.endsWith(' ') || out.endsWith('\n'))
@@ -192,6 +203,8 @@ void GdbEngine::handleStackFramePython(const GdbResponse &response)
             //PENDING_DEBUG("\n\n ....  AND TRIGGERS MODEL UPDATE\n");
             rebuildWatchModel();
         }
+        if (!partial)
+            emit stackFrameCompleted();
     } else {
         showMessage(_("DUMPER FAILED: " + response.toString()));
     }
diff --git a/src/plugins/debugger/images/pin.xpm b/src/plugins/debugger/images/pin.xpm
new file mode 100644
index 00000000000..0759becb67f
--- /dev/null
+++ b/src/plugins/debugger/images/pin.xpm
@@ -0,0 +1,19 @@
+/* XPM */
+static const char * pin_xpm[] = {
+"12 9 7 1",
+"       c None",
+".      c #000000",
+"+      c #515151",
+"@      c #A8A8A8",
+"#      c #A9A9A9",
+"$      c #999999",
+"%      c #696969",
+"     .      ",
+"     ......+",
+"     .@@@@@.",
+"     .#####.",
+"+.....$$$$$.",
+"     .%%%%%.",
+"     .......",
+"     ......+",
+"     .      "};
diff --git a/src/plugins/debugger/pdb/pdbengine.cpp b/src/plugins/debugger/pdb/pdbengine.cpp
index 5f9d8562ee6..a0547b7383e 100644
--- a/src/plugins/debugger/pdb/pdbengine.cpp
+++ b/src/plugins/debugger/pdb/pdbengine.cpp
@@ -41,6 +41,7 @@
 #include "debuggerdialogs.h"
 #include "debuggerplugin.h"
 #include "debuggerstringutils.h"
+#include "debuggertooltipmanager.h"
 
 #include "breakhandler.h"
 #include "moduleshandler.h"
@@ -456,11 +457,10 @@ static QPoint m_toolTipPos;
 static QHash<QString, WatchData> m_toolTipCache;
 
 void PdbEngine::setToolTipExpression(const QPoint &mousePos,
-    TextEditor::ITextEditor *editor, int cursorPos)
+    TextEditor::ITextEditor *editor, const DebuggerToolTipContext &ctx)
 {
     Q_UNUSED(mousePos)
     Q_UNUSED(editor)
-    Q_UNUSED(cursorPos)
 
     if (state() != InferiorStopOk) {
         //SDEBUG("SUPPRESSING DEBUGGER TOOLTIP, INFERIOR NOT STOPPED");
@@ -474,7 +474,7 @@ void PdbEngine::setToolTipExpression(const QPoint &mousePos,
 
     int line;
     int column;
-    QString exp = cppExpressionAt(editor, cursorPos, &line, &column);
+    QString exp = cppExpressionAt(editor, ctx.position, &line, &column);
 
 /*
     if (m_toolTipCache.contains(exp)) {
diff --git a/src/plugins/debugger/pdb/pdbengine.h b/src/plugins/debugger/pdb/pdbengine.h
index db00d408fbd..8da4377e42c 100644
--- a/src/plugins/debugger/pdb/pdbengine.h
+++ b/src/plugins/debugger/pdb/pdbengine.h
@@ -80,7 +80,7 @@ private:
     void shutdownEngine();
 
     void setToolTipExpression(const QPoint &mousePos,
-        TextEditor::ITextEditor *editor, int cursorPos);
+        TextEditor::ITextEditor *editor, const DebuggerToolTipContext &);
 
     void continueInferior();
     void interruptInferior();
diff --git a/src/plugins/debugger/qml/qmlcppengine.cpp b/src/plugins/debugger/qml/qmlcppengine.cpp
index 6326366f10e..f3b31fa08e9 100644
--- a/src/plugins/debugger/qml/qmlcppengine.cpp
+++ b/src/plugins/debugger/qml/qmlcppengine.cpp
@@ -138,9 +138,9 @@ QmlCppEngine::~QmlCppEngine()
 }
 
 void QmlCppEngine::setToolTipExpression(const QPoint & mousePos,
-        TextEditor::ITextEditor *editor, int cursorPos)
+        TextEditor::ITextEditor *editor, const DebuggerToolTipContext &ctx)
 {
-    d->m_activeEngine->setToolTipExpression(mousePos, editor, cursorPos);
+    d->m_activeEngine->setToolTipExpression(mousePos, editor, ctx);
 }
 
 void QmlCppEngine::updateWatchData(const WatchData &data,
diff --git a/src/plugins/debugger/qml/qmlcppengine.h b/src/plugins/debugger/qml/qmlcppengine.h
index 20fb497f053..f0cf10e6fb3 100644
--- a/src/plugins/debugger/qml/qmlcppengine.h
+++ b/src/plugins/debugger/qml/qmlcppengine.h
@@ -19,7 +19,7 @@ public:
     ~QmlCppEngine();
 
     void setToolTipExpression(const QPoint &mousePos,
-        TextEditor::ITextEditor * editor, int cursorPos);
+        TextEditor::ITextEditor * editor, const DebuggerToolTipContext &);
     void updateWatchData(const WatchData &data,
         const WatchUpdateFlags &flags);
 
diff --git a/src/plugins/debugger/qml/qmlengine.cpp b/src/plugins/debugger/qml/qmlengine.cpp
index e97cf49335d..b1a5cbe59d3 100644
--- a/src/plugins/debugger/qml/qmlengine.cpp
+++ b/src/plugins/debugger/qml/qmlengine.cpp
@@ -42,7 +42,7 @@
 #include "debuggermainwindow.h"
 #include "debuggerrunner.h"
 #include "debuggerstringutils.h"
-#include "debuggertooltip.h"
+#include "debuggertooltipmanager.h"
 
 #include "breakhandler.h"
 #include "moduleshandler.h"
@@ -564,11 +564,11 @@ void QmlEngine::requestModuleSymbols(const QString &moduleName)
 //////////////////////////////////////////////////////////////////////
 
 void QmlEngine::setToolTipExpression(const QPoint &mousePos,
-    TextEditor::ITextEditor *editor, int cursorPos)
+    TextEditor::ITextEditor *editor, const DebuggerToolTipContext &ctx)
 {
     // This is processed by QML inspector, which has dependencies to 
     // the qml js editor. Makes life easier.
-    emit tooltipRequested(mousePos, editor, cursorPos);
+    emit tooltipRequested(mousePos, editor, ctx.position);
 }
 
 //////////////////////////////////////////////////////////////////////
diff --git a/src/plugins/debugger/qml/qmlengine.h b/src/plugins/debugger/qml/qmlengine.h
index 4b5cd4832c8..3b900af02ed 100644
--- a/src/plugins/debugger/qml/qmlengine.h
+++ b/src/plugins/debugger/qml/qmlengine.h
@@ -85,7 +85,7 @@ private:
     void shutdownEngine();
 
     void setToolTipExpression(const QPoint &mousePos,
-        TextEditor::ITextEditor *editor, int cursorPos);
+        TextEditor::ITextEditor *editor, const DebuggerToolTipContext &);
 
     void continueInferior();
     void interruptInferior();
diff --git a/src/plugins/debugger/script/scriptengine.cpp b/src/plugins/debugger/script/scriptengine.cpp
index d441e72d27e..6b6a1e33d61 100644
--- a/src/plugins/debugger/script/scriptengine.cpp
+++ b/src/plugins/debugger/script/scriptengine.cpp
@@ -46,6 +46,7 @@
 #include "stackhandler.h"
 #include "watchhandler.h"
 #include "watchutils.h"
+#include "debuggertooltipmanager.h"
 
 #include <utils/qtcassert.h>
 
@@ -512,11 +513,10 @@ static QPoint m_toolTipPos;
 static QHash<QString, WatchData> m_toolTipCache;
 
 void ScriptEngine::setToolTipExpression(const QPoint &mousePos,
-    TextEditor::ITextEditor *editor, int cursorPos)
+    TextEditor::ITextEditor *editor, const DebuggerToolTipContext &ctx)
 {
     Q_UNUSED(mousePos)
     Q_UNUSED(editor)
-    Q_UNUSED(cursorPos)
 
     if (state() != InferiorStopOk) {
         //SDEBUG("SUPPRESSING DEBUGGER TOOLTIP, INFERIOR NOT STOPPED");
@@ -530,7 +530,7 @@ void ScriptEngine::setToolTipExpression(const QPoint &mousePos,
 
     int line;
     int column;
-    QString exp = cppExpressionAt(editor, cursorPos, &line, &column);
+    QString exp = cppExpressionAt(editor, ctx.position, &line, &column);
 
 /*
     if (m_toolTipCache.contains(exp)) {
diff --git a/src/plugins/debugger/script/scriptengine.h b/src/plugins/debugger/script/scriptengine.h
index 93220af84c7..b34ae49b581 100644
--- a/src/plugins/debugger/script/scriptengine.h
+++ b/src/plugins/debugger/script/scriptengine.h
@@ -74,7 +74,7 @@ private:
     void executeNextI();
 
     void setToolTipExpression(const QPoint &mousePos,
-        TextEditor::ITextEditor *editor, int cursorPos);
+        TextEditor::ITextEditor *editor, const DebuggerToolTipContext &);
     void setupEngine();
     void setupInferior();
     void runEngine();
diff --git a/src/plugins/debugger/tcf/tcfengine.cpp b/src/plugins/debugger/tcf/tcfengine.cpp
index f256705279f..a06910f6c10 100644
--- a/src/plugins/debugger/tcf/tcfengine.cpp
+++ b/src/plugins/debugger/tcf/tcfengine.cpp
@@ -535,11 +535,10 @@ static WatchData m_toolTip;
 static QPoint m_toolTipPos;
 static QHash<QString, WatchData> m_toolTipCache;
 
-void TcfEngine::setToolTipExpression(const QPoint &mousePos, TextEditor::ITextEditor *editor, int cursorPos)
+void TcfEngine::setToolTipExpression(const QPoint &mousePos, TextEditor::ITextEditor *editor, const DebuggerToolTipContext &)
 {
     Q_UNUSED(mousePos)
     Q_UNUSED(editor)
-    Q_UNUSED(cursorPos)
 }
 
 //////////////////////////////////////////////////////////////////////
diff --git a/src/plugins/debugger/tcf/tcfengine.h b/src/plugins/debugger/tcf/tcfengine.h
index 3545e8d607b..d3ade0a2e7e 100644
--- a/src/plugins/debugger/tcf/tcfengine.h
+++ b/src/plugins/debugger/tcf/tcfengine.h
@@ -83,7 +83,7 @@ private:
     void shutdownEngine();
 
     void setToolTipExpression(const QPoint &mousePos,
-        TextEditor::ITextEditor *editor, int cursorPos);
+        TextEditor::ITextEditor *editor, const DebuggerToolTipContext &);
 
     void continueInferior();
     void interruptInferior();
diff --git a/src/plugins/debugger/watchhandler.h b/src/plugins/debugger/watchhandler.h
index e7c64ea227d..10d96826ec0 100644
--- a/src/plugins/debugger/watchhandler.h
+++ b/src/plugins/debugger/watchhandler.h
@@ -75,8 +75,8 @@ private:
     virtual ~WatchModel();
 
 public:
-    int rowCount(const QModelIndex &idx) const;
-    int columnCount(const QModelIndex &idx) const;
+    virtual int rowCount(const QModelIndex &idx = QModelIndex()) const;
+    virtual int columnCount(const QModelIndex &idx) const;
 
 private:
     QVariant data(const QModelIndex &index, int role) const;
-- 
GitLab