watchhandler.cpp 41.1 KB
Newer Older
1
/**************************************************************************
con's avatar
con committed
2 3 4
**
** This file is part of Qt Creator
**
5
** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
con's avatar
con committed
6
**
7
** Contact: Nokia Corporation (qt-info@nokia.com)
con's avatar
con committed
8
**
9
** Commercial Usage
10
**
11 12 13 14
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
15
**
16
** GNU Lesser General Public License Usage
17
**
18 19 20 21 22 23
** 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.
24
**
25
** If you are unsure which license is appropriate for your use, please
hjk's avatar
hjk committed
26
** contact the sales department at http://qt.nokia.com/contact.
con's avatar
con committed
27
**
28
**************************************************************************/
hjk's avatar
hjk committed
29

con's avatar
con committed
30
#include "watchhandler.h"
31
#include "watchutils.h"
32
#include "debuggeractions.h"
con's avatar
con committed
33 34 35 36 37

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

hjk's avatar
hjk committed
38
#include <utils/qtcassert.h>
con's avatar
con committed
39 40 41

#include <QtCore/QDebug>
#include <QtCore/QEvent>
42
#include <QtCore/QtAlgorithms>
43
#include <QtCore/QTextStream>
hjk's avatar
hjk committed
44
#include <QtCore/QTimer>
con's avatar
con committed
45

46
#include <QtGui/QAction>
con's avatar
con committed
47 48 49 50 51 52 53
#include <QtGui/QApplication>
#include <QtGui/QLabel>
#include <QtGui/QToolTip>
#include <QtGui/QTextEdit>

#include <ctype.h>

hjk's avatar
hjk committed
54

55 56
// creates debug output for accesses to the model
//#define DEBUG_MODEL 1
con's avatar
con committed
57 58 59 60 61 62 63 64

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

hjk's avatar
hjk committed
65 66
namespace Debugger {
namespace Internal {
con's avatar
con committed
67

68
static const QString strNotInScope =
69
    QCoreApplication::translate("Debugger::Internal::WatchData", "<not in scope>");
70

71
static int watcherCounter = 0;
72 73
static int generationCounter = 0;

con's avatar
con committed
74 75
////////////////////////////////////////////////////////////////////
//
76
// WatchItem
con's avatar
con committed
77 78
//
////////////////////////////////////////////////////////////////////
79

80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
class WatchItem : public WatchData
{
public:
    WatchItem() { parent = 0; fetchTriggered = false; }

    WatchItem(const WatchData &data) : WatchData(data)
        { parent = 0; fetchTriggered = false; }

    void setData(const WatchData &data)
        { static_cast<WatchData &>(*this) = data; }

    WatchItem *parent;
    bool fetchTriggered;      // children fetch has been triggered
    QList<WatchItem *> children;  // fetched children
};

////////////////////////////////////////////////////////////////////
//
// WatchData
//
////////////////////////////////////////////////////////////////////
   
102 103 104
WatchData::WatchData() :
    hasChildren(false),
    generation(-1),
105 106
    valueEnabled(true),
    valueEditable(true),
107 108 109
    source(0),
    state(InitialState),
    changed(false)
con's avatar
con committed
110 111 112
{
}

113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
bool WatchData::isEqual(const WatchData &other) const
{
    return iname == other.iname
      && exp == other.exp
      && name == other.name
      && value == other.value
      && editvalue == other.editvalue
      && valuetooltip == other.valuetooltip
      && type == other.type
      && displayedType == other.displayedType
      && variable == other.variable
      && addr == other.addr
      && saddr == other.saddr
      && framekey == other.framekey
      && hasChildren == other.hasChildren
      && valueEnabled == other.valueEnabled
      && valueEditable == other.valueEditable;
}

con's avatar
con committed
132 133 134 135
void WatchData::setError(const QString &msg)
{
    setAllUnneeded();
    value = msg;
136
    setHasChildren(false);
137 138
    valueEnabled = false;
    valueEditable = false;
con's avatar
con committed
139 140
}

hjk's avatar
hjk committed
141
void WatchData::setValue(const QString &value0)
con's avatar
con committed
142
{
hjk's avatar
hjk committed
143
    value = value0;
con's avatar
con committed
144 145
    if (value == "{...}") {
        value.clear();
146
        hasChildren = true; // at least one...
con's avatar
con committed
147 148 149 150 151 152 153 154 155 156 157
    }

    // 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);
158
        setHasChildren(false);
con's avatar
con committed
159 160 161 162 163
    }

    // "numchild" is sometimes lying
    //MODEL_DEBUG("\n\n\nPOINTER: " << type << value);
    if (isPointerType(type))
164
        setHasChildren(value != "0x0" && value != "<null>");
con's avatar
con committed
165 166 167 168 169

    // 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);
170
    
con's avatar
con committed
171 172 173 174 175 176 177 178 179 180 181 182 183
    setValueUnneeded();
}

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

void WatchData::setType(const QString &str)
{
    type = str.trimmed();
    bool changed = true;
    while (changed) {
184
        if (type.endsWith(QLatin1String("const")))
con's avatar
con committed
185
            type.chop(5);
186
        else if (type.endsWith(QLatin1Char(' ')))
con's avatar
con committed
187
            type.chop(1);
188
        else if (type.endsWith(QLatin1Char('&')))
con's avatar
con committed
189
            type.chop(1);
190
        else if (type.startsWith(QLatin1String("const ")))
con's avatar
con committed
191
            type = type.mid(6);
192
        else if (type.startsWith(QLatin1String("volatile ")))
con's avatar
con committed
193
            type = type.mid(9);
194
        else if (type.startsWith(QLatin1String("class ")))
con's avatar
con committed
195
            type = type.mid(6);
196
        else if (type.startsWith(QLatin1String("struct ")))
con's avatar
con committed
197
            type = type.mid(6);
198
        else if (type.startsWith(QLatin1Char(' ')))
con's avatar
con committed
199 200 201 202 203
            type = type.mid(1);
        else
            changed = false;
    }
    setTypeUnneeded();
204 205 206 207 208 209 210 211 212 213 214
    switch (guessChildren(type)) {
        case HasChildren:
            setHasChildren(true);
            break;
        case HasNoChildren:
            setHasChildren(false);
            break;
        case HasPossiblyChildren:
            setHasChildren(true); // FIXME: bold assumption
            break;
    }
con's avatar
con committed
215 216
}

217
void WatchData::setAddress(const QString &str)
con's avatar
con committed
218 219 220 221 222 223
{
    addr = str;
}

QString WatchData::toString() const
{
224 225 226
    const char *doubleQuoteComma = "\",";
    QString res;
    QTextStream str(&res);
227
    str << QLatin1Char('{');
con's avatar
con committed
228
    if (!iname.isEmpty())
229
        str << "iname=\"" << iname << doubleQuoteComma;
230 231
    if (!name.isEmpty() && name != iname)
        str << "name=\"" << name << doubleQuoteComma;
232 233
    if (!addr.isEmpty())
        str << "addr=\"" << addr << doubleQuoteComma;
con's avatar
con committed
234
    if (!exp.isEmpty())
235
        str << "exp=\"" << exp << doubleQuoteComma;
con's avatar
con committed
236 237

    if (!variable.isEmpty())
238
        str << "variable=\"" << variable << doubleQuoteComma;
con's avatar
con committed
239 240

    if (isValueNeeded())
241
        str << "value=<needed>,";
con's avatar
con committed
242
    if (isValueKnown() && !value.isEmpty())
243
        str << "value=\"" << value << doubleQuoteComma;
con's avatar
con committed
244 245

    if (!editvalue.isEmpty())
246
        str << "editvalue=\"" << editvalue << doubleQuoteComma;
con's avatar
con committed
247 248

    if (isTypeNeeded())
249
        str << "type=<needed>,";
con's avatar
con committed
250
    if (isTypeKnown() && !type.isEmpty())
251
        str << "type=\"" << type << doubleQuoteComma;
con's avatar
con committed
252

253 254 255 256
    if (isHasChildrenNeeded())
        str << "hasChildren=<needed>,";
    if (isHasChildrenKnown())
        str << "hasChildren=\"" << (hasChildren ? "true" : "false") << doubleQuoteComma;
con's avatar
con committed
257 258

    if (isChildrenNeeded())
259 260 261 262 263
        str << "children=<needed>,";
    str.flush();
    if (res.endsWith(QLatin1Char(',')))
        res.truncate(res.size() - 1);
    return res + QLatin1Char('}');
con's avatar
con committed
264 265
}

266
// Format a tooltip fow with aligned colon
267
static void formatToolTipRow(QTextStream &str, const QString &category, const QString &value)
268
{
269 270
    str << "<tr><td>" << category << "</td><td> : </td><td>"
        << Qt::escape(value) << "</td></tr>";
271 272
}

273 274 275 276 277 278 279 280 281 282 283
static inline QString typeToolTip(const WatchData &wd)
{
    if (wd.displayedType.isEmpty())
        return wd.type;
    QString rc = wd.displayedType;
    rc += QLatin1String(" (");
    rc += wd.type;
    rc += QLatin1Char(')');
    return rc;
}

284 285
QString WatchData::toToolTip() const
{
hjk's avatar
hjk committed
286 287
    if (!valuetooltip.isEmpty())
        return QString::number(valuetooltip.size());
288 289 290
    QString res;
    QTextStream str(&res);
    str << "<html><body><table>";
291
    formatToolTipRow(str, WatchHandler::tr("Expression"), exp);
292
    formatToolTipRow(str, WatchHandler::tr("Type"), typeToolTip(*this));
293 294 295 296 297
    QString val = value;
    if (value.size() > 1000) {
        val.truncate(1000);
        val +=  WatchHandler::tr(" ... <cut off>");
    }
298 299 300 301
    formatToolTipRow(str, WatchHandler::tr("Value"), val);
    formatToolTipRow(str, WatchHandler::tr("Object Address"), addr);
    formatToolTipRow(str, WatchHandler::tr("Stored Address"), saddr);
    formatToolTipRow(str, WatchHandler::tr("Internal ID"), iname);
302 303
    formatToolTipRow(str, WatchHandler::tr("Generation"),
        QString::number(generation));
304 305 306 307
    str << "</table></body></html>";
    return res;
}

308
///////////////////////////////////////////////////////////////////////
hjk's avatar
hjk committed
309
//
310
// WatchModel
hjk's avatar
hjk committed
311
//
312
///////////////////////////////////////////////////////////////////////
hjk's avatar
hjk committed
313

314 315
WatchModel::WatchModel(WatchHandler *handler, WatchType type)
    : QAbstractItemModel(handler), m_handler(handler), m_type(type)
hjk's avatar
hjk committed
316
{
317 318 319 320
    m_root = new WatchItem;
    m_root->hasChildren = 1;
    m_root->state = 0;
    m_root->name = WatchHandler::tr("Root");
321 322
    m_root->parent = 0;
    m_root->fetchTriggered = true;
hjk's avatar
hjk committed
323

324 325
    switch (m_type) {
        case LocalsWatch:
326 327
            m_root->iname = QLatin1String("local");
            m_root->name = WatchHandler::tr("Locals");
328 329
            break;
        case WatchersWatch:
330 331
            m_root->iname = QLatin1String("watch");
            m_root->name = WatchHandler::tr("Watchers");
332 333
            break;
        case TooltipsWatch:
334 335
            m_root->iname = QLatin1String("tooltip");
            m_root->name = WatchHandler::tr("Tooltip");
336 337
            break;
    }
hjk's avatar
hjk committed
338 339
}

340
WatchItem *WatchModel::rootItem() const
hjk's avatar
hjk committed
341
{
342
    return m_root;
hjk's avatar
hjk committed
343 344
}

345
void WatchModel::reinitialize()
hjk's avatar
hjk committed
346
{
347
    int n = m_root->children.size();
348 349
    if (n == 0)
        return;
350 351
    //MODEL_DEBUG("REMOVING " << n << " CHILDREN OF " << m_root->iname);
    QModelIndex index = watchIndex(m_root);
352
    beginRemoveRows(index, 0, n - 1);
353 354
    qDeleteAll(m_root->children);
    m_root->children.clear();
355
    endRemoveRows();
hjk's avatar
hjk committed
356 357
}

358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384
void WatchModel::beginCycle()
{
    emit enableUpdates(false);
}

void WatchModel::endCycle()
{
    removeOutdated();
    emit enableUpdates(true);
}

void WatchModel::dump()
{
    qDebug() << "\n";
    foreach (WatchItem *child, m_root->children)
        dumpHelper(child);
}

void WatchModel::dumpHelper(WatchItem *item)
{
    qDebug() << "ITEM: " << item->iname
        << (item->parent ? item->parent->iname : "<none>")
        << item->generation;
    foreach (WatchItem *child, item->children)
        dumpHelper(child);
}

385
void WatchModel::removeOutdated()
hjk's avatar
hjk committed
386
{
387
    foreach (WatchItem *child, m_root->children)
388 389 390 391 392 393
        removeOutdatedHelper(child);
#if DEBUG_MODEL
#if USE_MODEL_TEST
    //(void) new ModelTest(this, this);
#endif
#endif
hjk's avatar
hjk committed
394 395
}

396
void WatchModel::removeOutdatedHelper(WatchItem *item)
hjk's avatar
hjk committed
397
{
398
    if (item->generation < generationCounter) {
399
        removeItem(item);
400
    } else {
401 402 403
        foreach (WatchItem *child, item->children)
            removeOutdatedHelper(child);
        item->fetchTriggered = false;
404
    }
hjk's avatar
hjk committed
405 406
}

407
void WatchModel::removeItem(WatchItem *item)
con's avatar
con committed
408
{
409 410 411 412 413 414 415
    WatchItem *parent = item->parent;
    QModelIndex index = watchIndex(parent);
    int n = parent->children.indexOf(item);
    //MODEL_DEBUG("NEED TO REMOVE: " << item->iname << "AT" << n);
    beginRemoveRows(index, n, n);
    parent->children.removeAt(n);
    endRemoveRows();
416 417
}

418
static QString parentName(const QString &iname)
419
{
420
    int pos = iname.lastIndexOf(QLatin1Char('.'));
con's avatar
con committed
421 422 423 424 425
    if (pos == -1)
        return QString();
    return iname.left(pos);
}

426

427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443
static QString chopConst(QString type)
{
   while (1) {
        if (type.startsWith("const"))
            type = type.mid(5);
        else if (type.startsWith(' '))
            type = type.mid(1);
        else if (type.endsWith("const"))
            type.chop(5);
        else if (type.endsWith(' '))
            type.chop(1);
        else
            break;
    }
    return type;
}

444
static inline QRegExp stdStringRegExp(const QString &charType)
con's avatar
con committed
445
{
446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461
    QString rc = QLatin1String("basic_string<");
    rc += charType;
    rc += QLatin1String(",[ ]?std::char_traits<");
    rc += charType;
    rc += QLatin1String(">,[ ]?std::allocator<");
    rc += charType;
    rc += QLatin1String("> >");
    const QRegExp re(rc);
    Q_ASSERT(re.isValid());
    return re;
}

QString niceType(const QString typeIn)
{
    static QMap<QString, QString> cache;
    const QMap<QString, QString>::const_iterator it = cache.constFind(typeIn);
462
    if (it != cache.constEnd())
463
        return it.value();
464

465 466
    QString type = typeIn;
    type.replace(QLatin1Char('*'), QLatin1Char('@'));
467 468 469 470

    for (int i = 0; i < 10; ++i) {
        int start = type.indexOf("std::allocator<");
        if (start == -1)
471
            break; 
472 473 474 475 476 477 478 479 480 481 482 483
        // search for matching '>'
        int pos;
        int level = 0;
        for (pos = start + 12; pos < type.size(); ++pos) {
            int c = type.at(pos).unicode();
            if (c == '<') {
                ++level;
            } else if (c == '>') {
                --level;
                if (level == 0)
                    break;
            }
484
        }
485 486 487
        QString alloc = type.mid(start, pos + 1 - start).trimmed();
        QString inner = alloc.mid(15, alloc.size() - 16).trimmed();

488
        if (inner == QLatin1String("char")) { // std::string
489
            const QRegExp stringRegexp = stdStringRegExp(inner);
490 491
            type.replace(stringRegexp, QLatin1String("string"));
        } else if (inner == QLatin1String("wchar_t")) { // std::wstring
492
            const QRegExp wchartStringRegexp = stdStringRegExp(inner);
493 494
            type.replace(wchartStringRegexp, QLatin1String("wstring"));
        } else if (inner == QLatin1String("unsigned short")) { // std::wstring/MSVC
495
            const QRegExp usStringRegexp = stdStringRegExp(inner);
496 497
            type.replace(usStringRegexp, QLatin1String("wstring"));
        }
498
        // std::vector, std::deque, std::list
499
        const QRegExp re1(QString::fromLatin1("(vector|list|deque)<%1, ?%2\\s*>").arg(inner, alloc));
500
        Q_ASSERT(re1.isValid());
501
        if (re1.indexIn(type) != -1)
502
            type.replace(re1.cap(0), QString::fromLatin1("%1<%2>").arg(re1.cap(1), inner));
503 504

        // std::stack
505
        QRegExp re6(QString::fromLatin1("stack<%1, ?std::deque<%2> >").arg(inner, inner));
506 507 508
        if (!re6.isMinimal())
            re6.setMinimal(true);
        Q_ASSERT(re6.isValid());
509
        if (re6.indexIn(type) != -1)
510
            type.replace(re6.cap(0), QString::fromLatin1("stack<%1>").arg(inner));
511 512

        // std::set
513
        QRegExp re4(QString::fromLatin1("set<%1, ?std::less<%2>, ?%3\\s*>").arg(inner, inner, alloc));
514 515 516
        if (!re4.isMinimal())
            re4.setMinimal(true);
        Q_ASSERT(re4.isValid());
517
        if (re4.indexIn(type) != -1)
518
            type.replace(re4.cap(0), QString::fromLatin1("set<%1>").arg(inner));
con's avatar
con committed
519

520
        // std::map
521 522 523 524 525 526 527 528 529 530 531
        if (inner.startsWith("std::pair<")) {
            // search for outermost ','
            int pos;
            int level = 0;
            for (pos = 10; pos < inner.size(); ++pos) {
                int c = inner.at(pos).unicode();
                if (c == '<')
                    ++level;
                else if (c == '>')
                    --level;
                else if (c == ',' && level == 0)
532
                    break;
533 534 535
            }
            QString ckey = inner.mid(10, pos - 10);
            QString key = chopConst(ckey);
536 537
            QString value = inner.mid(pos + 2, inner.size() - 3 - pos).trimmed();
            QRegExp re5(QString("map<%1, ?%2, ?std::less<%3 ?>, ?%4\\s*>")
538
                .arg(key, value, key, alloc));
539 540 541
            if (!re5.isMinimal())
                re5.setMinimal(true);
            Q_ASSERT(re5.isValid());
542
            if (re5.indexIn(type) != -1) {
543
                type.replace(re5.cap(0), QString("map<%1, %2>").arg(key, value));
544 545
            } else {
                QRegExp re7(QString("map<const %1, ?%2, ?std::less<const %3>, ?%4\\s*>")
546
                    .arg(key, value, key, alloc));
547 548
                if (!re7.isMinimal())
                    re7.setMinimal(true);
549 550
                if (re7.indexIn(type) != -1)
                    type.replace(re7.cap(0), QString("map<const %1, %2>").arg(key, value));
551
            }
552
        }
con's avatar
con committed
553
    }
554
    type.replace(QLatin1Char('@'), QLatin1Char('*'));
555
    type.replace(QLatin1String(" >"), QString(QLatin1Char('>')));
556
    cache.insert(typeIn, type); // For simplicity, also cache unmodified types
con's avatar
con committed
557 558 559
    return type;
}

560 561 562
static QString formattedValue(const WatchData &data,
    int individualFormat, int typeFormat)
{
563
    if (isIntType(data.type)) {
564 565
        int format = individualFormat == -1 ? typeFormat : individualFormat;
        int value = data.value.toInt();
566
        if (format == HexadecimalFormat)
567
            return ("(hex) ") + QString::number(value, 16);
568
        if (format == BinaryFormat)
569
            return ("(bin) ") + QString::number(value, 2);
570
        if (format == OctalFormat)
571
            return ("(oct) ") + QString::number(value, 8);
572
        return data.value;
573 574 575 576 577
    }

    return data.value;
}

578
bool WatchModel::canFetchMore(const QModelIndex &index) const
hjk's avatar
hjk committed
579
{
580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601
    return index.isValid() && !watchItem(index)->fetchTriggered;
}

void WatchModel::fetchMore(const QModelIndex &index)
{
    QTC_ASSERT(index.isValid(), return);
    QTC_ASSERT(!watchItem(index)->fetchTriggered, return);
    if (WatchItem *item = watchItem(index)) {
        item->fetchTriggered = true;
        WatchData data = *item;
        data.setChildrenNeeded();
        emit m_handler->watchDataUpdateNeeded(data);
    }
}

QModelIndex WatchModel::index(int row, int column, const QModelIndex &parent) const
{
    if (!hasIndex(row, column, parent))
        return QModelIndex();

    const WatchItem *item = watchItem(parent);
    QTC_ASSERT(item, return QModelIndex());
602 603
    if (row >= item->children.size())
        return QModelIndex();
604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624
    return createIndex(row, column, (void*)(item->children.at(row)));
}

QModelIndex WatchModel::parent(const QModelIndex &idx) const
{
    if (!idx.isValid())
        return QModelIndex();

    const WatchItem *item = watchItem(idx);
    if (!item->parent || item->parent == m_root)
        return QModelIndex();

    const WatchItem *grandparent = item->parent->parent;
    if (!grandparent)
        return QModelIndex();

    for (int i = 0; i < grandparent->children.size(); ++i)
        if (grandparent->children.at(i) == item->parent)
            return createIndex(i, 0, (void*) item->parent);

    return QModelIndex();
hjk's avatar
hjk committed
625 626 627 628 629 630 631 632 633
}

int WatchModel::rowCount(const QModelIndex &idx) const
{
    if (idx.column() > 0)
        return 0;
    return watchItem(idx)->children.size();
}

634
int WatchModel::columnCount(const QModelIndex &idx) const
hjk's avatar
hjk committed
635
{
636
    Q_UNUSED(idx)
637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667
    return 3;
}

bool WatchModel::hasChildren(const QModelIndex &parent) const
{
    WatchItem *item = watchItem(parent);
    return !item || item->hasChildren;
}

WatchItem *WatchModel::watchItem(const QModelIndex &idx) const
{
    return idx.isValid() 
        ? static_cast<WatchItem*>(idx.internalPointer()) : m_root;
}

QModelIndex WatchModel::watchIndex(const WatchItem *item) const
{
    return watchIndexHelper(item, m_root, QModelIndex());
}

QModelIndex WatchModel::watchIndexHelper(const WatchItem *needle, 
    const WatchItem *parentItem, const QModelIndex &parentIndex) const
{
    if (needle == parentItem)
        return parentIndex;
    for (int i = parentItem->children.size(); --i >= 0; ) {
        const WatchItem *childItem = parentItem->children.at(i);
        QModelIndex childIndex = index(i, 0, parentIndex);
        QModelIndex idx = watchIndexHelper(needle, childItem, childIndex);
        if (idx.isValid())
            return idx;
hjk's avatar
hjk committed
668
    }
669
    return QModelIndex();
hjk's avatar
hjk committed
670 671
}

672
void WatchModel::emitDataChanged(int column, const QModelIndex &parentIndex) 
673 674 675 676 677 678 679 680 681 682 683
{
    QModelIndex idx1 = index(0, column, parentIndex);
    QModelIndex idx2 = index(rowCount(parentIndex) - 1, column, parentIndex);
    if (idx1.isValid() && idx2.isValid())
        emit dataChanged(idx1, idx2);
    //qDebug() << "CHANGING:\n" << idx1 << "\n" << idx2 << "\n"
    //    << data(parentIndex, INameRole).toString();
    for (int i = rowCount(parentIndex); --i >= 0; )
        emitDataChanged(column, index(i, 0, parentIndex));
}

hjk's avatar
hjk committed
684 685
QVariant WatchModel::data(const QModelIndex &idx, int role) const
{
686
    const WatchItem &data = *watchItem(idx);
con's avatar
con committed
687 688 689

    switch (role) {
        case Qt::DisplayRole: {
690
            switch (idx.column()) {
con's avatar
con committed
691
                case 0: return data.name;
692 693 694
                case 1: return formattedValue(data,
                    m_handler->m_individualFormats[data.iname],
                    m_handler->m_typeFormats[data.type]);
695 696 697 698
                case 2:
                    if (!data.displayedType.isEmpty())
                        return data.displayedType;
                    return niceType(data.type);
con's avatar
con committed
699 700 701 702 703
                default: break;
            }
            break;
        }

704
        case Qt::ToolTipRole:
705 706
            return theDebuggerBoolSetting(UseToolTipsInLocalsView)
                ? data.toToolTip() : QVariant();
con's avatar
con committed
707 708 709 710

        case Qt::ForegroundRole: {
            static const QVariant red(QColor(200, 0, 0));
            static const QVariant gray(QColor(140, 140, 140));
711
            switch (idx.column()) {
712
                case 1: return !data.valueEnabled ? gray : data.changed ? red : QVariant();
con's avatar
con committed
713 714 715 716
            }
            break;
        }

717 718 719
        case ExpressionRole:
            return data.exp;

con's avatar
con committed
720 721 722
        case INameRole:
            return data.iname;

723
        case ExpandedRole:
hjk's avatar
hjk committed
724 725
            return m_handler->m_expandedINames.contains(data.iname);
            //FIXME return node < 4 || m_expandedINames.contains(data.iname);
726 727 728 729

        case ActiveDataRole:
            qDebug() << "ASK FOR" << data.iname;
            return true;
730
   
731
        case TypeFormatListRole:
732
            if (isIntType(data.type))
733 734 735 736 737 738 739 740 741 742 743 744 745
                return QStringList() << tr("decimal") << tr("hexadecimal")
                    << tr("binary") << tr("octal");
            break;

        case TypeFormatRole:
            return m_handler->m_typeFormats[data.type];

        case IndividualFormatRole: {
            int format = m_handler->m_individualFormats[data.iname];
            if (format == -1)
                return m_handler->m_typeFormats[data.type];
            return format;
        }
746 747 748 749 750 751 752 753 754 755
        
        case AddressRole: {
            if (!data.addr.isEmpty())
                return data.addr;
            bool ok;
            (void) data.value.toULongLong(&ok, 0);
            if (ok)
                return data.value;
            return QVariant();
        }
756

con's avatar
con committed
757
        default:
758
            break; 
con's avatar
con committed
759 760 761 762
    }
    return QVariant();
}

hjk's avatar
hjk committed
763
bool WatchModel::setData(const QModelIndex &index, const QVariant &value, int role)
764
{
765
    WatchItem &data = *watchItem(index);
hjk's avatar
hjk committed
766 767
    if (role == ExpandedRole) {
        if (value.toBool())
768
            m_handler->m_expandedINames.insert(data.iname);
hjk's avatar
hjk committed
769
        else
770 771 772 773 774
            m_handler->m_expandedINames.remove(data.iname);
    } else if (role == TypeFormatRole) {
        m_handler->setFormat(data.type, value.toInt());
    } else if (role == IndividualFormatRole) {
        m_handler->m_individualFormats[data.iname] = value.toInt();
hjk's avatar
hjk committed
775
    }
776 777 778 779
    emit dataChanged(index, index);
    return true;
}

hjk's avatar
hjk committed
780
Qt::ItemFlags WatchModel::flags(const QModelIndex &idx) const
con's avatar
con committed
781 782 783 784 785 786 787 788 789
{
    using namespace Qt;

    if (!idx.isValid())
        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.

790
    static const ItemFlags notEditable =
con's avatar
con committed
791 792 793 794 795 796 797
          ItemIsSelectable
        | ItemIsDragEnabled
        | ItemIsDropEnabled
        // | ItemIsUserCheckable
        // | ItemIsTristate
        | ItemIsEnabled;

798
    static const ItemFlags editable = notEditable | ItemIsEditable;
con's avatar
con committed
799

hjk's avatar
hjk committed
800
    const WatchData &data = *watchItem(idx);
801 802

    if (data.isWatcher() && idx.column() == 0)
hjk's avatar
hjk committed
803 804 805
        return editable; // watcher names are editable
    if (data.isWatcher() && idx.column() == 2)
        return editable; // watcher types are
806 807 808
    if (idx.column() == 1 && data.valueEditable)
        return editable; // locals and watcher values are sometimes editable
    return notEditable;
con's avatar
con committed
809 810
}

hjk's avatar
hjk committed
811
QVariant WatchModel::headerData(int section, Qt::Orientation orientation, int role) const
con's avatar
con committed
812 813 814 815 816
{
    if (orientation == Qt::Vertical)
        return QVariant();
    if (role == Qt::DisplayRole) {
        switch (section) {
hjk's avatar
hjk committed
817 818 819
            case 0: return QString(tr("Name")  + QLatin1String("     "));
            case 1: return QString(tr("Value") + QLatin1String("     "));
            case 2: return QString(tr("Type")  + QLatin1String("     "));
con's avatar
con committed
820 821
        }
    }
822
    return QVariant(); 
con's avatar
con committed
823 824
}

825
struct IName : public QString
826
{
827 828 829
    IName(const QString &iname) : QString(iname) {}
};

830
bool iNameLess(const QString &iname1, const QString &iname2)
831 832 833
{
    QString name1 = iname1.section('.', -1);
    QString name2 = iname2.section('.', -1);
834
    if (!name1.isEmpty() && !name2.isEmpty()) {
hjk's avatar
hjk committed
835 836 837 838 839 840
        if (name1.at(0).isDigit() && name2.at(0).isDigit()) {
            bool ok1 = false, ok2 = false;
            int i1 = name1.toInt(&ok1), i2 = name2.toInt(&ok2);
            if (ok1 && ok2)
                return i1 < i2;
        }
841
    }
842
    return name1 < name2; 
843 844
}

845 846 847 848
bool operator<(const IName &iname1, const IName &iname2)
{
    return iNameLess(iname1, iname2);
}
849 850 851

static bool iNameSorter(const WatchItem *item1, const WatchItem *item2)
{
852
    return iNameLess(item1->iname, item2->iname);
853 854
}

855
static int findInsertPosition(const QList<WatchItem *> &list, const WatchItem *item)
856
{
857 858 859
    QList<WatchItem *>::const_iterator it =
        qLowerBound(list.begin(), list.end(), item, iNameSorter);
    return it - list.begin(); 
860 861
}

862
void WatchModel::insertData(const WatchData &data)
con's avatar
con committed
863
{
864
    // qDebug() << "WMI:" << data.toString();
865
    //static int bulk = 0;
hjk's avatar
hjk committed
866
    //qDebug() << "SINGLE: " << ++bulk << data.toString();
867 868 869 870 871 872 873
    QTC_ASSERT(!data.iname.isEmpty(), return);
    WatchItem *parent = findItem(parentName(data.iname), m_root);
    if (!parent) {
        WatchData parent;
        parent.iname = parentName(data.iname);
        insertData(parent);
        //MODEL_DEBUG("\nFIXING MISSING PARENT FOR\n" << data.iname);
hjk's avatar
hjk committed
874 875
        return;
    }
876 877 878 879 880 881 882 883 884 885 886 887 888 889
    QModelIndex index = watchIndex(parent);
    if (WatchItem *oldItem = findItem(data.iname, parent)) {
        // overwrite old entry
        //MODEL_DEBUG("OVERWRITE : " << data.iname << data.value);
        bool changed = !data.value.isEmpty()
            && data.value != oldItem->value
            && data.value != strNotInScope;
        oldItem->setData(data);
        oldItem->changed = changed;
        oldItem->generation = generationCounter;
        QModelIndex idx = watchIndex(oldItem);
        emit dataChanged(idx, idx.sibling(idx.row(), 2));
    } else {
        // add new entry
890
        //MODEL_DEBUG("ADD : " << data.iname << data.value);
891 892 893 894 895 896 897 898 899
        WatchItem *item = new WatchItem(data);
        item->parent = parent;
        item->generation = generationCounter;
        item->changed = true;
        int n = findInsertPosition(parent->children, item);
        beginInsertRows(index, n, n);
        parent->children.insert(n, item);
        endInsertRows();
    }
con's avatar
con committed
900 901
}

902 903
void WatchModel::insertBulkData(const QList<WatchData> &list)
{
904 905 906 907 908 909 910 911
#if 1
    for (int i = 0; i != list.size(); ++i) 
        insertData(list.at(i));
    return;
#endif
    // This method does not properly insert items in proper "iname sort
    // order", leading to random removal of items in removeOutDated();

912
    //qDebug() << "WMI:" << list.toString();
913
    //static int bulk = 0;
914
    //foreach (const WatchItem &data, list)
hjk's avatar
hjk committed
915
    //    qDebug() << "BULK: " << ++bulk << data.toString();
916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931
    QTC_ASSERT(!list.isEmpty(), return);
    QString parentIName = parentName(list.at(0).iname);
    WatchItem *parent = findItem(parentIName, m_root);
    if (!parent) {
        WatchData parent;
        parent.iname = parentIName;
        insertData(parent);
        MODEL_DEBUG("\nFIXING MISSING PARENT FOR\n" << list.at(0).iname);
        return;
    }
    QModelIndex index = watchIndex(parent);

    QMap<IName, WatchData> newList;
    typedef QMap<IName, WatchData>::iterator Iterator;
    foreach (const WatchItem &data, list)
        newList[data.iname] = data;
932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947
    if (newList.size() != list.size()) {
        qDebug() << "LIST: ";
        foreach (const WatchItem &data, list)
            qDebug() << data.toString();
        qDebug() << "NEW LIST: ";
        foreach (const WatchItem &data, newList.values())
            qDebug() << data.toString();
        qDebug() << "P->CHILDREN: ";
        foreach (const WatchItem *item, parent->children)
            qDebug() << item->toString();
        qDebug()
            << "P->CHILDREN.SIZE: " << parent->children.size()
            << "NEWLIST SIZE: " << newList.size()
            << "LIST SIZE: " << list.size();
    }
    QTC_ASSERT(newList.size() == list.size(), return);
948 949 950 951

    foreach (WatchItem *oldItem, parent->children) {
        Iterator it = newList.find(oldItem->iname);
        if (it == newList.end()) {
952 953 954
            WatchData data = *oldItem;
            data.generation = generationCounter;
            newList[oldItem->iname] = data;
955 956 957 958 959