watchwindow.cpp 41.3 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
con's avatar
con committed
2
**
3
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
hjk's avatar
hjk committed
4
** Contact: http://www.qt-project.org/legal
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
12
13
14
** 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
** a written agreement between you and Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
15
**
16
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17
18
19
20
21
22
23
24
25
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
con's avatar
con committed
26
27
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
28
****************************************************************************/
hjk's avatar
hjk committed
29

con's avatar
con committed
30
31
#include "watchwindow.h"

32
#include "breakhandler.h"
33
#include "registerhandler.h"
34
#include "debuggeractions.h"
35
#include "debuggerinternalconstants.h"
36
#include "debuggercore.h"
37
#include "debuggerdialogs.h"
38
#include "debuggerengine.h"
39
#include "watchdelegatewidgets.h"
40
#include "watchhandler.h"
41
#include "debuggertooltipmanager.h"
42
#include "memoryagent.h"
hjk's avatar
hjk committed
43

44
45
#include <texteditor/syntaxhighlighter.h>

46
#include <utils/qtcassert.h>
47
#include <utils/savedaction.h>
hjk's avatar
hjk committed
48
#include <utils/fancylineedit.h>
49

50
51
52
#include <QDebug>
#include <QMetaProperty>
#include <QMimeData>
con's avatar
con committed
53

54
55
56
57
58
59
60
#include <QApplication>
#include <QClipboard>
#include <QHeaderView>
#include <QItemDelegate>
#include <QMenu>
#include <QInputDialog>
#include <QMessageBox>
con's avatar
con committed
61

hjk's avatar
hjk committed
62
63
Q_DECLARE_METATYPE(QModelIndex)

64
65
66
67
68
69
/////////////////////////////////////////////////////////////////////
//
// WatchDelegate
//
/////////////////////////////////////////////////////////////////////

70
71
72
namespace Debugger {
namespace Internal {

hjk's avatar
hjk committed
73
74
const char CurrentIndex[] = "CurrentIndex";

75
76
static DebuggerEngine *currentEngine()
{
77
    return debuggerCore()->currentEngine();
78
79
}

80
81
82
class WatchDelegate : public QItemDelegate
{
public:
hjk's avatar
hjk committed
83
    explicit WatchDelegate(WatchTreeView *parent)
84
85
        : QItemDelegate(parent), m_watchWindow(parent)
    {}
86
87

    QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &,
88
        const QModelIndex &index) const
89
    {
90
91
        // Value column: Custom editor. Apply integer-specific settings.
        if (index.column() == 1) {
92
93
            const QVariant::Type type =
                static_cast<QVariant::Type>(index.data(LocalsEditTypeRole).toInt());
94
95
96
97
98
99
100
            switch (type) {
            case QVariant::Bool:
                return new BooleanComboBox(parent);
            default:
                break;
            }
            WatchLineEdit *edit = WatchLineEdit::create(type, parent);
101
102
103
104
            edit->setFrame(false);
            IntegerWatchLineEdit *intEdit
                = qobject_cast<IntegerWatchLineEdit *>(edit);
            if (intEdit)
105
106
107
                intEdit->setBase(index.data(LocalsIntegerBaseRole).toInt());
            return edit;
        }
108
109

        // Standard line edits for the rest.
hjk's avatar
hjk committed
110
        Utils::FancyLineEdit *lineEdit = new Utils::FancyLineEdit(parent);
111
        lineEdit->setFrame(false);
hjk's avatar
hjk committed
112
        lineEdit->setHistoryCompleter(QLatin1String("WatchItems"));
113
        return lineEdit;
114
115
    }

116
    void setModelData(QWidget *editor, QAbstractItemModel *model,
117
                      const QModelIndex &index) const
118
    {
119
120
121
122
123
        // Standard handling for anything but the watcher name column (change
        // expression), which removes/recreates a row, which cannot be done
        // in model->setData().
        if (index.column() != 0) {
            QItemDelegate::setModelData(editor, model, index);
124
125
            return;
        }
126
127
        const QMetaProperty userProperty = editor->metaObject()->userProperty();
        QTC_ASSERT(userProperty.isValid(), return);
128
        const QString value = editor->property(userProperty.name()).toString();
129
        const QString exp = index.data(LocalsExpressionRole).toString();
130
131
        if (exp == value)
            return;
132
133
        WatchHandler *handler = currentEngine()->watchHandler();
        handler->removeData(index.data(LocalsINameRole).toByteArray());
134
        m_watchWindow->watchExpression(value);
135
136
137
138
139
140
141
    }

    void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option,
        const QModelIndex &) const
    {
        editor->setGeometry(option.rect);
    }
142
143

private:
hjk's avatar
hjk committed
144
    WatchTreeView *m_watchWindow;
145
146
};

147
148
// Watch model query helpers.
static inline quint64 addressOf(const QModelIndex &m)
149
{
150
    return m.data(LocalsObjectAddressRole).toULongLong();
151
152
}

153
static inline quint64 pointerAddressOf(const QModelIndex &m)
154
{
155
    return m.data(LocalsPointerAddressRole).toULongLong();
156
157
}

158
static inline QString nameOf(const QModelIndex &m)
159
160
161
162
{
    return m.data().toString();
}

163
static inline QString typeOf(const QModelIndex &m)
164
165
166
167
{
    return m.data(LocalsTypeRole).toString();
}

168
static inline uint sizeOf(const QModelIndex &m)
169
170
171
{
    return m.data(LocalsSizeRole).toUInt();
}
172

hjk's avatar
hjk committed
173
// Create a map of value->name for register markup.
174
175
176
177
178
179
180
181
182
typedef QMap<quint64, QString> RegisterMap;
typedef RegisterMap::const_iterator RegisterMapConstIt;

RegisterMap registerMap(const DebuggerEngine *engine)
{
    RegisterMap result;
    foreach (const Register &reg, engine->registerHandler()->registers()) {
        const QVariant v = reg.editValue();
        if (v.type() == QVariant::ULongLong)
183
            result.insert(v.toULongLong(), QString::fromLatin1(reg.name));
184
185
186
187
    }
    return result;
}

188
189
190
191
192
// Helper functionality to indicate the area of a member variable in
// a vector representing the memory area by a unique color
// number and tooltip. Parts of it will be overwritten when recursing
// over the children.

hjk's avatar
hjk committed
193
194
typedef QPair<int, QString> ColorNumberToolTip;
typedef QVector<ColorNumberToolTip> ColorNumberToolTips;
195

hjk's avatar
hjk committed
196
197
static QString variableToolTip(const QString &name, const QString &type,
    quint64 offset)
198
199
200
{
    return offset ?
           //: HTML tooltip of a variable in the memory editor
hjk's avatar
hjk committed
201
           WatchTreeView::tr("<i>%1</i> %2 at #%3").
202
203
               arg(type, name).arg(offset) :
           //: HTML tooltip of a variable in the memory editor
hjk's avatar
hjk committed
204
           WatchTreeView::tr("<i>%1</i> %2").arg(type, name);
205
206
}

207
208
209
210
211
static int memberVariableRecursion(const QAbstractItemModel *model,
                                   const QModelIndex &modelIndex,
                                   const QString &name,
                                   quint64 start, quint64 end,
                                   int *colorNumberIn,
hjk's avatar
hjk committed
212
                                   ColorNumberToolTips *cnmv)
213
{
214
    int childCount = 0;
215
216
    QTC_ASSERT(modelIndex.isValid(), return childCount );
    const int rowCount = model->rowCount(modelIndex);
217
    if (!rowCount)
218
        return childCount;
219
    const QString nameRoot = name.isEmpty() ? name : name +  QLatin1Char('.');
220
    for (int r = 0; r < rowCount; r++) {
221
        const QModelIndex childIndex = modelIndex.child(r, 0);
222
223
224
        const quint64 childAddress = addressOf(childIndex);
        const uint childSize = sizeOf(childIndex);
        if (childAddress && childAddress >= start
225
226
227
                && (childAddress + childSize) <= end) { // Non-static, within area?
            const QString childName = nameRoot + nameOf(childIndex);
            const quint64 childOffset = childAddress - start;
228
229
            const QString toolTip
                = variableToolTip(childName, typeOf(childIndex), childOffset);
hjk's avatar
hjk committed
230
231
            const ColorNumberToolTip colorNumberNamePair((*colorNumberIn)++, toolTip);
            const ColorNumberToolTips::iterator begin = cnmv->begin() + childOffset;
232
233
            qFill(begin, begin + childSize, colorNumberNamePair);
            childCount++;
hjk's avatar
hjk committed
234
235
            childCount += memberVariableRecursion(model, childIndex,
                            childName, start, end, colorNumberIn, cnmv);
236
237
        }
    }
238
    return childCount;
239
240
}

241
typedef QList<MemoryMarkup> MemoryMarkupList;
242

243
/*!
Leena Miettinen's avatar
Leena Miettinen committed
244
    Creates markup for a variable in the memory view.
245

246
    Marks the visible children with alternating colors in the parent, that is, for
247
248
249
250
251
    \code
    struct Foo {
    char c1
    char c2
    int x2;
252
    QPair<int, int> pair
253
254
255
256
257
258
259
260
261
262
    }
    \endcode
    create something like:
    \code
    0 memberColor1
    1 memberColor2
    2 base color (padding area of parent)
    3 base color
    4 member color1
    ...
263
264
265
    8 memberColor2 (pair.first)
    ...
    12 memberColor1 (pair.second)
266
267
    \endcode

268
269
    In addition, registers pointing into the area are shown as 1 byte-markers.

270
271
272
273
274
275
   Fixme: When dereferencing a pointer, the size of the pointee is not
   known, currently. So, we take an area of 1024 and fill the background
   with the default color so that just the members are shown
   (sizeIsEstimate=true). This could be fixed by passing the pointee size
   as well from the debugger, but would require expensive type manipulation.

276
277
278
   \note To recurse over the top level items of the model, pass an invalid model
   index.

279
280
    \sa Debugger::Internal::MemoryViewWidget
*/
hjk's avatar
hjk committed
281
static MemoryMarkupList
282
283
284
285
286
287
    variableMemoryMarkup(const QAbstractItemModel *model,
                         const QModelIndex &modelIndex,
                         const QString &rootName,
                         const QString &rootToolTip,
                         quint64 address, quint64 size,
                         const RegisterMap &registerMap,
288
289
290
291
                         bool sizeIsEstimate,
                         const QColor &defaultBackground)
{
    enum { debug = 0 };
292
    enum { registerColorNumber = 0x3453 };
293

294
295
296
297
    if (debug)
        qDebug() << address << ' ' << size << rootName << rootToolTip;
    // Starting out from base, create an array representing the area
    // filled with base color. Fill children with some unique color numbers,
298
    // leaving the padding areas of the parent colored with the base color.
299
    MemoryMarkupList result;
300
    int colorNumber = 0;
hjk's avatar
hjk committed
301
    ColorNumberToolTips ranges(size, ColorNumberToolTip(colorNumber, rootToolTip));
302
    colorNumber++;
303
304
305
    const int childCount = memberVariableRecursion(model, modelIndex,
                                                   rootName, address, address + size,
                                                   &colorNumber, &ranges);
306
307
    if (sizeIsEstimate && !childCount)
        return result; // Fixme: Exact size not known, no point in filling if no children.
308
309
    // Punch in registers as 1-byte markers on top.
    const RegisterMapConstIt regcEnd = registerMap.constEnd();
310
311
312
313
    for (RegisterMapConstIt it = registerMap.constBegin(); it != regcEnd; ++it) {
        if (it.key() >= address) {
            const quint64 offset = it.key() - address;
            if (offset < size) {
hjk's avatar
hjk committed
314
                ranges[offset] = ColorNumberToolTip(registerColorNumber,
hjk's avatar
hjk committed
315
                           WatchTreeView::tr("Register <i>%1</i>").arg(it.value()));
316
317
318
319
320
            } else {
                break; // Sorted.
            }
        }
    } // for registers.
321
322
    if (debug) {
        QDebug dbg = qDebug().nospace();
323
        dbg << rootToolTip << ' ' << address << ' ' << size << '\n';
324
        QString name;
325
        for (unsigned i = 0; i < size; ++i)
326
            if (name != ranges.at(i).second) {
hjk's avatar
hjk committed
327
328
                dbg << ",[" << i << ' ' << ranges.at(i).first << ' '
                    << ranges.at(i).second << ']';
329
330
331
332
                name = ranges.at(i).second;
            }
    }

333
    // Assign colors from a list, use base color for 0 (contrast to black text).
334
335
    // Overwrite the first color (which is usually very bright) by the base color.
    QList<QColor> colors = TextEditor::SyntaxHighlighter::generateColors(colorNumber + 2,
336
                                                                         QColor(Qt::black));
337
    colors[0] = sizeIsEstimate ? defaultBackground : Qt::lightGray;
338
    const QColor registerColor = Qt::green;
339
    int lastColorNumber = 0;
340
    for (unsigned i = 0; i < size; ++i) {
hjk's avatar
hjk committed
341
        const ColorNumberToolTip &range = ranges.at(i);
342
343
        if (result.isEmpty() || lastColorNumber != range.first) {
            lastColorNumber = range.first;
344
345
            const QColor color = range.first == registerColorNumber ?
                         registerColor : colors.at(range.first);
346
            result.push_back(MemoryMarkup(address + i, 1, color, range.second));
347
        } else {
348
            result.back().length++;
349
350
351
352
353
        }
    }

    if (debug) {
        QDebug dbg = qDebug().nospace();
354
        dbg << rootName << ' ' << address << ' ' << size << '\n';
355
        QString name;
356
        for (unsigned i = 0; i < size; ++i)
357
            if (name != ranges.at(i).second) {
hjk's avatar
hjk committed
358
359
                dbg << ',' << i << ' ' << ranges.at(i).first << ' '
                    << ranges.at(i).second;
360
361
362
                name = ranges.at(i).second;
            }
        dbg << '\n';
363
364
        foreach (const MemoryMarkup &m, result)
            dbg << m.address <<  ' ' << m.length << ' '  << m.toolTip << '\n';
365
366
367
368
369
370
    }

    return result;
}

// Convenience to create a memory view of a variable.
hjk's avatar
hjk committed
371
static void addVariableMemoryView(DebuggerEngine *engine, bool separateView,
372
    const QModelIndex &m, bool atPointerAddress,
hjk's avatar
hjk committed
373
    const QPoint &p, QWidget *parent)
374
375
{
    const QColor background = parent->palette().color(QPalette::Normal, QPalette::Base);
376
    const quint64 address = atPointerAddress ? pointerAddressOf(m) : addressOf(m);
377
    // Fixme: Get the size of pointee (see variableMemoryMarkup())?
378
    const QString rootToolTip = variableToolTip(nameOf(m), typeOf(m), 0);
379
    const quint64 typeSize = sizeOf(m);
380
    const bool sizeIsEstimate = atPointerAddress || !typeSize;
381
382
383
    const quint64 size    = sizeIsEstimate ? 1024 : typeSize;
    if (!address)
         return;
384
    const QList<MemoryMarkup> markup =
385
386
387
388
        variableMemoryMarkup(m.model(), m, nameOf(m), rootToolTip,
                             address, size,
                             registerMap(engine),
                             sizeIsEstimate, background);
hjk's avatar
hjk committed
389
390
    const unsigned flags = separateView
        ? DebuggerEngine::MemoryView|DebuggerEngine::MemoryReadOnly : 0;
391
392
    const QString title = atPointerAddress
        ?  WatchTreeView::tr("Memory at Pointer's Address \"%1\" (0x%2)")
hjk's avatar
hjk committed
393
                .arg(nameOf(m)).arg(address, 0, 16)
394
        : WatchTreeView::tr("Memory at Object's Address \"%1\" (0x%2)")
hjk's avatar
hjk committed
395
                .arg(nameOf(m)).arg(address, 0, 16);
396
    engine->openMemoryView(address, flags, markup, p, title, parent);
397
398
}

399
400
// Add a memory view of the stack layout showing local variables
// and registers.
hjk's avatar
hjk committed
401
402
static void addStackLayoutMemoryView(DebuggerEngine *engine, bool separateView,
    const QAbstractItemModel *m, const QPoint &p, QWidget *parent)
403
{
404
    QTC_ASSERT(engine && m, return);
405

hjk's avatar
hjk committed
406
    // Determine suitable address range from locals.
dt_'s avatar
dt_ committed
407
    quint64 start = Q_UINT64_C(0xFFFFFFFFFFFFFFFF);
408
    quint64 end = 0;
409
410
411
    const QModelIndex localsIndex = m->index(0, 0);
    QTC_ASSERT(localsIndex.data(LocalsINameRole).toString() == QLatin1String("local"), return);
    const int localsItemCount = m->rowCount(localsIndex);
412
413
    // Note: Unsorted by default. Exclude 'Automatically dereferenced
    // pointer' items as they are outside the address range.
414
415
    for (int r = 0; r < localsItemCount; r++) {
        const QModelIndex idx = localsIndex.child(r, 0);
416
417
        const quint64 pointerAddress = pointerAddressOf(idx);
        if (pointerAddress == 0) {
418
419
420
421
            const quint64 address = addressOf(idx);
            if (address) {
                if (address < start)
                    start = address;
422
423
424
                const uint size = qMax(1u, sizeOf(idx));
                if (address + size > end)
                    end = address + size;
425
            }
426
427
        }
    }
428
429
    if (const quint64 remainder = end % 8)
        end += 8 - remainder;
430
431
    // Anything found and everything in a sensible range (static data in-between)?
    if (end <= start || end - start > 100 * 1024) {
hjk's avatar
hjk committed
432
        QMessageBox::information(parent,
hjk's avatar
hjk committed
433
434
            WatchTreeView::tr("Cannot Display Stack Layout"),
            WatchTreeView::tr("Could not determine a suitable address range."));
435
436
437
438
439
440
441
442
        return;
    }
    // Take a look at the register values. Extend the range a bit if suitable
    // to show stack/stack frame pointers.
    const RegisterMap regMap = registerMap(engine);
    const RegisterMapConstIt regcEnd = regMap.constEnd();
    for (RegisterMapConstIt it = regMap.constBegin(); it != regcEnd; ++it) {
        const quint64 value = it.key();
443
        if (value < start && start - value < 512)
444
            start = value;
445
        else if (value > end && value - end < 512)
446
447
448
449
450
            end = value + 1;
    }
    // Indicate all variables.
    const QColor background = parent->palette().color(QPalette::Normal, QPalette::Base);
    const MemoryMarkupList markup =
451
        variableMemoryMarkup(m, localsIndex, QString(),
452
453
                             QString(), start, end - start,
                             regMap, true, background);
hjk's avatar
hjk committed
454
455
    const unsigned flags = separateView
        ? (DebuggerEngine::MemoryView|DebuggerEngine::MemoryReadOnly) : 0;
456
    const QString title =
hjk's avatar
hjk committed
457
        WatchTreeView::tr("Memory Layout of Local Variables at 0x%1").arg(start, 0, 16);
458
459
460
    engine->openMemoryView(start, flags, markup, p, title, parent);
}

con's avatar
con committed
461
462
463
464
465
466
/////////////////////////////////////////////////////////////////////
//
// WatchWindow
//
/////////////////////////////////////////////////////////////////////

hjk's avatar
hjk committed
467
468
WatchTreeView::WatchTreeView(Type type, QWidget *parent)
  : BaseTreeView(parent),
469
    m_type(type)
con's avatar
con committed
470
{
471
    setObjectName(QLatin1String("WatchWindow"));
472
    m_grabbing = false;
473
    setWindowTitle(tr("Locals and Expressions"));
hjk's avatar
hjk committed
474
475
476
477
478
479
    setIndentation(indentation() * 9/10);
    setUniformRowHeights(true);
    setItemDelegate(new WatchDelegate(this));
    setDragEnabled(true);
    setAcceptDrops(true);
    setDropIndicatorShown(true);
480
    setAlwaysAdjustColumnsAction(debuggerCore()->action(AlwaysAdjustLocalsColumnWidths));
con's avatar
con committed
481

hjk's avatar
hjk committed
482
    connect(this, SIGNAL(expanded(QModelIndex)),
483
        SLOT(expandNode(QModelIndex)));
hjk's avatar
hjk committed
484
    connect(this, SIGNAL(collapsed(QModelIndex)),
485
        SLOT(collapseNode(QModelIndex)));
486
487
}

hjk's avatar
hjk committed
488
void WatchTreeView::expandNode(const QModelIndex &idx)
489
{
490
    setModelData(LocalsExpandedRole, true, idx);
491
492
}

hjk's avatar
hjk committed
493
void WatchTreeView::collapseNode(const QModelIndex &idx)
494
{
495
    setModelData(LocalsExpandedRole, false, idx);
con's avatar
con committed
496
497
}

hjk's avatar
hjk committed
498
void WatchTreeView::keyPressEvent(QKeyEvent *ev)
499
{
hjk's avatar
hjk committed
500
    if (ev->key() == Qt::Key_Delete && m_type == WatchersType) {
501
        WatchHandler *handler = currentEngine()->watchHandler();
502
        foreach (const QModelIndex &idx, activeRows())
503
            handler->removeData(idx.data(LocalsINameRole).toByteArray());
504
505
506
    } else if (ev->key() == Qt::Key_Return
            && ev->modifiers() == Qt::ControlModifier
            && m_type == LocalsType) {
hjk's avatar
hjk committed
507
        QModelIndex idx = currentIndex();
hjk's avatar
hjk committed
508
509
        QModelIndex idx1 = idx.sibling(idx.row(), 0);
        QString exp = model()->data(idx1).toString();
510
        watchExpression(exp);
511
    }
hjk's avatar
hjk committed
512
    BaseTreeView::keyPressEvent(ev);
513
514
}

hjk's avatar
hjk committed
515
void WatchTreeView::dragEnterEvent(QDragEnterEvent *ev)
516
{
hjk's avatar
hjk committed
517
    //BaseTreeView::dragEnterEvent(ev);
518
    if (ev->mimeData()->hasText()) {
519
520
521
522
523
        ev->setDropAction(Qt::CopyAction);
        ev->accept();
    }
}

hjk's avatar
hjk committed
524
void WatchTreeView::dragMoveEvent(QDragMoveEvent *ev)
525
{
hjk's avatar
hjk committed
526
    //BaseTreeView::dragMoveEvent(ev);
527
    if (ev->mimeData()->hasText()) {
528
529
530
531
532
        ev->setDropAction(Qt::CopyAction);
        ev->accept();
    }
}

hjk's avatar
hjk committed
533
void WatchTreeView::dropEvent(QDropEvent *ev)
534
{
535
    if (ev->mimeData()->hasText()) {
536
537
538
539
        QString exp;
        QString data = ev->mimeData()->text();
        foreach (const QChar c, data)
            exp.append(c.isPrint() ? c : QChar(QLatin1Char(' ')));
540
        currentEngine()->watchHandler()->watchVariable(exp);
541
542
543
544
        //ev->acceptProposedAction();
        ev->setDropAction(Qt::CopyAction);
        ev->accept();
    }
hjk's avatar
hjk committed
545
    //BaseTreeView::dropEvent(ev);
546
547
}

hjk's avatar
hjk committed
548
void WatchTreeView::mouseDoubleClickEvent(QMouseEvent *ev)
549
550
551
{
    const QModelIndex idx = indexAt(ev->pos());
    if (!idx.isValid()) {
552
553
        // The "<Edit>" case.
        watchExpression(QString());
554
555
        return;
    }
hjk's avatar
hjk committed
556
    BaseTreeView::mouseDoubleClickEvent(ev);
557
558
}

hjk's avatar
hjk committed
559
560
// Text for add watch action with truncated expression.
static QString addWatchActionText(QString exp)
561
562
{
    if (exp.isEmpty())
563
        return WatchTreeView::tr("Add Expression Evaluator");
564
565
566
567
    if (exp.size() > 30) {
        exp.truncate(30);
        exp.append(QLatin1String("..."));
    }
568
    return WatchTreeView::tr("Add Expression Evaluator for \"%1\"").arg(exp);
569
570
}

hjk's avatar
hjk committed
571
572
// Text for add watch action with truncated expression.
static QString removeWatchActionText(QString exp)
573
574
{
    if (exp.isEmpty())
575
        return WatchTreeView::tr("Remove Expression Evaluator");
576
577
578
579
    if (exp.size() > 30) {
        exp.truncate(30);
        exp.append(QLatin1String("..."));
    }
580
581
    return WatchTreeView::tr("Remove Expression Evaluator for \"%1\"")
        .arg(exp.replace(QLatin1Char('&'), QLatin1String("&&")));
582
583
}

hjk's avatar
hjk committed
584
static void copyToClipboard(const QString &clipboardText)
585
586
587
588
589
590
{
    QClipboard *clipboard = QApplication::clipboard();
    clipboard->setText(clipboardText, QClipboard::Selection);
    clipboard->setText(clipboardText, QClipboard::Clipboard);
}

hjk's avatar
hjk committed
591
void WatchTreeView::fillFormatMenu(QMenu *formatMenu, const QModelIndex &mi)
con's avatar
con committed
592
{
hjk's avatar
hjk committed
593
594
595
    QTC_CHECK(mi.isValid());

    const QModelIndex mi2 = mi.sibling(mi.row(), 2);
596
    const QString type = mi2.data().toString();
597

hjk's avatar
hjk committed
598
599
    const TypeFormatList alternativeFormats =
        mi.data(LocalsTypeFormatListRole).value<TypeFormatList>();
600
    int typeFormat =
hjk's avatar
hjk committed
601
        mi.data(LocalsTypeFormatRole).toInt();
602
    const int individualFormat =
hjk's avatar
hjk committed
603
        mi.data(LocalsIndividualFormatRole).toInt();
604
    const int unprintableBase = WatchHandler::unprintableBase();
605

606
    QAction *showUnprintableUnicode = 0;
607
    QAction *showUnprintableEscape = 0;
608
609
    QAction *showUnprintableOctal = 0;
    QAction *showUnprintableHexadecimal = 0;
610
    showUnprintableUnicode =
hjk's avatar
hjk committed
611
        formatMenu->addAction(tr("Treat All Characters as Printable"));
612
613
    showUnprintableUnicode->setCheckable(true);
    showUnprintableUnicode->setChecked(unprintableBase == 0);
hjk's avatar
hjk committed
614
    showUnprintableUnicode->setData(0);
615
    showUnprintableEscape =
hjk's avatar
hjk committed
616
        formatMenu->addAction(tr("Show Unprintable Characters as Escape Sequences"));
617
618
    showUnprintableEscape->setCheckable(true);
    showUnprintableEscape->setChecked(unprintableBase == -1);
hjk's avatar
hjk committed
619
    showUnprintableEscape->setData(-1);
620
    showUnprintableOctal =
hjk's avatar
hjk committed
621
        formatMenu->addAction(tr("Show Unprintable Characters as Octal"));
622
623
    showUnprintableOctal->setCheckable(true);
    showUnprintableOctal->setChecked(unprintableBase == 8);
hjk's avatar
hjk committed
624
    showUnprintableOctal->setData(8);
625
    showUnprintableHexadecimal =
hjk's avatar
hjk committed
626
        formatMenu->addAction(tr("Show Unprintable Characters as Hexadecimal"));
627
628
    showUnprintableHexadecimal->setCheckable(true);
    showUnprintableHexadecimal->setChecked(unprintableBase == 16);
hjk's avatar
hjk committed
629
630
631
632
633
634
635
636
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
    showUnprintableHexadecimal->setData(16);

    connect(showUnprintableUnicode, SIGNAL(triggered()), SLOT(onShowUnprintable()));
    connect(showUnprintableEscape, SIGNAL(triggered()), SLOT(onShowUnprintable()));
    connect(showUnprintableOctal, SIGNAL(triggered()), SLOT(onShowUnprintable()));
    connect(showUnprintableHexadecimal, SIGNAL(triggered()), SLOT(onShowUnprintable()));


    const QString spacer = QLatin1String("     ");
    formatMenu->addSeparator();
    QAction *dummy = formatMenu->addAction(
        tr("Change Display for Object Named \"%1\":").arg(mi.data().toString()));
    dummy->setEnabled(false);
    QString msg = (individualFormat == AutomaticFormat && typeFormat != AutomaticFormat)
        ? tr("Use Format for Type (Currently %1)")
            .arg(alternativeFormats.find(typeFormat).display)
        : tr("Use Display Format Based on Type") + QLatin1Char(' ');

    QAction *clearIndividualFormatAction = formatMenu->addAction(spacer + msg);
    clearIndividualFormatAction->setCheckable(true);
    clearIndividualFormatAction->setChecked(individualFormat == AutomaticFormat);
    connect(clearIndividualFormatAction, SIGNAL(triggered()),
        SLOT(onClearIndividualFormat()));

    for (int i = 0; i != alternativeFormats.size(); ++i) {
        const QString display = spacer + alternativeFormats.at(i).display;
        const int format = alternativeFormats.at(i).format;
        QAction *act = new QAction(display, formatMenu);
        act->setData(format);
        act->setCheckable(true);
        act->setChecked(format == individualFormat);
        act->setProperty(CurrentIndex, QVariant::fromValue(mi));
        formatMenu->addAction(act);
        connect(act, SIGNAL(triggered()), SLOT(onIndividualFormatChange()));
663
664
    }

hjk's avatar
hjk committed
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
    formatMenu->addSeparator();
    dummy = formatMenu->addAction(tr("Change Display for Type \"%1\":").arg(type));
    dummy->setEnabled(false);

    QAction *clearTypeFormatAction = formatMenu->addAction(spacer + tr("Automatic"));
    clearTypeFormatAction->setCheckable(true);
    clearTypeFormatAction->setChecked(typeFormat == AutomaticFormat);
    connect(clearTypeFormatAction, SIGNAL(triggered()), SLOT(onClearTypeFormat()));

    for (int i = 0; i != alternativeFormats.size(); ++i) {
        const QString display = spacer + alternativeFormats.at(i).display;
        QAction *act = new QAction(display, formatMenu);
        const int format = alternativeFormats.at(i).format;
        act->setData(format);
        act->setCheckable(true);
        act->setChecked(format == typeFormat);
        act->setProperty(CurrentIndex, QVariant::fromValue(mi));
        formatMenu->addAction(act);
        connect(act, SIGNAL(triggered()), SLOT(onTypeFormatChange()));
    }
}

void WatchTreeView::onClearTypeFormat()
{
    const QModelIndexList active = activeRows();
    foreach (const QModelIndex &idx, active)
        setModelData(LocalsTypeFormatRole, AutomaticFormat, idx);
}

void WatchTreeView::onClearIndividualFormat()
{
    const QModelIndexList active = activeRows();
    foreach (const QModelIndex &idx, active)
        setModelData(LocalsIndividualFormatRole, AutomaticFormat, idx);
}

void WatchTreeView::onShowUnprintable()
{
    QAction *act = qobject_cast<QAction *>(sender());
    QTC_ASSERT(act, return);
    DebuggerEngine *engine = currentEngine();
    WatchHandler *handler = engine->watchHandler();
    handler->setUnprintableBase(act->data().toInt());
}

void WatchTreeView::onTypeFormatChange()
{
    QAction *act = qobject_cast<QAction *>(sender());
    QTC_ASSERT(act, return);
    QModelIndex idx = act->property(CurrentIndex).value<QModelIndex>();
    setModelData(LocalsTypeFormatRole, act->data(), idx);
}

void WatchTreeView::onIndividualFormatChange()
{
    QAction *act = qobject_cast<QAction *>(sender());
    QTC_ASSERT(act, return);
    QModelIndex idx = act->property(CurrentIndex).value<QModelIndex>();
    setModelData(LocalsIndividualFormatRole, act->data(), idx);
}

void WatchTreeView::contextMenuEvent(QContextMenuEvent *ev)
{
    DebuggerEngine *engine = currentEngine();
    WatchHandler *handler = engine->watchHandler();

    const QModelIndex idx = indexAt(ev->pos());
    const QModelIndex mi0 = idx.sibling(idx.row(), 0);
    const QModelIndex mi1 = idx.sibling(idx.row(), 1);
    const quint64 address = addressOf(mi0);
    const uint size = sizeOf(mi0);
    const quint64 pointerAddress = pointerAddressOf(mi0);
    const QString exp = mi0.data(LocalsExpressionRole).toString();
    const QString name = mi0.data(LocalsNameRole).toString();

    // Offer to open address pointed to or variable address.
    const bool createPointerActions = pointerAddress && pointerAddress != address;

hjk's avatar
hjk committed
743
    const bool actionsEnabled = engine->debuggerActionsEnabled();
744
    const bool canHandleWatches = engine->hasCapability(AddWatcherCapability);
hjk's avatar
hjk committed
745
    const DebuggerState state = engine->state();
746
    const bool canInsertWatches = state == InferiorStopOk
747
        || state == DebuggerNotReady
748
        || state == InferiorUnrunnable
749
        || (state == InferiorRunOk && engine->hasCapability(AddWatcherWhileRunningCapability));
750

751
752
    QMenu breakpointMenu;
    breakpointMenu.setTitle(tr("Add Data Breakpoint..."));
753
754
    QAction *actSetWatchpointAtObjectAddress = 0;
    QAction *actSetWatchpointAtPointerAddress = 0;
755
    const bool canSetWatchpoint = engine->hasCapability(WatchpointByAddressCapability);
756
    if (canSetWatchpoint && address) {
757
        actSetWatchpointAtObjectAddress =
758
759
            new QAction(tr("Add Data Breakpoint at Object's Address (0x%1)")
                .arg(address, 0, 16), &breakpointMenu);
760
761
        actSetWatchpointAtObjectAddress->
            setChecked(mi0.data(LocalsIsWatchpointAtObjectAddressRole).toBool());
762
        if (createPointerActions) {
763
764
765
766
767
768
            actSetWatchpointAtPointerAddress =
                new QAction(tr("Add Data Breakpoint at Pointer's Address (0x%1)")
                    .arg(pointerAddress, 0, 16), &breakpointMenu);
            actSetWatchpointAtPointerAddress->setCheckable(true);
            actSetWatchpointAtPointerAddress->
                setChecked(mi0.data(LocalsIsWatchpointAtPointerAddressRole).toBool());
769
        }
770
    } else {
771
        actSetWatchpointAtObjectAddress =
772
            new QAction(tr("Add Data Breakpoint"), &breakpointMenu);
773
        actSetWatchpointAtObjectAddress->setEnabled(false);
774
    }
775
    actSetWatchpointAtObjectAddress->setToolTip(
776
777
778
        tr("Setting a data breakpoint on an address will cause the program "
           "to stop when the data at the address is modified."));

779
    QAction *actSetWatchpointAtExpression = 0;
780
    const bool canSetWatchpointAtExpression = engine->hasCapability(WatchpointByExpressionCapability);
781
    if (name.isEmpty() || !canSetWatchpointAtExpression) {
782
783
784
785
786
787
        actSetWatchpointAtExpression =
            new QAction(tr("Add Data Breakpoint at Expression"),
                &breakpointMenu);
        actSetWatchpointAtExpression->setEnabled(false);
    } else {
        actSetWatchpointAtExpression =
788
789
            new QAction(tr("Add Data Breakpoint at Expression \"%1\"").arg(name),
                &breakpointMenu);
790
    }
791
792
793
794
795
    actSetWatchpointAtExpression->setToolTip(
        tr("Setting a data breakpoint on an expression will cause the program "
           "to stop when the data at the address given by the expression "
           "is modified."));

796
797
798
    breakpointMenu.addAction(actSetWatchpointAtObjectAddress);
    if (actSetWatchpointAtPointerAddress)
        breakpointMenu.addAction(actSetWatchpointAtPointerAddress);
799
800
801
    breakpointMenu.addAction(actSetWatchpointAtExpression);

    QMenu menu;
802
    QAction *actInsertNewWatchItem =
803
        menu.addAction(tr("Insert New Expression Evaluator"));
804
    actInsertNewWatchItem->setEnabled(canHandleWatches && canInsertWatches);
805
806
    QAction *actSelectWidgetToWatch =
        menu.addAction(tr("Select Widget to Add into Expression Evaluator"));
807
    actSelectWidgetToWatch->setEnabled(canHandleWatches
808
809
           && engine->hasCapability(WatchWidgetsCapability));

810
    menu.addSeparator();
811

812
    QAction *actWatchExpression = new QAction(addWatchActionText(exp), &menu);
813
814
    actWatchExpression->setEnabled(
        canHandleWatches && !exp.isEmpty() && m_type == LocalsType);
815

816
    // Can remove watch if engine can handle it or session engine.
817
    QModelIndex p = mi0;
818
819
820
821
822
823
    while (true) {
        QModelIndex pp = p.parent();
        if (!pp.isValid() || !pp.parent().isValid())
            break;
        p = pp;
    }
824
825
    QString removeExp = p.data(LocalsExpressionRole).toString();
    QAction *actRemoveWatchExpression = new QAction(removeWatchActionText(removeExp), &menu);
826
    actRemoveWatchExpression->setEnabled(
827
828
829
830
        (canHandleWatches || state == DebuggerNotReady)
                && !exp.isEmpty() && m_type == WatchersType);
    menu.addAction(actWatchExpression);
    menu.addAction(actRemoveWatchExpression);
831

832
    QMenu formatMenu(tr("Change Local Display Format..."));
hjk's avatar
hjk committed
833
834
835
836
837
838
839
    if (mi0.isValid()) {
        fillFormatMenu(&formatMenu, mi0);
    } else {
        QAction *dummy = formatMenu.addAction(tr("Change Display for Type or Item..."));
        dummy->setEnabled(false);
    }

840
841
    QMenu memoryMenu;
    memoryMenu.setTitle(tr("Open Memory Editor..."));
842
843
    QAction *actOpenMemoryEditAtObjectAddress = new QAction(&memoryMenu);
    QAction *actOpenMemoryEditAtPointerAddress = new QAction(&memoryMenu);
844
    QAction *actOpenMemoryEditor = new QAction(&memoryMenu);
845
    QAction *actOpenMemoryEditorStackLayout = new QAction(&memoryMenu);
846
847
    QAction *actOpenMemoryViewAtObjectAddress = new QAction(&memoryMenu);
    QAction *actOpenMemoryViewAtPointerAddress = new QAction(&memoryMenu);
848
    if (engine->hasCapability(ShowMemoryCapability)) {
849
850
        actOpenMemoryEditor->setText(tr("Open Memory Editor..."));
        if (address) {
851
            actOpenMemoryEditAtObjectAddress->setText(
852
853
                tr("Open Memory Editor at Object's Address (0x%1)")
                    .arg(address, 0, 16));
854
            actOpenMemoryViewAtObjectAddress->setText(
855
856
                    tr("Open Memory View at Object's Address (0x%1)")
                        .arg(address, 0, 16));
857
        } else {