watchhandler.cpp 58.2 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
con's avatar
con committed
2
**
Eike Ziller's avatar
Eike Ziller committed
3
4
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing
con's avatar
con committed
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
con's avatar
con committed
7
**
hjk's avatar
hjk committed
8
9
10
11
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
Eike Ziller's avatar
Eike Ziller committed
12
13
** a written agreement between you and The Qt Company.  For licensing terms and
** conditions see http://www.qt.io/terms-conditions.  For further information
Eike Ziller's avatar
Eike Ziller committed
14
** use the contact form at http://www.qt.io/contact-us.
15
**
16
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17
** Alternatively, this file may be used under the terms of the GNU Lesser
Eike Ziller's avatar
Eike Ziller committed
18
19
20
21
22
23
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file.  Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24
**
Eike Ziller's avatar
Eike Ziller committed
25
26
** In addition, as a special exception, The Qt Company gives you certain additional
** rights.  These rights are described in The Qt Company LGPL Exception
con's avatar
con committed
27
28
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
29
****************************************************************************/
hjk's avatar
hjk committed
30

con's avatar
con committed
31
#include "watchhandler.h"
32
33

#include "breakhandler.h"
34
#include "debuggeractions.h"
35
#include "debuggercore.h"
36
#include "debuggerdialogs.h"
37
38
39
#include "debuggerengine.h"
#include "debuggerinternalconstants.h"
#include "debuggerprotocol.h"
40
#include "simplifytype.h"
41
#include "imageviewer.h"
42
#include "watchutils.h"
con's avatar
con committed
43

44
45
#include <coreplugin/icore.h>

46
#include <utils/algorithm.h>
47
#include <utils/basetreeview.h>
hjk's avatar
hjk committed
48
#include <utils/qtcassert.h>
49
#include <utils/savedaction.h>
50
#include <utils/checkablemessagebox.h>
con's avatar
con committed
51

52
#include <QDebug>
53
#include <QFile>
54
#include <QProcess>
55
#include <QTabWidget>
56
#include <QTextEdit>
con's avatar
con committed
57

58
#include <cstring>
con's avatar
con committed
59
#include <ctype.h>
hjk's avatar
hjk committed
60

61
62
using namespace Utils;

hjk's avatar
hjk committed
63
64
namespace Debugger {
namespace Internal {
con's avatar
con committed
65

66
67
68
69
// Creates debug output for accesses to the model.
enum { debugModel = 0 };

#define MODEL_DEBUG(s) do { if (debugModel) qDebug() << s; } while (0)
hjk's avatar
hjk committed
70

71
72
73
74
75
static QHash<QByteArray, int> theWatcherNames;
static QHash<QByteArray, int> theTypeFormats;
static QHash<QByteArray, int> theIndividualFormats;
static int theUnprintableBase = -1;

76
77
78
const char INameProperty[] = "INameProperty";
const char KeyProperty[] = "KeyProperty";

79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
static QByteArray stripForFormat(const QByteArray &ba)
{
    QByteArray res;
    res.reserve(ba.size());
    int inArray = 0;
    for (int i = 0; i != ba.size(); ++i) {
        const char c = ba.at(i);
        if (c == '<')
            break;
        if (c == '[')
            ++inArray;
        if (c == ']')
            --inArray;
        if (c == ' ')
            continue;
94
95
        if (c == '&') // Treat references like the referenced type.
            continue;
96
97
98
99
100
        if (inArray && c >= '0' && c <= '9')
            continue;
        res.append(c);
    }
    return res;
101
102
}

103
///////////////////////////////////////////////////////////////////////
hjk's avatar
hjk committed
104
//
hjk's avatar
hjk committed
105
// SeparatedView
hjk's avatar
hjk committed
106
//
107
///////////////////////////////////////////////////////////////////////
hjk's avatar
hjk committed
108

109
class SeparatedView : public QTabWidget
110
111
112
113
{
    Q_OBJECT

public:
114
    SeparatedView() : QTabWidget(Internal::mainWindow())
115
116
    {
        setTabsClosable(true);
hjk's avatar
hjk committed
117
        connect(this, &QTabWidget::tabCloseRequested, this, &SeparatedView::closeTab);
118
119
        setWindowFlags(windowFlags() | Qt::Window);
        setWindowTitle(WatchHandler::tr("Debugger - Qt Creator"));
120

hjk's avatar
hjk committed
121
        QVariant geometry = sessionValue("DebuggerSeparateWidgetGeometry");
122
123
124
125
126
127
        if (geometry.isValid())
            setGeometry(geometry.toRect());
    }

    ~SeparatedView()
    {
hjk's avatar
hjk committed
128
        setSessionValue("DebuggerSeparateWidgetGeometry", geometry());
129
130
    }

131
    void removeObject(const QByteArray &key)
132
    {
133
134
135
136
137
138
        if (QWidget *w = findWidget(key)) {
            removeTab(indexOf(w));
            sanitize();
        }
    }

hjk's avatar
hjk committed
139
    void closeTab(int index)
140
141
142
143
144
    {
        if (QObject *o = widget(index)) {
            QByteArray iname = o->property(INameProperty).toByteArray();
            theIndividualFormats.remove(iname);
        }
145
        removeTab(index);
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
        sanitize();
    }

    void sanitize()
    {
        if (count() == 0)
            hide();
    }

    QWidget *findWidget(const QByteArray &needle)
    {
        for (int i = count(); --i >= 0; ) {
            QWidget *w = widget(i);
            QByteArray key = w->property(KeyProperty).toByteArray();
            if (key == needle)
                return w;
        }
        return 0;
    }

    template <class T> T *prepareObject(const QByteArray &key, const QString &title)
    {
        T *t = 0;
        if (QWidget *w = findWidget(key)) {
            t = qobject_cast<T *>(w);
            if (!t)
                removeTab(indexOf(w));
        }
        if (!t) {
            t = new T;
            t->setProperty(KeyProperty, key);
            addTab(t, title);
        }

        setCurrentWidget(t);
        show();
        raise();
        return t;
184
185
186
    }
};

187

hjk's avatar
hjk committed
188
189
190
191
192
///////////////////////////////////////////////////////////////////////
//
// WatchModel
//
///////////////////////////////////////////////////////////////////////
hjk's avatar
hjk committed
193

hjk's avatar
hjk committed
194
195
class WatchModel : public WatchModelBase
{
hjk's avatar
hjk committed
196
public:
197
    explicit WatchModel(WatchHandler *handler);
hjk's avatar
hjk committed
198

hjk's avatar
hjk committed
199
200
201
    static QString nameForFormat(int format);
    TypeFormatList typeFormatList(const WatchData &value) const;

hjk's avatar
hjk committed
202
203
    bool setData(const QModelIndex &idx, const QVariant &value, int role);

204
    void insertDataItem(const WatchData &data, bool destructive);
hjk's avatar
hjk committed
205
    void reinsertAllData();
hjk's avatar
hjk committed
206
207
208
    void reinsertAllDataHelper(WatchItem *item, QList<WatchData> *data);
    void insertBulkData(const QList<WatchData> &data);
    QString displayForAutoTest(const QByteArray &iname) const;
209
    void reinitialize(bool includeInspectData = false);
hjk's avatar
hjk committed
210
211
212
213
214
215
216
217
218
219
220
221

    friend QDebug operator<<(QDebug d, const WatchModel &m);

    void showInEditorHelper(QString *contents, WatchItem *item, int level);
    void setCurrentItem(const QByteArray &iname);

    QString removeNamespaces(QString str) const;
    DebuggerEngine *engine() const;
    bool contentIsValid() const;

    WatchHandler *m_handler; // Not owned.

222
    WatchItem *root() const { return static_cast<WatchItem *>(rootItem()); }
hjk's avatar
hjk committed
223
224
225
226
227
228
229
230
    WatchItem *m_localsRoot; // Not owned.
    WatchItem *m_inspectorRoot; // Not owned.
    WatchItem *m_watchRoot; // Not owned.
    WatchItem *m_returnRoot; // Not owned.
    WatchItem *m_tooltipRoot; // Not owned.

    QSet<QByteArray> m_expandedINames;

hjk's avatar
hjk committed
231
232
233
    TypeFormatList builtinTypeFormatList(const WatchData &data) const;
    QStringList dumperTypeFormatList(const WatchData &data) const;
    DumperTypeFormats m_reportedTypeFormats;
hjk's avatar
hjk committed
234
235
236
237

    WatchItem *createItem(const QByteArray &iname);
    WatchItem *findItem(const QByteArray &iname) const;
    friend class WatchItem;
238
239
    typedef QHash<QByteArray, QString> ValueCache;
    ValueCache m_valueCache;
hjk's avatar
hjk committed
240

241
242
    void insertItem(WatchItem *item);
    void reexpandItems();
hjk's avatar
hjk committed
243
244
245
246
247
};

WatchModel::WatchModel(WatchHandler *handler)
    : m_handler(handler)
{
248
    setObjectName(QLatin1String("WatchModel"));
249
250
251
252
253
254
255
256
257

    setHeader(QStringList() << tr("Name") << tr("Value") << tr("Type"));
    auto root = new WatchItem;
    root->appendChild(m_localsRoot = new WatchItem("local", tr("Locals")));
    root->appendChild(m_inspectorRoot = new WatchItem("inspect", tr("Inspector")));
    root->appendChild(m_watchRoot = new WatchItem("watch", tr("Expressions")));
    root->appendChild(m_returnRoot = new WatchItem("return", tr("Return Value")));
    root->appendChild(m_tooltipRoot = new WatchItem("tooltip", tr("Tooltip")));
    setRootItem(root);
hjk's avatar
hjk committed
258

259
    connect(action(SortStructMembers), &SavedAction::valueChanged,
hjk's avatar
hjk committed
260
        this, &WatchModel::reinsertAllData);
261
    connect(action(ShowStdNamespace), &SavedAction::valueChanged,
hjk's avatar
hjk committed
262
        this, &WatchModel::reinsertAllData);
263
    connect(action(ShowQtNamespace), &SavedAction::valueChanged,
hjk's avatar
hjk committed
264
        this, &WatchModel::reinsertAllData);
hjk's avatar
hjk committed
265
266
}

267
void WatchModel::reinitialize(bool includeInspectData)
268
{
269
270
271
272
273
274
    m_localsRoot->removeChildren();
    m_watchRoot->removeChildren();
    m_returnRoot->removeChildren();
    m_tooltipRoot->removeChildren();
    if (includeInspectData)
        m_inspectorRoot->removeChildren();
275
}
276

277
278
279
280
DebuggerEngine *WatchModel::engine() const
{
    return m_handler->m_engine;
}
281

282
WatchItem *WatchModel::findItem(const QByteArray &iname) const
283
{
284
    return root()->findItem(iname);
285
286
}

287
WatchItem *WatchItem::findItem(const QByteArray &iname)
hjk's avatar
hjk committed
288
{
289
290
291
292
293
294
295
296
    if (d.iname == iname)
        return this;
    foreach (TreeItem *child, children()) {
        auto witem = static_cast<WatchItem *>(child);
        if (witem->d.iname == iname)
            return witem;
        if (witem->d.isAncestorOf(iname))
            return witem->findItem(iname);
297
    }
298
    return 0;
hjk's avatar
hjk committed
299
300
}

301
302
303
void WatchModel::reinsertAllData()
{
    QList<WatchData> list;
304
305
    foreach (TreeItem *child, rootItem()->children())
        reinsertAllDataHelper(static_cast<WatchItem *>(child), &list);
306
    reinitialize(true);
hjk's avatar
hjk committed
307
    insertBulkData(list);
308
309
310
311
}

void WatchModel::reinsertAllDataHelper(WatchItem *item, QList<WatchData> *data)
{
312
    data->append(item->d);
hjk's avatar
hjk committed
313
    data->back().setAllUnneeded();
314
315
    foreach (TreeItem *child, item->children())
        reinsertAllDataHelper(static_cast<WatchItem *>(child), data);
316
317
}

318
static QByteArray parentName(const QByteArray &iname)
319
{
hjk's avatar
hjk committed
320
321
    const int pos = iname.lastIndexOf('.');
    return pos == -1 ? QByteArray() : iname.left(pos);
con's avatar
con committed
322
323
}

324
static QString niceTypeHelper(const QByteArray &typeIn)
325
{
326
327
328
    typedef QMap<QByteArray, QString> Cache;
    static Cache cache;
    const Cache::const_iterator it = cache.constFind(typeIn);
329
    if (it != cache.constEnd())
330
        return it.value();
331
    const QString simplified = simplifyType(QLatin1String(typeIn));
332
333
    cache.insert(typeIn, simplified); // For simplicity, also cache unmodified types
    return simplified;
con's avatar
con committed
334
335
}

336
QString WatchModel::removeNamespaces(QString str) const
337
{
hjk's avatar
hjk committed
338
    if (!boolSetting(ShowStdNamespace))
339
        str.remove(QLatin1String("std::"));
hjk's avatar
hjk committed
340
    if (!boolSetting(ShowQtNamespace)) {
341
        const QString qtNamespace = QString::fromLatin1(engine()->qtNamespace());
342
        if (!qtNamespace.isEmpty())
343
344
345
346
347
            str.remove(qtNamespace);
    }
    return str;
}

hjk's avatar
hjk committed
348
static int formatToIntegerBase(int format)
349
350
{
    switch (format) {
hjk's avatar
hjk committed
351
        case HexadecimalIntegerFormat:
hjk's avatar
hjk committed
352
            return 16;
hjk's avatar
hjk committed
353
        case BinaryIntegerFormat:
hjk's avatar
hjk committed
354
            return 2;
hjk's avatar
hjk committed
355
        case OctalIntegerFormat:
hjk's avatar
hjk committed
356
            return 8;
357
358
359
360
    }
    return 10;
}

361
362
363
template <class IntType> QString reformatInteger(IntType value, int format)
{
    switch (format) {
hjk's avatar
hjk committed
364
        case HexadecimalIntegerFormat:
365
            return QLatin1String("(hex) ") + QString::number(value, 16);
hjk's avatar
hjk committed
366
        case BinaryIntegerFormat:
367
            return QLatin1String("(bin) ") + QString::number(value, 2);
hjk's avatar
hjk committed
368
        case OctalIntegerFormat:
369
            return QLatin1String("(oct) ") + QString::number(value, 8);
370
    }
371
372
373
374
375
376
    return QString::number(value, 10); // not reached
}

static QString reformatInteger(quint64 value, int format, int size, bool isSigned)
{
    // Follow convention and don't show negative non-decimal numbers.
377
    if (format != AutomaticFormat && format != DecimalIntegerFormat)
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
        isSigned = false;

    switch (size) {
        case 1:
            value = value & 0xff;
            return isSigned
                ? reformatInteger<qint8>(value, format)
                : reformatInteger<quint8>(value, format);
        case 2:
            value = value & 0xffff;
            return isSigned
                ? reformatInteger<qint16>(value, format)
                : reformatInteger<quint16>(value, format);
        case 4:
            value = value & 0xffffffff;
            return isSigned
                ? reformatInteger<qint32>(value, format)
                : reformatInteger<quint32>(value, format);
        default:
        case 8: return isSigned
                ? reformatInteger<qint64>(value, format)
                : reformatInteger<quint64>(value, format);
    }
401
402
}

403
// Format printable (char-type) characters
404
static QString reformatCharacter(int code, int format)
405
{
406
    const QString codeS = reformatInteger(code, format, 1, true);
407
    if (code < 0) // Append unsigned value.
408
        return codeS + QLatin1String(" / ") + reformatInteger(256 + code, format, 1, false);
409
    const QChar c = QChar(uint(code));
410
411
    if (c.isPrint())
        return codeS + QLatin1String(" '") + c + QLatin1Char('\'');
412
413
414
415
416
417
418
419
420
421
422
423
424
    switch (code) {
    case 0:
        return codeS + QLatin1String(" '\\0'");
    case '\r':
        return codeS + QLatin1String(" '\\r'");
    case '\t':
        return codeS + QLatin1String(" '\\t'");
    case '\n':
        return codeS + QLatin1String(" '\\n'");
    }
    return codeS;
}

425
426
427
428
static QString quoteUnprintable(const QString &str)
{
    if (WatchHandler::unprintableBase() == 0)
        return str;
429

430
    QString encoded;
431
432
433
    if (WatchHandler::unprintableBase() == -1) {
        foreach (const QChar c, str) {
            int u = c.unicode();
434
            if (c.isPrint())
435
                encoded += c;
436
            else if (u == '\r')
437
                encoded += QLatin1String("\\r");
438
            else if (u == '\t')
439
                encoded += QLatin1String("\\t");
440
            else if (u == '\n')
441
                encoded += QLatin1String("\\n");
442
            else
443
                encoded += QString::fromLatin1("\\%1")
444
445
446
447
448
                    .arg(c.unicode(), 3, 8, QLatin1Char('0'));
        }
        return encoded;
    }

449
450
451
452
    foreach (const QChar c, str) {
        if (c.isPrint()) {
            encoded += c;
        } else if (WatchHandler::unprintableBase() == 8) {
453
            encoded += QString::fromLatin1("\\%1")
454
455
                .arg(c.unicode(), 3, 8, QLatin1Char('0'));
        } else {
456
            encoded += QString::fromLatin1("\\u%1")
457
458
459
460
461
462
                .arg(c.unicode(), 4, 16, QLatin1Char('0'));
        }
    }
    return encoded;
}

463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
static QString translate(const QString &str)
{
    if (str.startsWith(QLatin1Char('<'))) {
        if (str == QLatin1String("<empty>"))
            return WatchHandler::tr("<empty>");
        if (str == QLatin1String("<uninitialized>"))
            return WatchHandler::tr("<uninitialized>");
        if (str == QLatin1String("<invalid>"))
            return WatchHandler::tr("<invalid>");
        if (str == QLatin1String("<not accessible>"))
            return WatchHandler::tr("<not accessible>");
        if (str.endsWith(QLatin1String(" items>"))) {
            // '<10 items>' or '<>10 items>' (more than)
            bool ok;
            const bool moreThan = str.at(1) == QLatin1Char('>');
            const int numberPos = moreThan ? 2 : 1;
            const int len = str.indexOf(QLatin1Char(' ')) - numberPos;
            const int size = str.mid(numberPos, len).toInt(&ok);
            QTC_ASSERT(ok, qWarning("WatchHandler: Invalid item count '%s'",
482
                qPrintable(str)));
483
484
485
486
487
488
489
490
            return moreThan ?
                     WatchHandler::tr("<more than %n items>", 0, size) :
                     WatchHandler::tr("<%n items>", 0, size);
        }
    }
    return quoteUnprintable(str);
}

491
QString WatchItem::formattedValue() const
492
{
493
494
    if (d.type == "bool") {
        if (d.value == QLatin1String("0"))
495
            return QLatin1String("false");
496
        if (d.value == QLatin1String("1"))
497
            return QLatin1String("true");
498
        return d.value;
499
500
    }

501
    const int format = itemFormat();
502

hjk's avatar
hjk committed
503
    // Append quoted, printable character also for decimal.
504
    if (d.type.endsWith("char") || d.type.endsWith("QChar")) {
hjk's avatar
hjk committed
505
        bool ok;
506
507
        const int code = d.value.toInt(&ok);
        return ok ? reformatCharacter(code, format) : d.value;
hjk's avatar
hjk committed
508
509
510
511
512
513
    }

    if (format == HexadecimalIntegerFormat
            || format == DecimalIntegerFormat
            || format == OctalIntegerFormat
            || format == BinaryIntegerFormat) {
514
515
516
        bool isSigned = d.value.startsWith(QLatin1Char('-'));
        quint64 raw = isSigned ? quint64(d.value.toLongLong()) : d.value.toULongLong();
        return reformatInteger(raw, format, d.size, isSigned);
517
    }
518

hjk's avatar
hjk committed
519
    if (format == ScientificFloatFormat) {
520
521
        double dd = d.value.toDouble();
        return QString::number(dd, 'e');
hjk's avatar
hjk committed
522
523
524
    }

    if (format == CompactFloatFormat) {
525
526
        double dd = d.value.toDouble();
        return QString::number(dd, 'g');
hjk's avatar
hjk committed
527
528
    }

529
530
    if (d.type == "va_list")
        return d.value;
hjk's avatar
hjk committed
531

532
    if (!isPointerType(d.type) && !d.isVTablePointer()) {
533
        bool ok = false;
534
        qulonglong integer = d.value.toULongLong(&ok, 0);
535
        if (ok) {
536
537
            const int format = itemFormat();
            return reformatInteger(integer, format, d.size, false);
538
        }
539
    }
540

541
542
    if (d.elided) {
        QString v = d.value;
543
        v.chop(1);
544
        v = translate(v);
545
        QString len = d.elided > 0 ? QString::number(d.elided)
546
547
548
549
                                      : QLatin1String("unknown length");
        return v + QLatin1String("\"... (") + len  + QLatin1Char(')');
    }

550
    return translate(d.value);
551
552
}

553
554
555
556
557
558
559
560
561
// Get a pointer address from pointer values reported by the debugger.
// Fix CDB formatting of pointers "0x00000000`000003fd class foo *", or
// "0x00000000`000003fd "Hallo"", or check gdb formatting of characters.
static inline quint64 pointerValue(QString data)
{
    const int blankPos = data.indexOf(QLatin1Char(' '));
    if (blankPos != -1)
        data.truncate(blankPos);
    data.remove(QLatin1Char('`'));
562
    return data.toULongLong(0, 0);
563
564
}

565
// Return the type used for editing
566
int WatchItem::editType() const
567
568
569
570
571
572
573
{
    if (d.type == "bool")
        return QVariant::Bool;
    if (isIntType(d.type))
        return d.type.contains('u') ? QVariant::ULongLong : QVariant::LongLong;
    if (isFloatType(d.type))
        return QVariant::Double;
574
575
576
    // Check for pointers using hex values (0xAD00 "Hallo")
    if (isPointerType(d.type) && d.value.startsWith(QLatin1String("0x")))
        return QVariant::ULongLong;
577
578
579
580
   return QVariant::String;
}

// Convert to editable (see above)
581
QVariant WatchItem::editValue() const
582
{
583
    switch (editType()) {
584
585
586
    case QVariant::Bool:
        return d.value != QLatin1String("0") && d.value != QLatin1String("false");
    case QVariant::ULongLong:
587
588
        if (isPointerType(d.type)) // Fix pointer values (0xAD00 "Hallo" -> 0xAD00)
            return QVariant(pointerValue(d.value));
589
590
591
592
593
594
595
596
        return QVariant(d.value.toULongLong());
    case QVariant::LongLong:
        return QVariant(d.value.toLongLong());
    case QVariant::Double:
        return QVariant(d.value.toDouble());
    default:
        break;
    }
597
598
    // Some string value: '0x434 "Hallo"':
    // Remove quotes and replace newlines, which will cause line edit troubles.
hjk's avatar
hjk committed
599
    QString stringValue = d.value;
600
601
602
603
604
605
606
607
    if (stringValue.endsWith(QLatin1Char('"'))) {
        const int leadingDoubleQuote = stringValue.indexOf(QLatin1Char('"'));
        if (leadingDoubleQuote != stringValue.size() - 1) {
            stringValue.truncate(stringValue.size() - 1);
            stringValue.remove(0, leadingDoubleQuote + 1);
            stringValue.replace(QLatin1String("\n"), QLatin1String("\\n"));
        }
    }
608
    return QVariant(translate(stringValue));
609
610
}

611
bool WatchItem::canFetchMore() const
hjk's avatar
hjk committed
612
{
613
    if (!d.hasChildren)
614
        return false;
615
616
    if (!watchModel())
        return false;
617
    if (!watchModel()->contentIsValid() && !d.isInspect())
hjk's avatar
hjk committed
618
        return false;
619
    return !fetchTriggered;
620
621
}

622
void WatchItem::fetchMore()
623
{
hjk's avatar
hjk committed
624
625
626
    if (fetchTriggered)
        return;

627
628
629
630
    watchModel()->m_expandedINames.insert(d.iname);
    fetchTriggered = true;
    if (children().isEmpty()) {
        d.setChildrenNeeded();
631
632
        WatchUpdateFlags flags;
        flags.tryIncremental = true;
633
        watchModel()->engine()->updateWatchData(d, flags);
hjk's avatar
hjk committed
634
    }
635
636
}

637
638
// Truncate value for item view, maintaining quotes.
static QString truncateValue(QString v)
639
640
641
642
643
644
645
646
647
648
{
    enum { maxLength = 512 };
    if (v.size() < maxLength)
        return v;
    const bool isQuoted = v.endsWith(QLatin1Char('"')); // check for 'char* "Hallo"'
    v.truncate(maxLength);
    v += isQuoted ? QLatin1String("...\"") : QLatin1String("...");
    return v;
}

649
int WatchItem::itemFormat() const
650
{
651
    const int individualFormat = theIndividualFormats.value(d.iname, AutomaticFormat);
hjk's avatar
hjk committed
652
    if (individualFormat != AutomaticFormat)
653
        return individualFormat;
654
    return theTypeFormats.value(stripForFormat(d.type), AutomaticFormat);
655
656
}

657
658
bool WatchModel::contentIsValid() const
{
hjk's avatar
hjk committed
659
    // FIXME:
660
    // inspector doesn't follow normal beginCycle()/endCycle()
hjk's avatar
hjk committed
661
662
    //if (m_type == InspectWatch)
    //    return true;
663
664
665
    return m_handler->m_contentsValid;
}

666
QString WatchItem::expression() const
667
{
668
669
670
    if (!d.exp.isEmpty())
         return QString::fromLatin1(d.exp);
    if (d.address && !d.type.isEmpty()) {
671
        return QString::fromLatin1("*(%1*)%2").
672
                arg(QLatin1String(d.type), QLatin1String(d.hexAddress()));
hjk's avatar
hjk committed
673
    }
674
675
676
    if (const WatchItem *p = parentItem()) {
        if (!p->d.exp.isEmpty())
           return QString::fromLatin1("(%1).%2").arg(QString::fromLatin1(p->d.exp), d.name);
hjk's avatar
hjk committed
677
    }
678
    return d.name;
679
680
}

681
QString WatchItem::displayName() const
682
683
{
    QString result;
hjk's avatar
hjk committed
684
    if (!parentItem())
685
686
687
688
689
        return result;
    if (d.iname.startsWith("return"))
        result = WatchModel::tr("returned value");
    else if (parentItem()->d.name == QLatin1String("*"))
        result = QLatin1Char('*') + parentItem()->d.name;
690
    else
691
        result = watchModel()->removeNamespaces(d.name);
692
693
694
695
696

    // Simplyfy names that refer to base classes.
    if (result.startsWith(QLatin1Char('['))) {
        result = simplifyType(result);
        if (result.size() > 30)
Fawzi Mohamed's avatar
Fawzi Mohamed committed
697
            result = result.left(27) + QLatin1String("...]");
698
699
    }

700
701
702
    return result;
}

703
QString WatchItem::displayValue() const
704
{
705
706
707
708
709
    QString result = watchModel()->removeNamespaces(truncateValue(formattedValue()));
    if (result.isEmpty() && d.address)
        result += QString::fromLatin1("@0x" + QByteArray::number(d.address, 16));
//    if (d.origaddr)
//        result += QString::fromLatin1(" (0x" + QByteArray::number(d.origaddr, 16) + ')');
710
711
712
    return result;
}

713
QString WatchItem::displayType() const
714
{
715
716
717
718
719
    QString result = d.displayedType.isEmpty()
        ? niceTypeHelper(d.type)
        : d.displayedType;
    if (d.bitsize)
        result += QString::fromLatin1(":%1").arg(d.bitsize);
720
    result.remove(QLatin1Char('\''));
721
    result = watchModel()->removeNamespaces(result);
722
723
724
    return result;
}

hjk's avatar
hjk committed
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
QColor WatchItem::color() const
{
    static const QColor red(200, 0, 0);
    static const QColor gray(140, 140, 140);
    if (watchModel()) {
        if (!d.valueEnabled)
            return gray;
        if (!watchModel()->contentIsValid() && !d.isInspect())
            return gray;
        if (d.value.isEmpty()) // This might still show 0x...
            return gray;
        if (d.value != watchModel()->m_valueCache.value(d.iname))
            return red;
    }
    return QColor();
}

742
QVariant WatchItem::data(int column, int role) const
hjk's avatar
hjk committed
743
{
con's avatar
con committed
744
    switch (role) {
hjk's avatar
hjk committed
745
        case LocalsEditTypeRole:
746
            return QVariant(editType());
hjk's avatar
hjk committed
747

748
        case LocalsNameRole:
749
            return QVariant(d.name);
750

hjk's avatar
hjk committed
751
        case LocalsIntegerBaseRole:
752
            if (isPointerType(d.type)) // Pointers using 0x-convention
753
                return QVariant(16);
754
            return QVariant(formatToIntegerBase(itemFormat()));
hjk's avatar
hjk committed
755
756

        case Qt::EditRole: {
757
            switch (column) {
hjk's avatar
hjk committed
758
                case 0:
759
                    return expression();
hjk's avatar
hjk committed
760
                case 1:
761
                    return editValue();
hjk's avatar
hjk committed
762
763
                case 2:
                    // FIXME:: To be tested: Can debuggers handle those?
764
765
766
                    if (!d.displayedType.isEmpty())
                        return d.displayedType;
                    return QString::fromUtf8(d.type);
hjk's avatar
hjk committed
767
768
769
            }
        }

770
        case Qt::DisplayRole: {
771
            switch (column) {
772
                case 0:
773
                    return displayName();
774
                case 1:
775
                    return displayValue();
776
                case 2:
777
                    return displayType();
778
779
            }
        }
hjk's avatar
hjk committed
780

781
        case Qt::ToolTipRole:
hjk's avatar
hjk committed
782
            return boolSetting(UseToolTipsInLocalsView)
783
                ? d.toToolTip() : QVariant();
con's avatar
con committed
784

hjk's avatar
hjk committed
785
786
787
        case Qt::ForegroundRole:
            if (column == 1)
                return color();
con's avatar
con committed
788

789
        case LocalsExpressionRole:
790
            return expression();
hjk's avatar
hjk committed
791

792
        case LocalsRawExpressionRole:
793
            return d.exp;
794

795
        case LocalsINameRole:
796
            return d.iname;
con's avatar
con committed
797

798
        case LocalsExpandedRole:
799
            return watchModel()->m_expandedINames.contains(d.iname);
800

801
        case LocalsTypeFormatListRole:
802
            return QVariant::fromValue(watchModel()->typeFormatList(d));
803

804
        case LocalsTypeRole:
805
            return watchModel()->removeNamespaces(displayType());
806

807
        case LocalsRawTypeRole:
808
            return QString::fromLatin1(d.type);
809

810
        case LocalsTypeFormatRole:
811
            return theTypeFormats.value(stripForFormat(d.type), AutomaticFormat);
812

813
        case LocalsIndividualFormatRole:
814
            return theIndividualFormats.value(d.iname, AutomaticFormat);
815

816
        case LocalsRawValueRole:
817
            return d.value;
818

819
        case LocalsObjectAddressRole:
820
            return d.address;
821
822

        case LocalsPointerAddressRole:
823
            return d.origaddr;
824

825
        case LocalsIsWatchpointAtObjectAddressRole: {
826
            BreakpointParameters bp(WatchpointAtAddress);
827
828
            bp.address = d.address;
            return watchModel()->engine()->breakHandler()->findWatchpoint(bp) != 0;
hjk's avatar
hjk committed
829
        }
830

831
        case LocalsSizeRole:
832
            return QVariant(d.size);
833

834
        case LocalsIsWatchpointAtPointerAddressRole:
835
            if (isPointerType(d.type)) {
836
                BreakpointParameters bp(WatchpointAtAddress);
837
838
                bp.address = pointerValue(d.value);
                return watchModel()->engine()->breakHandler()->findWatchpoint(bp) != 0;
hjk's avatar
hjk committed
839
            }
840
841
            return false;

con's avatar
con committed
842
        default:
843
            break;
con's avatar
con committed
844
845
846
847
    }
    return QVariant();
}

hjk's avatar
hjk committed
848
bool WatchModel::setData(const QModelIndex &idx, const QVariant &value, int role)
849
{
hjk's avatar
hjk committed
850
851
852
    if (!idx.isValid())
        return false; // Triggered by ModelTester.

853
854
855
    WatchItem *item = static_cast<WatchItem *>(itemFromIndex(idx));
    QTC_ASSERT(item, return false);
    const WatchData &data = item->d;
856
857

    switch (role) {
858
        case Qt::EditRole:
hjk's avatar
hjk committed
859
            switch (idx.column()) {
860
861
862
            case 0: // Watch expression: See delegate.
                break;
            case 1: // Change value
863
                engine()->assignValueInDebugger(&data, item->expression(), value);
864
865
                break;
            case 2: // TODO: Implement change type.
866
                engine()->assignValueInDebugger(&data, item->expression(), value);
867
868
                break;
            }
869
870
871
        case LocalsExpandedRole:
            if (value.toBool()) {
                // Should already have been triggered by fetchMore()
hjk's avatar
hjk committed
872
873
                //QTC_CHECK(m_expandedINames.contains(data.iname));
                m_expandedINames.insert(data.iname);
874
            } else {
hjk's avatar
hjk committed
875
                m_expandedINames.remove(data.iname);
876
            }
877
            emit columnAdjustmentRequested();
878
879
880
881
882
883
884
885
886
            break;

        case LocalsTypeFormatRole:
            m_handler->setFormat(data.type, value.toInt());
            engine()->updateWatchData(data);
            break;

        case LocalsIndividualFormatRole: {
            const int format = value.toInt();
hjk's avatar
hjk committed
887
            if (format == AutomaticFormat)
888
                theIndividualFormats.remove(data.iname);
889
            else