Skip to content
Snippets Groups Projects
watchhandler.cpp 35.4 KiB
Newer Older
con's avatar
con committed
/***************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2008-2009 Nokia Corporation and/or its subsidiary(-ies).
con's avatar
con committed
**
** Contact:  Qt Software Information (qt-info@nokia.com)
**
**
** Non-Open Source Usage
**
con's avatar
con committed
** Licensees may use this file in accordance with the Qt Beta Version
** License Agreement, Agreement version 2.2 provided with the Software or,
** alternatively, in accordance with the terms contained in a written
** agreement between you and Nokia.
**
** GNU General Public License Usage
**
con's avatar
con committed
** Alternatively, this file may be used under the terms of the GNU General
** Public License versions 2.0 or 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the packaging
** of this file.  Please review the following information to ensure GNU
** General Public Licensing requirements will be met:
**
** http://www.fsf.org/licensing/licenses/info/GPLv2.html and
** http://www.gnu.org/copyleft/gpl.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt GPL Exception
** version 1.3, included in the file GPL_EXCEPTION.txt in this package.
**
***************************************************************************/
hjk's avatar
hjk committed

con's avatar
con committed
#include "watchhandler.h"

#if USE_MODEL_TEST
#include "modeltest.h"
#endif

hjk's avatar
hjk committed
#include <utils/qtcassert.h>
con's avatar
con committed

#include <QtCore/QDebug>
#include <QtCore/QEvent>

#include <QtGui/QApplication>
#include <QtGui/QLabel>
#include <QtGui/QToolTip>
#include <QtGui/QTextEdit>

#include <ctype.h>

// creates debug output regarding pending watch data results
con's avatar
con committed
// creates debug output for accesses to the itemmodel
//#define DEBUG_MODEL 1

#if DEBUG_MODEL
#   define MODEL_DEBUG(s) qDebug() << s
#else
#   define MODEL_DEBUG(s) 
#endif
#define MODEL_DEBUGX(s) qDebug() << s

using namespace Debugger::Internal;

static const QString strNotInScope = QLatin1String("<not in scope>");

static bool isIntOrFloatType(const QString &type)
{
    static const QStringList types = QStringList()
        << "char" << "int" << "short" << "float" << "double" << "long"
        << "bool" << "signed char" << "unsigned" << "unsigned char"
        << "unsigned int" << "unsigned long" << "long long";
    return types.contains(type);
}

static bool isPointerType(const QString &type)
{
    return type.endsWith("*") || type.endsWith("* const");
}

static QString htmlQuote(const QString &str0)
{
    QString str = str0;
    str.replace('&', "&amp;");
    str.replace('<', "&lt;");
    str.replace('>', "&gt;");
    return str;
}

////////////////////////////////////////////////////////////////////
//
// WatchData
//
////////////////////////////////////////////////////////////////////

WatchData::WatchData()
{
    valuedisabled = false;
    state = InitialState;
    childCount = -1;
    parentIndex = -1;
    row = -1;
    level = -1;
    changed = false;
}

void WatchData::setError(const QString &msg)
{
    setAllUnneeded();
    value = msg;
    setChildCount(0);
    valuedisabled = true;
}

static QByteArray quoteUnprintable(const QByteArray &ba)
{
    QByteArray res;
    char buf[10];
    for (int i = 0, n = ba.size(); i != n; ++i) {
Patrick Star's avatar
Patrick Star committed
        unsigned char c = ba.at(i);
con's avatar
con committed
        if (isprint(c)) {
            res += c;
        } else {
            qsnprintf(buf, sizeof(buf) - 1, "\\%x", int(c));
            res += buf;
        }
    }
    return res;
}

void WatchData::setValue(const QByteArray &value0)
{
    value = quoteUnprintable(value0);
    if (value == "{...}") {
        value.clear();
        childCount = 1; // at least one...
    }

    // avoid duplicated information
    if (value.startsWith("(") && value.contains(") 0x"))
        value = value.mid(value.lastIndexOf(") 0x") + 2);

    // doubles are sometimes displayed as "@0x6141378: 1.2".
    // I don't want that.
    if (/*isIntOrFloatType(type) && */ value.startsWith("@0x")
         && value.contains(':')) {
        value = value.mid(value.indexOf(':') + 2);
        setChildCount(0);
    }

    // "numchild" is sometimes lying
    //MODEL_DEBUG("\n\n\nPOINTER: " << type << value);
    if (isPointerType(type))
        setChildCount(value != "0x0" && value != "<null>");

    // pointer type information is available in the 'type'
    // column. No need to duplicate it here.
    if (value.startsWith("(" + type + ") 0x"))
        value = value.section(" ", -1, -1);
    
    setValueUnneeded();
}

void WatchData::setValueToolTip(const QString &tooltip)
{
    valuetooltip = tooltip;
}

void WatchData::setType(const QString &str)
{
    type = str.trimmed();
    bool changed = true;
    while (changed) {
        if (type.endsWith("const"))
            type.chop(5);
        else if (type.endsWith(" "))
            type.chop(1);
        else if (type.endsWith("&"))
            type.chop(1);
        else if (type.startsWith("const "))
            type = type.mid(6);
        else if (type.startsWith("volatile "))
            type = type.mid(9);
        else if (type.startsWith("class "))
            type = type.mid(6);
        else if (type.startsWith("struct "))
            type = type.mid(6);
        else if (type.startsWith(" "))
            type = type.mid(1);
        else
            changed = false;
    }
    setTypeUnneeded();
    if (isIntOrFloatType(type))
        setChildCount(0);
}

void WatchData::setAddress(const QString & str)
{
    addr = str;
}

QString WatchData::toString() const
{
    QString res = "{";

    res += "level=\"" + QString::number(level) + "\",";
    res += "parent=\"" + QString::number(parentIndex) + "\",";
    res += "row=\"" + QString::number(row) + "\",";
    res += "child=\"";
    foreach (int index, childIndex)
        res += QString::number(index) + ",";
    if (res.endsWith(','))
        res[res.size() - 1] = '"';
    else
        res += '"';
    res += ",";
    

    if (!iname.isEmpty())
        res += "iname=\"" + iname + "\",";
    if (!exp.isEmpty())
        res += "exp=\"" + exp + "\",";

    if (!variable.isEmpty())
        res += "variable=\"" + variable + "\",";

    if (isValueNeeded())
        res += "value=<needed>,";
    if (isValueKnown() && !value.isEmpty())
        res += "value=\"" + value + "\",";

    if (!editvalue.isEmpty())
        res += "editvalue=\"" + editvalue + "\",";

    if (isTypeNeeded())
        res += "type=<needed>,";
    if (isTypeKnown() && !type.isEmpty())
        res += "type=\"" + type + "\",";

    if (isChildCountNeeded())
        res += "numchild=<needed>,";
    if (isChildCountKnown() && childCount == -1)
        res += "numchild=\"" + QString::number(childCount) + "\",";

    if (isChildrenNeeded())
        res += "children=<needed>,";

    if (res.endsWith(','))
        res[res.size() - 1] = '}';
    else
        res += '}';

    return res;
}


static bool iNameSorter(const WatchData &d1, const WatchData &d2)
{
    if (d1.level != d2.level)
        return d1.level < d2.level;

    for (int level = 0; level != d1.level; ++level) {
        QString name1 = d1.iname.section('.', level, level);
        QString name2 = d2.iname.section('.', level, level);
        //MODEL_DEBUG(" SORT: " << name1 << name2 << (name1 < name2));

        if (name1 != name2) {
            // This formerly used inames. in this case 'lastIndexOf' probably
            // makes more sense.
            if (name1.startsWith('[') && name2.startsWith('[')) {
                return name1.mid(1, name1.indexOf(']') - 1).toInt()
                     < name2.mid(1, name2.indexOf(']') - 1).toInt();
                // numbers should be sorted according to their numerical value
                //int pos = d1.name.lastIndexOf('.');
                //if (pos != -1 && pos + 1 != d1.name.size() && d1.name.at(pos + 1).isDigit())
                //    return d1.name.size() < d2.name.size();
                // fall through
            }
            return name1 < name2;
        }
    }
    return false; 
}

static QString parentName(const QString &iname)
{
    int pos = iname.lastIndexOf(".");
    if (pos == -1)
        return QString();
    return iname.left(pos);
}


static void insertDataHelper(QList<WatchData> &list, const WatchData &data)
{
    // FIXME: Quadratic algorithm
    for (int i = list.size(); --i >= 0; ) {
        if (list.at(i).iname == data.iname) {
            list[i] = data;
            return;
        }
    }
    list.append(data);
}

static WatchData take(const QString &iname, QList<WatchData> *list)
{
    for (int i = list->size(); --i >= 0;) {
        if (list->at(i).iname == iname) {
            WatchData res = list->at(i);
            (*list)[i] = list->back();
            (void) list->takeLast();
            return res;
            //return list->takeAt(i);
        }
    }
    return WatchData();
}


static QList<WatchData> initialSet()
{
    QList<WatchData> result;

    WatchData root;
    root.state = 0;
    root.level = 0;
    root.row = 0;
    root.name = "Root";
    root.parentIndex = -1;
    root.childIndex.append(1);
    root.childIndex.append(2);
    root.childIndex.append(3);
    result.append(root);

    WatchData local;
    local.iname = "local";
    local.name = "Locals";
    local.state = 0;
    local.level = 1;
    local.row = 0;
    local.parentIndex = 0;
    result.append(local);

    WatchData tooltip;
    tooltip.iname = "tooltip";
    tooltip.name = "Tooltip";
    tooltip.state = 0;
    tooltip.level = 1;
    tooltip.row = 1;
    tooltip.parentIndex = 0;
    result.append(tooltip);

    WatchData watch;
    watch.iname = "watch";
    watch.name = "Watchers";
    watch.state = 0;
    watch.level = 1;
    watch.row = 2;
    watch.parentIndex = 0;
    result.append(watch);

    return result;
}

con's avatar
con committed
///////////////////////////////////////////////////////////////////////
//
// WatchHandler
//
///////////////////////////////////////////////////////////////////////

WatchHandler::WatchHandler()
{
    m_expandPointers = true;
    m_inFetchMore = false;
    m_inChange = false;

    m_completeSet = initialSet();
    m_incompleteSet.clear();
con's avatar
con committed
    m_displaySet = m_completeSet;
}

bool WatchHandler::setData(const QModelIndex &idx,
    const QVariant &value, int role)
{
/*
    Q_UNUSED(idx);
    Q_UNUSED(value);
    Q_UNUSED(role);
    if (role == VisualRole) {
        QString iname = inameFromIndex(index);
        setDisplayedIName(iname, value.toBool());
        return true;
    }
    return true;
*/
    return QAbstractItemModel::setData(idx, value, role);
}

static QString niceType(QString type)
{
    if (type.contains("std::")) {
hjk's avatar
hjk committed
        // std::string
con's avatar
con committed
        type.replace("std::basic_string<char, std::char_traits<char>, "
                     "std::allocator<char> >", "std::string");
hjk's avatar
hjk committed

        // std::wstring
con's avatar
con committed
        type.replace("std::basic_string<wchar_t, std::char_traits<wchar_t>, "
                     "std::allocator<wchar_t> >", "std::wstring");

hjk's avatar
hjk committed
        // std::vector
        static QRegExp re1("std::vector<(.*), std::allocator<(.*)>\\s*>");
hjk's avatar
hjk committed
        re1.setMinimal(true);
        for (int i = 0; i != 10; ++i) {
            if (re1.indexIn(type) == -1 || re1.cap(1) != re1.cap(2)) 
                break;
            type.replace(re1.cap(0), "std::vector<" + re1.cap(1) + ">");
        }

        // std::list
        static QRegExp re2("std::list<(.*), std::allocator<(.*)>\\s*>");
hjk's avatar
hjk committed
        re2.setMinimal(true);
con's avatar
con committed
        for (int i = 0; i != 10; ++i) {
hjk's avatar
hjk committed
            if (re2.indexIn(type) == -1 || re2.cap(1) != re2.cap(2)) 
con's avatar
con committed
                break;
hjk's avatar
hjk committed
            type.replace(re2.cap(0), "std::list<" + re2.cap(1) + ">");
con's avatar
con committed
        }

        // std::map
        static QRegExp re3("std::map<(.*), (.*), std::less<(.*)\\s*>, "
            "std::allocator<std::pair<const (.*), (.*)\\s*> > >");
        re3.setMinimal(true);
        for (int i = 0; i != 10; ++i) {
            if (re3.indexIn(type) == -1 || re3.cap(1) != re3.cap(3)
                || re3.cap(1) != re3.cap(4) || re3.cap(2) != re3.cap(5)) 
                break;
            type.replace(re3.cap(0), "std::map<" + re3.cap(1) + ", " + re3.cap(2) + ">");
        }

con's avatar
con committed
        type.replace(" >", ">");
    }
    return type;
}

QVariant WatchHandler::data(const QModelIndex &idx, int role) const
{
    int node = idx.internalId();
    if (node < 0)
        return QVariant();
hjk's avatar
hjk committed
    QTC_ASSERT(node < m_displaySet.size(), return QVariant());
con's avatar
con committed

    const WatchData &data = m_displaySet.at(node);

    switch (role) {
        case Qt::DisplayRole: {
            switch (idx.column()) {
                case 0: return data.name;
                case 1: return data.value;
                case 2: return niceType(data.type);
                default: break;
            }
            break;
        }

        case Qt::ToolTipRole: {
            QString val = data.value;
            if (val.size() > 1000)
                val = val.left(1000) + " ... <cut off>";

            QString tt = "<table>";
            //tt += "<tr><td>internal name</td><td> : </td><td>";
            //tt += htmlQuote(iname) + "</td></tr>";
            tt += "<tr><td>expression</td><td> : </td><td>";
            tt += htmlQuote(data.exp) + "</td></tr>";
            tt += "<tr><td>type</td><td> : </td><td>";
            tt += htmlQuote(data.type) + "</td></tr>";
            //if (!valuetooltip.isEmpty())
            //    tt += valuetooltip;
            //else
                tt += "<tr><td>value</td><td> : </td><td>"; 
                tt += htmlQuote(data.value) + "</td></tr>";
            tt += "<tr><td>addr</td><td> : </td><td>";
            tt += htmlQuote(data.addr) + "</td></tr>";
            tt += "<tr><td>iname</td><td> : </td><td>";
            tt += htmlQuote(data.iname) + "</td></tr>";
            tt += "</table>";
            tt.replace("@value@", htmlQuote(data.value));

            if (tt.size() > 10000)
                tt = tt.left(10000) + " ... <cut off>";
            return tt;
        }

        case Qt::ForegroundRole: {
            static const QVariant red(QColor(200, 0, 0));
            static const QVariant black(QColor(0, 0, 0));
            static const QVariant gray(QColor(140, 140, 140));
            switch (idx.column()) {
                case 0: return black;
                case 1: return data.valuedisabled ? gray : data.changed ? red : black;
                case 2: return black;
            }
            break;
        }

        case INameRole:
            return data.iname;

        case VisualRole:
            return m_displayedINames.contains(data.iname);
    
        case ExpandedRole:
            //qDebug() << " FETCHING: " << data.iname
            //    << m_expandedINames.contains(data.iname)
            //    << m_expandedINames;
            // Level 0 and 1 are always expanded
            return node < 4 || m_expandedINames.contains(data.iname);
    
con's avatar
con committed
        default:
            break; 
    }
    return QVariant();
}

Qt::ItemFlags WatchHandler::flags(const QModelIndex &idx) const
{
    using namespace Qt;

    if (!idx.isValid())
        return ItemFlags();

    int node = idx.internalId();
    if (node < 0)
        return ItemFlags();

    // enabled, editable, selectable, checkable, and can be used both as the
    // source of a drag and drop operation and as a drop target.

    static const ItemFlags DefaultNotEditable =
          ItemIsSelectable
        | ItemIsDragEnabled
        | ItemIsDropEnabled
        // | ItemIsUserCheckable
        // | ItemIsTristate
        | ItemIsEnabled;

    static const ItemFlags DefaultEditable =
        DefaultNotEditable | ItemIsEditable;

    const WatchData &data = m_displaySet.at(node);
    return idx.column() == 1 &&
        data.isWatcher() ? DefaultEditable : DefaultNotEditable;
}

QVariant WatchHandler::headerData(int section, Qt::Orientation orientation,
    int role) const
{
    if (orientation == Qt::Vertical)
        return QVariant();
    if (role == Qt::DisplayRole) {
        switch (section) {
            case 0: return tr("Name")  + "     ";
            case 1: return tr("Value") + "     ";
            case 2: return tr("Type")  + "     ";
        }
    }
    return QVariant(); 
}

QString WatchHandler::toString() const
{
    QString res;
    res += "\nIncomplete:\n";
    for (int i = 0, n = m_incompleteSet.size(); i != n; ++i) {
        res += QString("%1: ").arg(i);
        res += m_incompleteSet.at(i).toString();
        res += '\n';
    }
    res += "\nComplete:\n";
    for (int i = 0, n = m_completeSet.size(); i != n; ++i) {
        res += QString("%1: ").arg(i);
        res += m_completeSet.at(i).toString();
        res += '\n';
    }
    res += "\nDisplay:\n";
    for (int i = 0, n = m_displaySet.size(); i != n; ++i) {
        res += QString("%1: ").arg(i);
        res += m_displaySet.at(i).toString();
        res += '\n';
    }
#if 0
    res += "\nOld:\n";
    for (int i = 0, n = m_oldSet.size(); i != n; ++i) {
        res += m_oldSet.at(i).toString();
        res += '\n';
    }
#endif
    return res;
}

WatchData *WatchHandler::findData(const QString &iname)
{
    for (int i = m_completeSet.size(); --i >= 0; )
        if (m_completeSet.at(i).iname == iname)
            return &m_completeSet[i];
    return 0;
}

WatchData WatchHandler::takeData(const QString &iname)
{
    WatchData data = take(iname, &m_incompleteSet);
    if (data.isValid())
        return data;
    return take(iname, &m_completeSet);
}

QList<WatchData> WatchHandler::takeCurrentIncompletes()
{
    QList<WatchData> res = m_incompleteSet;
    //MODEL_DEBUG("TAKING INCOMPLETES" << toString());
    m_incompleteSet.clear();
    return res;
}

void WatchHandler::rebuildModel()
{
    if (m_inChange) {
        MODEL_DEBUG("RECREATE MODEL IGNORED, CURRENT SET:\n" << toString());
        return;
    }

    #ifdef DEBUG_PENDING
    MODEL_DEBUG("RECREATE MODEL, CURRENT SET:\n" << toString());
    #endif

    QHash<QString, int> oldTopINames;
con's avatar
con committed
    QHash<QString, QString> oldValues;
    for (int i = 0, n = m_oldSet.size(); i != n; ++i) {
        WatchData &data = m_oldSet[i];
        oldValues[data.iname] = data.value;
        if (data.level == 2)
            ++oldTopINames[data.iname];
con's avatar
con committed
    }
    #ifdef DEBUG_PENDING
    MODEL_DEBUG("OLD VALUES: " << oldValues);
    #endif

    for (int i = m_completeSet.size(); --i >= 0; ) {
        WatchData &data = m_completeSet[i];
        data.level = data.iname.isEmpty() ? 0 : data.iname.count('.') + 1;
        data.childIndex.clear();
    }

    qSort(m_completeSet.begin(), m_completeSet.end(), &iNameSorter);

    // This helps to decide whether the view has completely changed or not.
    QHash<QString, int> topINames;

con's avatar
con committed
    QHash<QString, int> iname2idx;

    for (int i = m_completeSet.size(); --i > 0; ) {
        WatchData &data = m_completeSet[i];
        data.parentIndex = 0;
        data.childIndex.clear();
        iname2idx[data.iname] = i;
        if (data.level == 2)
            ++topINames[data.iname];
con's avatar
con committed
    }
    //qDebug() << "TOPINAMES: " << topINames << "\nOLD: " << oldTopINames;
con's avatar
con committed

    for (int i = 1; i < m_completeSet.size(); ++i) {
        WatchData &data = m_completeSet[i];
        QString parentIName = parentName(data.iname);
        data.parentIndex = iname2idx.value(parentIName, 0);
        WatchData &parent = m_completeSet[data.parentIndex];
        data.row = parent.childIndex.size();
        parent.childIndex.append(i);
    }

    m_oldSet = m_completeSet;
    m_oldSet += m_incompleteSet;

    for (int i = 0, n = m_completeSet.size(); i != n; ++i) {
        WatchData &data = m_completeSet[i];
        data.changed = !data.value.isEmpty()
            && data.value != oldValues[data.iname]
            && data.value != strNotInScope;
    }

    emit layoutAboutToBeChanged();

    if (oldTopINames != topINames) {
        m_displaySet = initialSet();
        m_expandedINames.clear();
        emit reset();
    }
con's avatar
con committed

    m_displaySet = m_completeSet;

    #ifdef DEBUG_PENDING
    MODEL_DEBUG("SET  " << toString());
    #endif

#if 1
    // Append dummy item to get the [+] effect
    for (int i = 0, n = m_displaySet.size(); i != n; ++i) {
        WatchData &data = m_displaySet[i];
        if (data.childCount > 0 && data.childIndex.size() == 0) {
            WatchData dummy;
            dummy.state = 0;
            dummy.row = 0;
            dummy.iname = data.iname + ".dummy";
            //dummy.name = data.iname + ".dummy";
            //dummy.name  = "<loading>";
            dummy.level = data.level + 1;
            dummy.parentIndex = i;
            dummy.childCount = 0;
            data.childIndex.append(m_displaySet.size());
            m_displaySet.append(dummy); 
        }
    }
#endif

    // Possibly append dummy items to prevent empty views
    bool ok = true;
hjk's avatar
hjk committed
    QTC_ASSERT(m_displaySet.size() >= 2, ok = false);
    QTC_ASSERT(m_displaySet.at(1).iname == "local", ok = false);
    QTC_ASSERT(m_displaySet.at(2).iname == "tooltip", ok = false);
    QTC_ASSERT(m_displaySet.at(3).iname == "watch", ok = false);
con's avatar
con committed
    if (ok) {
        for (int i = 1; i <= 3; ++i) {
            WatchData &data = m_displaySet[i];
            if (data.childIndex.size() == 0) {
                WatchData dummy;
                dummy.state = 0;
                dummy.row = 0;
                if (i == 1) {
                    dummy.iname = "local.dummy";
                    dummy.name  = "<No Locals>";
                } else if (i == 2) {
                    dummy.iname = "tooltip.dummy";
                    dummy.name  = "<No Tooltip>";
                } else {
                    dummy.iname = "watch.dummy";
                    dummy.name  = "<No Watchers>";
                }
                dummy.level = 2;
                dummy.parentIndex = i;
                dummy.childCount = 0;
                data.childIndex.append(m_displaySet.size());
                m_displaySet.append(dummy); 
            }
        }
    }

    m_inChange = true;
    //qDebug() << "WATCHHANDLER: RESET ABOUT TO EMIT";
    emit reset();
    //qDebug() << "WATCHHANDLER: RESET EMITTED";
    m_inChange = false;

    #if DEBUG_MODEL
    #if USE_MODEL_TEST
    //(void) new ModelTest(this, this);
    #endif
    #endif
    
    #ifdef DEBUG_PENDING
    MODEL_DEBUG("SORTED: " << toString());
    MODEL_DEBUG("EXPANDED INAMES: " << m_expandedINames);
    #endif
}

void WatchHandler::cleanup()
{
    m_oldSet.clear();
    m_expandedINames.clear();
    m_displayedINames.clear();

    m_incompleteSet.clear();
    m_completeSet = initialSet();
con's avatar
con committed
    m_displaySet = m_completeSet;
con's avatar
con committed
#if 0
    for (EditWindows::ConstIterator it = m_editWindows.begin();
            it != m_editWindows.end(); ++it) {
        if (!it.value().isNull())
            delete it.value();
    }
    m_editWindows.clear();
#endif
    emit reset();
}

void WatchHandler::collapseChildren(const QModelIndex &idx)
{
    if (m_inChange || m_completeSet.isEmpty()) {
        qDebug() << "WATCHHANDLER: COLLAPSE IGNORED" << idx;
con's avatar
con committed
        return;
    }
hjk's avatar
hjk committed
    QTC_ASSERT(checkIndex(idx.internalId()), return);
con's avatar
con committed
#if 0
    QString iname0 = m_displaySet.at(idx.internalId()).iname;
    MODEL_DEBUG("COLLAPSE NODE" << iname0);
    QString iname1 = iname0 + '.';
    for (int i = m_completeSet.size(); --i >= 0; ) {
        QString iname = m_completeSet.at(i).iname;
        if (iname.startsWith(iname1)) {
            // Better leave it in in case the user re-enters the branch?
            (void) m_completeSet.takeAt(i);
            MODEL_DEBUG(" REMOVING " << iname);
            m_expandedINames.remove(iname);
        }
    }
    m_expandedINames.remove(iname0);
    //MODEL_DEBUG(toString());
    //rebuildModel();
#endif
}

void WatchHandler::expandChildren(const QModelIndex &idx)
{
    if (m_inChange || m_completeSet.isEmpty()) {
        //qDebug() << "WATCHHANDLER: EXPAND IGNORED" << idx;
        return;
    }
    int index = idx.internalId();
    if (index == 0)
        return;
hjk's avatar
hjk committed
    QTC_ASSERT(index >= 0, qDebug() << toString() << index; return);
    QTC_ASSERT(index < m_completeSet.size(), qDebug() << toString() << index; return);
con's avatar
con committed
    const WatchData &display = m_displaySet.at(index);
hjk's avatar
hjk committed
    QTC_ASSERT(index >= 0, qDebug() << toString() << index; return);
    QTC_ASSERT(index < m_completeSet.size(), qDebug() << toString() << index; return);
con's avatar
con committed
    const WatchData &complete = m_completeSet.at(index);
    MODEL_DEBUG("\n\nEXPAND" << display.iname);
    if (display.iname.isEmpty()) {
        // This should not happen but the view seems to send spurious
        // "expand()" signals folr the root item from time to time.
        // Try to handle that gracfully.
        //MODEL_DEBUG(toString());
        qDebug() << "FIXME: expandChildren, no data " << display.iname << "found"
            << idx;
        //rebuildModel();
        return;
    }

    //qDebug() << "   ... NODE: " << display.toString()
    //         << complete.childIndex.size() << complete.childCount;

    if (m_expandedINames.contains(display.iname))
        return;

    // This is a performance hack and not strictly necessary.
    // Remove it if there are troubles when expanding nodes.
    if (0 && complete.childCount > 0 && complete.childIndex.size() > 0) {
        MODEL_DEBUG("SKIP FETCHING CHILDREN");
        return;
    }

    WatchData data = takeData(display.iname); // remove previous data
    m_expandedINames.insert(data.iname);
    if (data.iname.contains('.')) // not for top-level items
        data.setChildrenNeeded();
    insertData(data);
    emit watchModelUpdateRequested();
}

void WatchHandler::insertData(const WatchData &data)
{
    //MODEL_DEBUG("INSERTDATA: " << data.toString());
hjk's avatar
hjk committed
    QTC_ASSERT(data.isValid(), return);
con's avatar
con committed
    if (data.isSomethingNeeded())
        insertDataHelper(m_incompleteSet, data);
    else
        insertDataHelper(m_completeSet, data);
    //MODEL_DEBUG("INSERT RESULT" << toString());
}

void WatchHandler::watchExpression(const QString &exp)
{
    // FIXME: 'exp' can contain illegal characters
    //MODEL_DEBUG("WATCH: " << exp);
    WatchData data;
    data.exp = exp;
    data.name = exp;
    data.iname = "watch." + exp;
    insertData(data);
    m_watchers.append(exp);
    saveWatchers();
    emit watchModelUpdateRequested();
con's avatar
con committed
}

void WatchHandler::setDisplayedIName(const QString &iname, bool on)
{
    WatchData *d = findData(iname);
    if (!on || !d) {
        delete m_editWindows.take(iname);
        m_displayedINames.remove(iname);
        return;
    }
    if (d->exp.isEmpty()) {
        //emit statusMessageRequested(tr("Sorry. Cannot visualize objects without known address."), 5000);
        return;
    }
    d->setValueNeeded();
    m_displayedINames.insert(iname);
    insertData(*d);
}

void WatchHandler::showEditValue(const WatchData &data)
{
    // editvalue is always base64 encoded
    QByteArray ba = QByteArray::fromBase64(data.editvalue);
    //QByteArray ba = data.editvalue;
    QWidget *w = m_editWindows.value(data.iname);
    qDebug() << "SHOW_EDIT_VALUE " << data.toString() << data.type
            << data.iname << w;
    if (data.type == "QImage") {
        if (!w) {
            w = new QLabel;
            m_editWindows[data.iname] = w;
        }
        QDataStream ds(&ba, QIODevice::ReadOnly);
        QVariant v;
        ds >> v;
        QString type = QString::fromAscii(v.typeName());
        QImage im = v.value<QImage>();
        if (QLabel *l = qobject_cast<QLabel *>(w))
            l->setPixmap(QPixmap::fromImage(im));
    } else if (data.type == "QPixmap") {
        if (!w) {
            w = new QLabel;
            m_editWindows[data.iname] = w;
        }
        QDataStream ds(&ba, QIODevice::ReadOnly);
        QVariant v;
        ds >> v;
        QString type = QString::fromAscii(v.typeName());
        QPixmap im = v.value<QPixmap>();
        if (QLabel *l = qobject_cast<QLabel *>(w))
            l->setPixmap(im);
    } else if (data.type == "QString") {
        if (!w) {
            w = new QTextEdit;
            m_editWindows[data.iname] = w;
        }
#if 0
        QDataStream ds(&ba, QIODevice::ReadOnly);
        QVariant v;
        ds >> v;
        QString type = QString::fromAscii(v.typeName());
        QString str = v.value<QString>();
#else
        MODEL_DEBUG("DATA: " << ba);
        QString str = QString::fromUtf16((ushort *)ba.constData(), ba.size()/2);
#endif
        if (QTextEdit *t = qobject_cast<QTextEdit *>(w))
            t->setText(str);
    }
    if (w)
        w->show();
}

void WatchHandler::removeWatchExpression(const QString &exp)
con's avatar
con committed
{
    MODEL_DEBUG("REMOVE WATCH: " << iname);
    m_watchers.removeOne(exp);
    for (int i = m_completeSet.size(); --i >= 0;) {
        const WatchData & data = m_completeSet.at(i);
        if (data.iname.startsWith("watch.") && data.exp == exp) {
            m_completeSet.takeAt(i);
            break;
        }
    }
    saveWatchers();
con's avatar
con committed
    emit watchModelUpdateRequested();
}

void WatchHandler::reinitializeWatchers()
{
    m_completeSet = initialSet();
    m_incompleteSet.clear();
    reinitializeWatchersHelper();
}
con's avatar
con committed

void WatchHandler::reinitializeWatchersHelper()
{
con's avatar
con committed
    // copy over all watchers and mark all watchers as incomplete
    int i = 0;
    foreach (const QString &exp, m_watchers) {
        WatchData data;
        data.level = -1;
        data.row = -1;
        data.parentIndex = -1;
        data.variable.clear();
        data.setAllNeeded();