From 3f0d02ad7f4f3b9873bd16b98cb0d57a904af1ea Mon Sep 17 00:00:00 2001
From: hjk <hjk121@nokiamail.com>
Date: Tue, 15 Apr 2014 18:13:03 +0200
Subject: [PATCH] Debugger: Rework display type selection

The previous index based way was getting too brittle, use enums instead.
Also add a switch between exponential and flat display for floating
point types.

Task-number: QTCREATORBUG-12050
Change-Id: I86addbac5a80e8b79b176c6107b251b466503fe7
Reviewed-by: David Schulz <david.schulz@digia.com>
Reviewed-by: Christian Stenger <christian.stenger@digia.com>
---
 share/qtcreator/debugger/dumper.py           |  28 +-
 src/libs/qtcreatorcdbext/symbolgroupnode.cpp |  12 +-
 src/plugins/debugger/debuggerdialogs.cpp     |   4 +-
 src/plugins/debugger/debuggerdialogs.h       |   4 +-
 src/plugins/debugger/watchhandler.cpp        | 261 ++++++++++++-------
 src/plugins/debugger/watchhandler.h          |  81 +++++-
 src/plugins/debugger/watchwindow.cpp         | 250 ++++++++++--------
 src/plugins/debugger/watchwindow.h           |  18 +-
 8 files changed, 424 insertions(+), 234 deletions(-)

diff --git a/share/qtcreator/debugger/dumper.py b/share/qtcreator/debugger/dumper.py
index 0b19cbaeb0..b7725fbe83 100644
--- a/share/qtcreator/debugger/dumper.py
+++ b/share/qtcreator/debugger/dumper.py
@@ -42,6 +42,19 @@ else:
 verbosity = 0
 verbosity = 1
 
+# Known special formats. Keep in sync with DisplayFormat in watchhandler.h
+KnownDumperFormatBase, \
+Latin1StringFormat, \
+Utf8StringFormat, \
+Local8BitStringFormat, \
+Utf16StringFormat, \
+Ucs4StringFormat, \
+Array10Format, \
+Array100Format, \
+Array1000Format, \
+Array10000Format \
+    = range(100, 110)
+
 def hasPlot():
     fileName = "/usr/bin/gnuplot"
     return os.path.isfile(fileName) and os.access(fileName, os.X_OK)
@@ -805,44 +818,45 @@ class DumperBase:
                         self.putItem(value.dereference())
             return
 
-        if format == 1:
+        if format == Latin1StringFormat:
             # Explicitly requested Latin1 formatting.
             self.putType(typeName)
             self.putValue(self.encodeCharArray(value), Hex2EncodedLatin1)
             self.putNumChild(0)
             return
 
-        if format == 2:
+        if format == Utf8StringFormat:
             # Explicitly requested UTF-8 formatting.
             self.putType(typeName)
             self.putValue(self.encodeCharArray(value), Hex2EncodedUtf8)
             self.putNumChild(0)
             return
 
-        if format == 3:
+        if format == Local8BitStringFormat:
             # Explicitly requested local 8 bit formatting.
             self.putType(typeName)
             self.putValue(self.encodeCharArray(value), Hex2EncodedLocal8Bit)
             self.putNumChild(0)
             return
 
-        if format == 4:
+        if format == Utf16StringFormat:
             # Explicitly requested UTF-16 formatting.
             self.putType(typeName)
             self.putValue(self.encodeChar2Array(value), Hex4EncodedLittleEndian)
             self.putNumChild(0)
             return
 
-        if format == 5:
+        if format == Ucs4StringFormat:
             # Explicitly requested UCS-4 formatting.
             self.putType(typeName)
             self.putValue(self.encodeChar4Array(value), Hex8EncodedLittleEndian)
             self.putNumChild(0)
             return
 
-        if not format is None and format >= 6 and format <= 9:
+        if not format is None \
+            and format >= Array10Format and format <= Array1000Format:
             # Explicitly requested formatting as array of n items.
-            n = (10, 100, 1000, 10000)[format - 6]
+            n = (10, 100, 1000, 10000)[format - Array10Format]
             self.putType(typeName)
             self.putItemCount(n)
             self.putNumChild(n)
diff --git a/src/libs/qtcreatorcdbext/symbolgroupnode.cpp b/src/libs/qtcreatorcdbext/symbolgroupnode.cpp
index ce67deb698..ac6b4b6c67 100644
--- a/src/libs/qtcreatorcdbext/symbolgroupnode.cpp
+++ b/src/libs/qtcreatorcdbext/symbolgroupnode.cpp
@@ -352,13 +352,15 @@ int DumpParameters::format(const std::string &type, const std::string &iname) co
     return -1;
 }
 
-enum PointerFormats // Watch data pointer format requests
+// Watch data pointer format requests. This should match the values
+// in DisplayFormat in watchhandler.h.
+enum PointerFormats
 {
     FormatAuto = 0,
-    FormatLatin1String = 1,
-    FormatUtf8String = 2,
-    FormatUtf16String = 3,
-    FormatUcs4String = 4
+    FormatLatin1String = 101,
+    FormatUtf8String = 102,
+    FormatUtf16String = 104,
+    FormatUcs4String = 105
 };
 
 enum DumpEncoding // WatchData encoding of GDBMI values
diff --git a/src/plugins/debugger/debuggerdialogs.cpp b/src/plugins/debugger/debuggerdialogs.cpp
index afcea28a58..c8e4e175c5 100644
--- a/src/plugins/debugger/debuggerdialogs.cpp
+++ b/src/plugins/debugger/debuggerdialogs.cpp
@@ -889,9 +889,9 @@ void TypeFormatsDialog::addTypeFormats(const QString &type0,
     m_ui->pages[pos]->addTypeFormats(type, typeFormats, current);
 }
 
-TypeFormats TypeFormatsDialog::typeFormats() const
+DumperTypeFormats TypeFormatsDialog::typeFormats() const
 {
-    return TypeFormats();
+    return DumperTypeFormats();
 }
 
 } // namespace Internal
diff --git a/src/plugins/debugger/debuggerdialogs.h b/src/plugins/debugger/debuggerdialogs.h
index cc1a9bffb8..3fe1cc1b51 100644
--- a/src/plugins/debugger/debuggerdialogs.h
+++ b/src/plugins/debugger/debuggerdialogs.h
@@ -159,7 +159,7 @@ private:
      QDialogButtonBox *m_box;
 };
 
-typedef QHash<QString, QStringList> TypeFormats;
+typedef QHash<QString, QStringList> DumperTypeFormats;
 
 class StartRemoteEngineDialog : public QDialog
 {
@@ -190,7 +190,7 @@ public:
 
     void addTypeFormats(const QString &type, const QStringList &formats,
         int currentFormat);
-    TypeFormats typeFormats() const;
+    DumperTypeFormats typeFormats() const;
 
 private:
     TypeFormatsDialogUi *m_ui;
diff --git a/src/plugins/debugger/watchhandler.cpp b/src/plugins/debugger/watchhandler.cpp
index dcfd28f5ee..55a0630976 100644
--- a/src/plugins/debugger/watchhandler.cpp
+++ b/src/plugins/debugger/watchhandler.cpp
@@ -79,7 +79,6 @@ static QHash<QByteArray, int> theTypeFormats;
 static QHash<QByteArray, int> theIndividualFormats;
 static int theUnprintableBase = -1;
 
-
 static QByteArray stripForFormat(const QByteArray &ba)
 {
     QByteArray res;
@@ -175,6 +174,9 @@ public:
     int rowCount(const QModelIndex &idx = QModelIndex()) const;
     int columnCount(const QModelIndex &idx) const;
 
+    static QString nameForFormat(int format);
+    TypeFormatList typeFormatList(const WatchData &value) const;
+
 signals:
     void currentIndexRequested(const QModelIndex &idx);
     void itemIsExpanded(const QModelIndex &idx);
@@ -247,8 +249,9 @@ private:
     QSet<QByteArray> m_expandedINames;
     QSet<QByteArray> m_fetchTriggered;
 
-    QStringList typeFormatList(const WatchData &data) const;
-    TypeFormats m_reportedTypeFormats;
+    TypeFormatList builtinTypeFormatList(const WatchData &data) const;
+    QStringList dumperTypeFormatList(const WatchData &data) const;
+    DumperTypeFormats m_reportedTypeFormats;
 
     // QWidgets and QProcesses taking care of special displays.
     typedef QMap<QByteArray, QPointer<QObject> > EditHandlers;
@@ -498,43 +501,24 @@ QString WatchModel::removeNamespaces(QString str) const
 static int formatToIntegerBase(int format)
 {
     switch (format) {
-        case HexadecimalFormat:
+        case HexadecimalIntegerFormat:
             return 16;
-        case BinaryFormat:
+        case BinaryIntegerFormat:
             return 2;
-        case OctalFormat:
+        case OctalIntegerFormat:
             return 8;
     }
     return 10;
 }
 
-static bool isIntegralValue(const QString &value)
-{
-    if (value.startsWith(QLatin1Char('-')))
-        return isIntegralValue(value.mid(1));
-
-    bool ok;
-    value.toULongLong(&ok, 10);
-    if (ok)
-        return true;
-    value.toULongLong(&ok, 16);
-    if (ok)
-        return true;
-    value.toULongLong(&ok, 8);
-    if (ok)
-        return true;
-
-    return false;
-}
-
 template <class IntType> QString reformatInteger(IntType value, int format)
 {
     switch (format) {
-        case HexadecimalFormat:
+        case HexadecimalIntegerFormat:
             return QLatin1String("(hex) ") + QString::number(value, 16);
-        case BinaryFormat:
+        case BinaryIntegerFormat:
             return QLatin1String("(bin) ") + QString::number(value, 2);
-        case OctalFormat:
+        case OctalIntegerFormat:
             return QLatin1String("(oct) ") + QString::number(value, 8);
     }
     return QString::number(value, 10); // not reached
@@ -543,7 +527,7 @@ template <class IntType> QString reformatInteger(IntType value, int format)
 static QString reformatInteger(quint64 value, int format, int size, bool isSigned)
 {
     // Follow convention and don't show negative non-decimal numbers.
-    if (format != DecimalFormat)
+    if (format != AutomaticFormat)
         isSigned = false;
 
     switch (size) {
@@ -671,23 +655,34 @@ QString WatchModel::formattedValue(const WatchData &data) const
         return value;
     }
 
-    if (isIntegralValue(value)) {
-        // Append quoted, printable character also for decimal.
-        const int format = itemFormat(data);
-        if (data.type.endsWith("char") || data.type.endsWith("QChar")) {
-            bool ok;
-            const int code = value.toInt(&ok);
-            return ok ? reformatCharacter(code, format) : value;
-        }
-        // Rest: Leave decimal as is
-        if (format <= 0)
-            return value;
+    const int format = itemFormat(data);
 
+    // Append quoted, printable character also for decimal.
+    if (data.type.endsWith("char") || data.type.endsWith("QChar")) {
+        bool ok;
+        const int code = value.toInt(&ok);
+        return ok ? reformatCharacter(code, format) : value;
+    }
+
+    if (format == HexadecimalIntegerFormat
+            || format == DecimalIntegerFormat
+            || format == OctalIntegerFormat
+            || format == BinaryIntegerFormat) {
         bool isSigned = value.startsWith(QLatin1Char('-'));
         quint64 raw = isSigned ? quint64(value.toLongLong()): value.toULongLong();
         return reformatInteger(raw, format, data.size, isSigned);
     }
 
+    if (format == ScientificFloatFormat) {
+        double d = value.toDouble();
+        return QString::number(d, 'e');
+    }
+
+    if (format == CompactFloatFormat) {
+        double d = value.toDouble();
+        return QString::number(d, 'g');
+    }
+
     if (data.type == "va_list")
         return value;
 
@@ -918,10 +913,10 @@ static QString truncateValue(QString v)
 
 int WatchModel::itemFormat(const WatchData &data) const
 {
-    const int individualFormat = theIndividualFormats.value(data.iname, -1);
-    if (individualFormat != -1)
+    const int individualFormat = theIndividualFormats.value(data.iname, AutomaticFormat);
+    if (individualFormat != AutomaticFormat)
         return individualFormat;
-    return theTypeFormats.value(stripForFormat(data.type), -1);
+    return theTypeFormats.value(stripForFormat(data.type), AutomaticFormat);
 }
 
 bool WatchModel::contentIsValid() const
@@ -1089,7 +1084,7 @@ QVariant WatchModel::data(const QModelIndex &idx, int role) const
             return m_expandedINames.contains(data.iname);
 
         case LocalsTypeFormatListRole:
-            return typeFormatList(data);
+            return QVariant::fromValue(typeFormatList(data));
 
         case LocalsTypeRole:
             return removeNamespaces(displayType(data));
@@ -1098,10 +1093,10 @@ QVariant WatchModel::data(const QModelIndex &idx, int role) const
             return QString::fromLatin1(data.type);
 
         case LocalsTypeFormatRole:
-            return theTypeFormats.value(stripForFormat(data.type), -1);
+            return theTypeFormats.value(stripForFormat(data.type), AutomaticFormat);
 
         case LocalsIndividualFormatRole:
-            return theIndividualFormats.value(data.iname, -1);
+            return theIndividualFormats.value(data.iname, AutomaticFormat);
 
         case LocalsRawValueRole:
             return data.value;
@@ -1171,7 +1166,7 @@ bool WatchModel::setData(const QModelIndex &idx, const QVariant &value, int role
 
         case LocalsIndividualFormatRole: {
             const int format = value.toInt();
-            if (format == -1)
+            if (format == AutomaticFormat)
                 theIndividualFormats.remove(data.iname);
             else
                 theIndividualFormats[data.iname] = format;
@@ -1246,31 +1241,36 @@ static inline QString msgArrayFormat(int n)
     return WatchModel::tr("Array of %n items", 0, n);
 }
 
-QStringList WatchModel::typeFormatList(const WatchData &data) const
-{
-    if (data.origaddr || isPointerType(data.type))
-        return QStringList()
-            << tr("Raw pointer")
-            << tr("Latin1 string")
-            << tr("UTF8 string")
-            << tr("Local 8bit string")
-            << tr("UTF16 string")
-            << tr("UCS4 string")
-            << msgArrayFormat(10)
-            << msgArrayFormat(100)
-            << msgArrayFormat(1000)
-            << msgArrayFormat(10000);
-    if (data.type.contains("char[") || data.type.contains("char ["))
-        return QStringList()
-            << tr("Latin1 string")
-            << tr("UTF8 string")
-            << tr("Local 8bit string");
-    if (isIntegralValue(data.value))
-        return QStringList()
-            << tr("Decimal")
-            << tr("Hexadecimal")
-            << tr("Binary")
-            << tr("Octal");
+QString WatchModel::nameForFormat(int format)
+{
+    switch (format) {
+        case RawFormat: return tr("Raw Data");
+        case Latin1StringFormat: return tr("Latin1 String");
+        case Utf8StringFormat: return tr("UTF-8 String");
+        case Local8BitStringFormat: return tr("Local 8-Bit String");
+        case Utf16StringFormat: return tr("UTF-16 String");
+        case Ucs4StringFormat: return tr("UCS-4 String");
+        case Array10Format: return msgArrayFormat(10);
+        case Array100Format: return msgArrayFormat(100);
+        case Array1000Format: return msgArrayFormat(1000);
+        case Array10000Format: return msgArrayFormat(10000);
+        case DecimalIntegerFormat: return tr("Decimal Integer");
+        case HexadecimalIntegerFormat: return tr("Hexadecimal Integer");
+        case BinaryIntegerFormat: return tr("Binary Integer");
+        case OctalIntegerFormat: return tr("Octal Integer");
+        case CompactFloatFormat: return tr("Compact Float");
+        case ScientificFloatFormat: return tr("Scientific Float");
+    }
+
+    QTC_CHECK(false);
+    return QString();
+}
+
+TypeFormatList WatchModel::typeFormatList(const WatchData &data) const
+{
+    TypeFormatList formats;
+
+    // Types supported by dumpers:
     // Hack: Compensate for namespaces.
     QString type = QLatin1String(stripForFormat(data.type));
     int pos = type.indexOf(QLatin1String("::Q"));
@@ -1280,7 +1280,50 @@ QStringList WatchModel::typeFormatList(const WatchData &data) const
     if (pos >= 0)
         type.truncate(pos);
     type.replace(QLatin1Char(':'), QLatin1Char('_'));
-    return m_reportedTypeFormats.value(type);
+    QStringList reported = m_reportedTypeFormats.value(type);
+    for (int i = 0, n = reported.size(); i != n; ++i)
+        formats.append(TypeFormatItem(reported.at(i), i));
+
+    // Fixed artificial string and pointer types.
+    if (data.origaddr || isPointerType(data.type)) {
+        formats.append(RawFormat);
+        formats.append(Latin1StringFormat);
+        formats.append(Utf8StringFormat);
+        formats.append(Local8BitStringFormat);
+        formats.append(Utf16StringFormat);
+        formats.append(Ucs4StringFormat);
+        formats.append(Array10Format);
+        formats.append(Array100Format);
+        formats.append(Array1000Format);
+        formats.append(Array10000Format);
+    } else if (data.type.contains("char[") || data.type.contains("char [")) {
+        formats.append(Latin1StringFormat);
+        formats.append(Utf8StringFormat);
+        formats.append(Ucs4StringFormat);
+    }
+
+    // Fixed artificial floating point types.
+    bool ok = false;
+    data.value.toDouble(&ok);
+    if (ok) {
+        formats.append(CompactFloatFormat);
+        formats.append(ScientificFloatFormat);
+    }
+
+    // Fixed artificial integral types.
+    data.value.toULongLong(&ok, 10);
+    if (!ok)
+        data.value.toULongLong(&ok, 16);
+    if (!ok)
+        data.value.toULongLong(&ok, 8);
+    if (ok) {
+        formats.append(DecimalIntegerFormat);
+        formats.append(HexadecimalIntegerFormat);
+        formats.append(BinaryIntegerFormat);
+        formats.append(OctalIntegerFormat);
+    }
+
+    return formats;
 }
 
 // Determine sort order of watch items by sort order or alphabetical inames
@@ -1439,10 +1482,10 @@ QDebug operator<<(QDebug d, const WatchModel &m)
 
 void WatchModel::formatRequests(QByteArray *out, const WatchItem *item) const
 {
-    int format = theIndividualFormats.value(item->iname, -1);
-    if (format == -1)
-        format = theTypeFormats.value(stripForFormat(item->type), -1);
-    if (format != -1)
+    int format = theIndividualFormats.value(item->iname, AutomaticFormat);
+    if (format == AutomaticFormat)
+        format = theTypeFormats.value(stripForFormat(item->type), AutomaticFormat);
+    if (format != AutomaticFormat)
         *out += item->iname + ":format=" + QByteArray::number(format) + ',';
     foreach (const WatchItem *child, item->children)
         formatRequests(out, child);
@@ -1869,7 +1912,7 @@ void WatchHandler::saveFormats()
     while (it.hasNext()) {
         it.next();
         const int format = it.value();
-        if (format != DecimalFormat) {
+        if (format != AutomaticFormat) {
             const QByteArray key = it.key().trimmed();
             if (!key.isEmpty())
                 formats.insert(QString::fromLatin1(key), format);
@@ -1948,7 +1991,7 @@ bool WatchHandler::hasItem(const QByteArray &iname) const
 void WatchHandler::setFormat(const QByteArray &type0, int format)
 {
     const QByteArray type = stripForFormat(type0);
-    if (format == -1)
+    if (format == AutomaticFormat)
         theTypeFormats.remove(type);
     else
         theTypeFormats[type] = format;
@@ -1958,11 +2001,11 @@ void WatchHandler::setFormat(const QByteArray &type0, int format)
 
 int WatchHandler::format(const QByteArray &iname) const
 {
-    int result = -1;
+    int result = AutomaticFormat;
     if (const WatchData *item = m_model->findItem(iname)) {
-        int result = theIndividualFormats.value(item->iname, -1);
-        if (result == -1)
-            result = theTypeFormats.value(stripForFormat(item->type), -1);
+        int result = theIndividualFormats.value(item->iname, AutomaticFormat);
+        if (result == AutomaticFormat)
+            result = theTypeFormats.value(stripForFormat(item->type), AutomaticFormat);
     }
     return result;
 }
@@ -1990,10 +2033,13 @@ QByteArray WatchHandler::typeFormatRequests() const
         QHashIterator<QByteArray, int> it(theTypeFormats);
         while (it.hasNext()) {
             it.next();
-            ba.append(it.key().toHex());
-            ba.append('=');
-            ba.append(QByteArray::number(it.value()));
-            ba.append(',');
+            const int format = it.value();
+            if (format >= RawFormat && format < ArtificialFormatBase) {
+                ba.append(it.key().toHex());
+                ba.append('=');
+                ba.append(QByteArray::number(format));
+                ba.append(',');
+            }
         }
         ba.chop(1);
     }
@@ -2007,10 +2053,13 @@ QByteArray WatchHandler::individualFormatRequests() const
         QHashIterator<QByteArray, int> it(theIndividualFormats);
         while (it.hasNext()) {
             it.next();
-            ba.append(it.key());
-            ba.append('=');
-            ba.append(QByteArray::number(it.value()));
-            ba.append(',');
+            const int format = it.value();
+            if (format >= RawFormat && format < ArtificialFormatBase) {
+                ba.append(it.key());
+                ba.append('=');
+                ba.append(QByteArray::number(it.value()));
+                ba.append(',');
+            }
         }
         ba.chop(1);
     }
@@ -2029,12 +2078,12 @@ QString WatchHandler::editorContents()
     return contents;
 }
 
-void WatchHandler::setTypeFormats(const TypeFormats &typeFormats)
+void WatchHandler::setTypeFormats(const DumperTypeFormats &typeFormats)
 {
     m_model->m_reportedTypeFormats = typeFormats;
 }
 
-TypeFormats WatchHandler::typeFormats() const
+DumperTypeFormats WatchHandler::typeFormats() const
 {
     return m_model->m_reportedTypeFormats;
 }
@@ -2048,7 +2097,7 @@ void WatchHandler::editTypeFormats(bool includeLocals, const QByteArray &iname)
     QList<QString> l = m_model->m_reportedTypeFormats.keys();
     qSort(l.begin(), l.end());
     foreach (const QString &ba, l) {
-        int f = iname.isEmpty() ? -1 : format(iname);
+        int f = iname.isEmpty() ? AutomaticFormat : format(iname);
         dlg.addTypeFormats(ba, m_model->m_reportedTypeFormats.value(ba), f);
     }
     if (dlg.exec())
@@ -2107,6 +2156,30 @@ QSet<QByteArray> WatchHandler::expandedINames() const
     return m_model->m_expandedINames;
 }
 
+
+////////////////////////////////////////////////////////////////////
+//
+// TypeFormatItem/List
+//
+////////////////////////////////////////////////////////////////////
+
+TypeFormatItem::TypeFormatItem(const QString &display, int format)
+    : display(display), format(format)
+{}
+
+void TypeFormatList::append(int format)
+{
+    append(TypeFormatItem(WatchModel::nameForFormat(format), format));
+}
+
+TypeFormatItem TypeFormatList::find(int format) const
+{
+    for (int i = 0; i != size(); ++i)
+        if (at(i).format == format)
+            return at(i);
+    return TypeFormatItem();
+}
+
 } // namespace Internal
 } // namespace Debugger
 
diff --git a/src/plugins/debugger/watchhandler.h b/src/plugins/debugger/watchhandler.h
index 0e610c40f3..3f446102ef 100644
--- a/src/plugins/debugger/watchhandler.h
+++ b/src/plugins/debugger/watchhandler.h
@@ -32,11 +32,76 @@
 
 #include "watchdata.h"
 
-#include <QPointer>
 #include <QAbstractItemModel>
+#include <QPointer>
+#include <QVector>
 
 QT_FORWARD_DECLARE_CLASS(QTabWidget)
 
+namespace Debugger {
+namespace Internal {
+
+// Special formats. Keep in sync with dumper.py.
+enum DisplayFormat
+{
+    AutomaticFormat = -1, // Based on type for individuals, dumper default for types.
+    RawFormat = 0,
+
+    // Values between 1 and 99 refer to dumper provided custom formats.
+
+    // Values between 100 and 199 refer to well-known formats handled in dumpers.
+    KnownDumperFormatBase = 100,
+    Latin1StringFormat,
+    Utf8StringFormat,
+    Local8BitStringFormat,
+    Utf16StringFormat,
+    Ucs4StringFormat,
+
+    Array10Format,
+    Array100Format,
+    Array1000Format,
+    Array10000Format,
+
+
+    // Values above 200 refer to format solely handled in the WatchHandler code
+    ArtificialFormatBase = 200,
+
+    BoolTextFormat,
+    BoolIntegerFormat,
+
+    DecimalIntegerFormat,
+    HexadecimalIntegerFormat,
+    BinaryIntegerFormat,
+    OctalIntegerFormat,
+
+    CompactFloatFormat,
+    ScientificFloatFormat,
+};
+
+
+class TypeFormatItem
+{
+public:
+    TypeFormatItem() : format(-1) {}
+    TypeFormatItem(const QString &display, int format);
+
+    QString display;
+    int format;
+};
+
+class TypeFormatList : public QVector<TypeFormatItem>
+{
+public:
+    using QVector::append;
+    void append(int format);
+    TypeFormatItem find(int format) const;
+};
+
+} // namespace Internal
+} // namespace Debugger
+
+Q_DECLARE_METATYPE(Debugger::Internal::TypeFormatList)
+
 namespace Debugger {
 
 class DebuggerEngine;
@@ -55,15 +120,7 @@ public:
     QByteArray varList;
 };
 
-typedef QHash<QString, QStringList> TypeFormats;
-
-enum IntegerFormat
-{
-    DecimalFormat = 0, // Keep that at 0 as default.
-    HexadecimalFormat,
-    BinaryFormat,
-    OctalFormat
-};
+typedef QHash<QString, QStringList> DumperTypeFormats; // Type name -> Dumper Formats
 
 class WatchHandler : public QObject
 {
@@ -104,8 +161,8 @@ public:
     int format(const QByteArray &iname) const;
 
     void addTypeFormats(const QByteArray &type, const QStringList &formats);
-    void setTypeFormats(const TypeFormats &typeFormats);
-    TypeFormats typeFormats() const;
+    void setTypeFormats(const DumperTypeFormats &typeFormats);
+    DumperTypeFormats typeFormats() const;
 
     void setUnprintableBase(int base);
     static int unprintableBase();
diff --git a/src/plugins/debugger/watchwindow.cpp b/src/plugins/debugger/watchwindow.cpp
index b3d033b4d3..7b62198211 100644
--- a/src/plugins/debugger/watchwindow.cpp
+++ b/src/plugins/debugger/watchwindow.cpp
@@ -59,6 +59,8 @@
 #include <QInputDialog>
 #include <QMessageBox>
 
+Q_DECLARE_METATYPE(QModelIndex)
+
 /////////////////////////////////////////////////////////////////////
 //
 // WatchDelegate
@@ -68,6 +70,8 @@
 namespace Debugger {
 namespace Internal {
 
+const char CurrentIndex[] = "CurrentIndex";
+
 static DebuggerEngine *currentEngine()
 {
     return debuggerCore()->currentEngine();
@@ -584,109 +588,164 @@ static void copyToClipboard(const QString &clipboardText)
     clipboard->setText(clipboardText, QClipboard::Clipboard);
 }
 
-void WatchTreeView::contextMenuEvent(QContextMenuEvent *ev)
+void WatchTreeView::fillFormatMenu(QMenu *formatMenu, const QModelIndex &mi)
 {
+    QTC_CHECK(mi.isValid());
+
     DebuggerEngine *engine = currentEngine();
     WatchHandler *handler = engine->watchHandler();
 
-    const QModelIndexList active = activeRows();
-    const QModelIndex idx = indexAt(ev->pos());
-    const QModelIndex mi0 = idx.sibling(idx.row(), 0);
-    const QModelIndex mi1 = idx.sibling(idx.row(), 1);
-    const QModelIndex mi2 = idx.sibling(idx.row(), 2);
-    const quint64 address = addressOf(mi0);
-    const uint size = sizeOf(mi0);
-    const quint64 pointerAddress = pointerAddressOf(mi0);
-    const QString exp = mi0.data(LocalsExpressionRole).toString();
-    const QString name = mi0.data(LocalsNameRole).toString();
+    const QModelIndex mi2 = mi.sibling(mi.row(), 2);
     const QString type = mi2.data().toString();
 
-    // Offer to open address pointed to or variable address.
-    const bool createPointerActions = pointerAddress && pointerAddress != address;
-
-    const QStringList alternativeFormats =
-        mi0.data(LocalsTypeFormatListRole).toStringList();
+    const TypeFormatList alternativeFormats =
+        mi.data(LocalsTypeFormatListRole).value<TypeFormatList>();
     int typeFormat =
-        mi0.data(LocalsTypeFormatRole).toInt();
-    if (typeFormat >= alternativeFormats.size())
-        typeFormat = -1;
+        mi.data(LocalsTypeFormatRole).toInt();
     const int individualFormat =
-        mi0.data(LocalsIndividualFormatRole).toInt();
+        mi.data(LocalsIndividualFormatRole).toInt();
     const int unprintableBase = handler->unprintableBase();
 
-    QMenu formatMenu;
-    QList<QAction *> typeFormatActions;
-    QList<QAction *> individualFormatActions;
-    QAction *clearTypeFormatAction = 0;
-    QAction *clearIndividualFormatAction = 0;
     QAction *showUnprintableUnicode = 0;
     QAction *showUnprintableEscape = 0;
     QAction *showUnprintableOctal = 0;
     QAction *showUnprintableHexadecimal = 0;
-    formatMenu.setTitle(tr("Change Local Display Format..."));
+    formatMenu->setTitle(tr("Change Local Display Format..."));
     showUnprintableUnicode =
-        formatMenu.addAction(tr("Treat All Characters as Printable"));
+        formatMenu->addAction(tr("Treat All Characters as Printable"));
     showUnprintableUnicode->setCheckable(true);
     showUnprintableUnicode->setChecked(unprintableBase == 0);
+    showUnprintableUnicode->setData(0);
     showUnprintableEscape =
-        formatMenu.addAction(tr("Show Unprintable Characters as Escape Sequences"));
+        formatMenu->addAction(tr("Show Unprintable Characters as Escape Sequences"));
     showUnprintableEscape->setCheckable(true);
     showUnprintableEscape->setChecked(unprintableBase == -1);
+    showUnprintableEscape->setData(-1);
     showUnprintableOctal =
-        formatMenu.addAction(tr("Show Unprintable Characters as Octal"));
+        formatMenu->addAction(tr("Show Unprintable Characters as Octal"));
     showUnprintableOctal->setCheckable(true);
     showUnprintableOctal->setChecked(unprintableBase == 8);
+    showUnprintableOctal->setData(8);
     showUnprintableHexadecimal =
-        formatMenu.addAction(tr("Show Unprintable Characters as Hexadecimal"));
+        formatMenu->addAction(tr("Show Unprintable Characters as Hexadecimal"));
     showUnprintableHexadecimal->setCheckable(true);
     showUnprintableHexadecimal->setChecked(unprintableBase == 16);
-    if (idx.isValid() /*&& !alternativeFormats.isEmpty() */) {
-        const QString spacer = QLatin1String("     ");
-        formatMenu.addSeparator();
-        QAction *dummy = formatMenu.addAction(
-            tr("Change Display for Object Named \"%1\":").arg(mi0.data().toString()));
-        dummy->setEnabled(false);
-        QString msg = (individualFormat == -1 && typeFormat != -1)
-            ? tr("Use Format for Type (Currently %1)")
-                .arg(alternativeFormats.at(typeFormat))
-            : tr("Use Display Format Based on Type") + QLatin1Char(' ');
-        clearIndividualFormatAction = formatMenu.addAction(spacer + msg);
-        clearIndividualFormatAction->setCheckable(true);
-        clearIndividualFormatAction->setChecked(individualFormat == -1);
-        for (int i = 0; i != alternativeFormats.size(); ++i) {
-            const QString format = spacer + alternativeFormats.at(i);
-            QAction *act = new QAction(format, &formatMenu);
-            act->setCheckable(true);
-            if (i == individualFormat)
-                act->setChecked(true);
-            formatMenu.addAction(act);
-            individualFormatActions.append(act);
-        }
-        formatMenu.addSeparator();
-        dummy = formatMenu.addAction(
-            tr("Change Display for Type \"%1\":").arg(type));
-        dummy->setEnabled(false);
-        clearTypeFormatAction = formatMenu.addAction(spacer + tr("Automatic"));
-        //clearTypeFormatAction->setEnabled(typeFormat != -1);
-        //clearTypeFormatAction->setEnabled(individualFormat != -1);
-        clearTypeFormatAction->setCheckable(true);
-        clearTypeFormatAction->setChecked(typeFormat == -1);
-        for (int i = 0; i != alternativeFormats.size(); ++i) {
-            const QString format = spacer + alternativeFormats.at(i);
-            QAction *act = new QAction(format, &formatMenu);
-            act->setCheckable(true);
-            //act->setEnabled(individualFormat != -1);
-            if (i == typeFormat)
-                act->setChecked(true);
-            formatMenu.addAction(act);
-            typeFormatActions.append(act);
-        }
-    } else {
-        QAction *dummy = formatMenu.addAction(
-            tr("Change Display for Type or Item..."));
-        dummy->setEnabled(false);
+    showUnprintableHexadecimal->setData(16);
+
+    connect(showUnprintableUnicode, SIGNAL(triggered()), SLOT(onShowUnprintable()));
+    connect(showUnprintableEscape, SIGNAL(triggered()), SLOT(onShowUnprintable()));
+    connect(showUnprintableOctal, SIGNAL(triggered()), SLOT(onShowUnprintable()));
+    connect(showUnprintableHexadecimal, SIGNAL(triggered()), SLOT(onShowUnprintable()));
+
+
+    const QString spacer = QLatin1String("     ");
+    formatMenu->addSeparator();
+    QAction *dummy = formatMenu->addAction(
+        tr("Change Display for Object Named \"%1\":").arg(mi.data().toString()));
+    dummy->setEnabled(false);
+    QString msg = (individualFormat == AutomaticFormat && typeFormat != AutomaticFormat)
+        ? tr("Use Format for Type (Currently %1)")
+            .arg(alternativeFormats.find(typeFormat).display)
+        : tr("Use Display Format Based on Type") + QLatin1Char(' ');
+
+    QAction *clearIndividualFormatAction = formatMenu->addAction(spacer + msg);
+    clearIndividualFormatAction->setCheckable(true);
+    clearIndividualFormatAction->setChecked(individualFormat == AutomaticFormat);
+    connect(clearIndividualFormatAction, SIGNAL(triggered()),
+        SLOT(onClearIndividualFormat()));
+
+    for (int i = 0; i != alternativeFormats.size(); ++i) {
+        const QString display = spacer + alternativeFormats.at(i).display;
+        const int format = alternativeFormats.at(i).format;
+        QAction *act = new QAction(display, formatMenu);
+        act->setData(format);
+        act->setCheckable(true);
+        act->setChecked(format == individualFormat);
+        act->setProperty(CurrentIndex, QVariant::fromValue(mi));
+        formatMenu->addAction(act);
+        connect(act, SIGNAL(triggered()), SLOT(onIndividualFormatChange()));
     }
 
+    formatMenu->addSeparator();
+    dummy = formatMenu->addAction(tr("Change Display for Type \"%1\":").arg(type));
+    dummy->setEnabled(false);
+
+    QAction *clearTypeFormatAction = formatMenu->addAction(spacer + tr("Automatic"));
+    clearTypeFormatAction->setCheckable(true);
+    clearTypeFormatAction->setChecked(typeFormat == AutomaticFormat);
+    connect(clearTypeFormatAction, SIGNAL(triggered()), SLOT(onClearTypeFormat()));
+
+    for (int i = 0; i != alternativeFormats.size(); ++i) {
+        const QString display = spacer + alternativeFormats.at(i).display;
+        QAction *act = new QAction(display, formatMenu);
+        const int format = alternativeFormats.at(i).format;
+        act->setData(format);
+        act->setCheckable(true);
+        act->setChecked(format == typeFormat);
+        act->setProperty(CurrentIndex, QVariant::fromValue(mi));
+        formatMenu->addAction(act);
+        connect(act, SIGNAL(triggered()), SLOT(onTypeFormatChange()));
+    }
+}
+
+void WatchTreeView::onClearTypeFormat()
+{
+    const QModelIndexList active = activeRows();
+    foreach (const QModelIndex &idx, active)
+        setModelData(LocalsTypeFormatRole, AutomaticFormat, idx);
+}
+
+void WatchTreeView::onClearIndividualFormat()
+{
+    const QModelIndexList active = activeRows();
+    foreach (const QModelIndex &idx, active)
+        setModelData(LocalsIndividualFormatRole, AutomaticFormat, idx);
+}
+
+void WatchTreeView::onShowUnprintable()
+{
+    QAction *act = qobject_cast<QAction *>(sender());
+    QTC_ASSERT(act, return);
+    DebuggerEngine *engine = currentEngine();
+    WatchHandler *handler = engine->watchHandler();
+    handler->setUnprintableBase(act->data().toInt());
+}
+
+void WatchTreeView::onTypeFormatChange()
+{
+    QAction *act = qobject_cast<QAction *>(sender());
+    QTC_ASSERT(act, return);
+    QModelIndex idx = act->property(CurrentIndex).value<QModelIndex>();
+    setModelData(LocalsTypeFormatRole, act->data(), idx);
+}
+
+void WatchTreeView::onIndividualFormatChange()
+{
+    QAction *act = qobject_cast<QAction *>(sender());
+    QTC_ASSERT(act, return);
+    QModelIndex idx = act->property(CurrentIndex).value<QModelIndex>();
+    setModelData(LocalsIndividualFormatRole, act->data(), idx);
+}
+
+void WatchTreeView::contextMenuEvent(QContextMenuEvent *ev)
+{
+    DebuggerEngine *engine = currentEngine();
+    WatchHandler *handler = engine->watchHandler();
+
+    const QModelIndex idx = indexAt(ev->pos());
+    const QModelIndex mi0 = idx.sibling(idx.row(), 0);
+    const QModelIndex mi1 = idx.sibling(idx.row(), 1);
+    const QModelIndex mi2 = idx.sibling(idx.row(), 2);
+    const quint64 address = addressOf(mi0);
+    const uint size = sizeOf(mi0);
+    const quint64 pointerAddress = pointerAddressOf(mi0);
+    const QString exp = mi0.data(LocalsExpressionRole).toString();
+    const QString name = mi0.data(LocalsNameRole).toString();
+    const QString type = mi2.data().toString();
+
+    // Offer to open address pointed to or variable address.
+    const bool createPointerActions = pointerAddress && pointerAddress != address;
+
     const bool actionsEnabled = engine->debuggerActionsEnabled();
     const bool canHandleWatches = engine->hasCapability(AddWatcherCapability);
     const DebuggerState state = engine->state();
@@ -782,6 +841,14 @@ void WatchTreeView::contextMenuEvent(QContextMenuEvent *ev)
         menu.addAction(actRemoveWatches);
     }
 
+    QMenu formatMenu;
+    if (mi0.isValid()) {
+        fillFormatMenu(&formatMenu, mi0);
+    } else {
+        QAction *dummy = formatMenu.addAction(tr("Change Display for Type or Item..."));
+        dummy->setEnabled(false);
+    }
+
     QMenu memoryMenu;
     memoryMenu.setTitle(tr("Open Memory Editor..."));
     QAction *actOpenMemoryEditAtObjectAddress = new QAction(&memoryMenu);
@@ -839,7 +906,6 @@ void WatchTreeView::contextMenuEvent(QContextMenuEvent *ev)
     QAction *actCopyValue = new QAction(tr("Copy Value to Clipboard"), &menu);
     actCopyValue->setEnabled(idx.isValid());
 
-
     menu.addAction(actInsertNewWatchItem);
     menu.addAction(actSelectWidgetToWatch);
     menu.addMenu(&formatMenu);
@@ -914,43 +980,11 @@ void WatchTreeView::contextMenuEvent(QContextMenuEvent *ev)
         copyToClipboard(mi1.data().toString());
     } else if (act == actRemoveWatches) {
         handler->clearWatches();
-    } else if (act == clearTypeFormatAction) {
-        foreach (const QModelIndex &idx, active)
-            setModelData(LocalsTypeFormatRole, -1, idx);
-    } else if (act == clearIndividualFormatAction) {
-        foreach (const QModelIndex &idx, active)
-            setModelData(LocalsIndividualFormatRole, -1, idx);
     } else if (act == actShowInEditor) {
         QString contents = handler->editorContents();
         debuggerCore()->openTextEditor(tr("Locals & Expressions"), contents);
-    } else if (act == showUnprintableUnicode) {
-        handler->setUnprintableBase(0);
-    } else if (act == showUnprintableEscape) {
-        handler->setUnprintableBase(-1);
-    } else if (act == showUnprintableOctal) {
-        handler->setUnprintableBase(8);
-    } else if (act == showUnprintableHexadecimal) {
-        handler->setUnprintableBase(16);
     } else if (act == actCloseEditorToolTips) {
         DebuggerToolTipManager::closeAllToolTips();
-    } else if (handleBaseContextAction(act)) {
-        ;
-    } else {
-        // Restrict multiple changes to items of the same type
-        // to avoid assigning illegal formats.
-        const QVariant currentType = mi1.data(LocalsTypeRole);
-        for (int i = 0; i != typeFormatActions.size(); ++i) {
-            if (act == typeFormatActions.at(i))
-                foreach (const QModelIndex &idx, active)
-                    if (idx.data(LocalsTypeRole) == currentType)
-                        setModelData(LocalsTypeFormatRole, i, idx);
-        }
-        for (int i = 0; i != individualFormatActions.size(); ++i) {
-            if (act == individualFormatActions.at(i))
-                foreach (const QModelIndex &idx, active)
-                    if (idx.data(LocalsTypeRole) == currentType)
-                        setModelData(LocalsIndividualFormatRole, i, idx);
-        }
     }
 }
 
diff --git a/src/plugins/debugger/watchwindow.h b/src/plugins/debugger/watchwindow.h
index f443fb481b..86decee94f 100644
--- a/src/plugins/debugger/watchwindow.h
+++ b/src/plugins/debugger/watchwindow.h
@@ -54,6 +54,8 @@ public:
     void rowActivated(const QModelIndex &index);
     void reset();
 
+    void fillFormatMenu(QMenu *, const QModelIndex &mi);
+
 public slots:
     void watchExpression(const QString &exp);
     void watchExpression(const QString &exp, const QString &name);
@@ -62,11 +64,19 @@ public slots:
 signals:
     void currentIndexChanged(const QModelIndex &currentIndex);
 
-private:
-    Q_SLOT void resetHelper();
-    Q_SLOT void expandNode(const QModelIndex &idx);
-    Q_SLOT void collapseNode(const QModelIndex &idx);
+private slots:
+    void resetHelper();
+    void expandNode(const QModelIndex &idx);
+    void collapseNode(const QModelIndex &idx);
+
+    void onClearIndividualFormat();
+    void onClearTypeFormat();
+    void onShowUnprintable();
 
+    void onTypeFormatChange();
+    void onIndividualFormatChange();
+
+private:
     void keyPressEvent(QKeyEvent *ev);
     void contextMenuEvent(QContextMenuEvent *ev);
     void dragEnterEvent(QDragEnterEvent *ev);
-- 
GitLab