watchwindow.cpp 41.8 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
con's avatar
con committed
2
**
3 4
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://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
12 13 14
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
15
**
16 17 18 19 20 21 22
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
con's avatar
con committed
23
**
hjk's avatar
hjk committed
24
****************************************************************************/
hjk's avatar
hjk committed
25

con's avatar
con committed
26 27
#include "watchwindow.h"

28
#include "breakhandler.h"
29
#include "registerhandler.h"
30
#include "debuggeractions.h"
31
#include "debuggerinternalconstants.h"
32
#include "debuggercore.h"
33
#include "debuggerdialogs.h"
34
#include "debuggerengine.h"
35
#include "watchdelegatewidgets.h"
36
#include "watchhandler.h"
37
#include "debuggertooltipmanager.h"
38
#include "memoryagent.h"
hjk's avatar
hjk committed
39

40 41
#include <texteditor/syntaxhighlighter.h>

42 43
#include <coreplugin/messagebox.h>

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

49 50
#include <QApplication>
#include <QClipboard>
51
#include <QDebug>
52
#include <QHeaderView>
53
#include <QInputDialog>
54 55
#include <QItemDelegate>
#include <QMenu>
56 57 58 59
#include <QMetaProperty>
#include <QMimeData>
#include <QScrollBar>
#include <QTimer>
hjk's avatar
hjk committed
60
#include <QTextStream>
con's avatar
con committed
61

62 63 64 65 66 67 68
// For InputDialog, move to Utils?
#include <coreplugin/helpmanager.h>
#include <QLabel>
#include <QVBoxLayout>
#include <QButtonGroup>
#include <QDialogButtonBox>

69 70 71 72 73 74
/////////////////////////////////////////////////////////////////////
//
// WatchDelegate
//
/////////////////////////////////////////////////////////////////////

75 76 77
namespace Debugger {
namespace Internal {

78 79 80
class WatchDelegate : public QItemDelegate
{
public:
81 82
    explicit WatchDelegate(QObject *parent)
        : QItemDelegate(parent)
83
    {}
84 85

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

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

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

121 122
// Watch model query helpers.
static inline quint64 addressOf(const QModelIndex &m)
123
{
124
    return m.data(LocalsObjectAddressRole).toULongLong();
125 126
}

127
static inline quint64 pointerAddressOf(const QModelIndex &m)
128
{
129
    return m.data(LocalsPointerAddressRole).toULongLong();
130 131
}

132
static inline QString nameOf(const QModelIndex &m)
133 134 135 136
{
    return m.data().toString();
}

137
static inline QString typeOf(const QModelIndex &m)
138 139 140 141
{
    return m.data(LocalsTypeRole).toString();
}

142
static inline uint sizeOf(const QModelIndex &m)
143 144 145
{
    return m.data(LocalsSizeRole).toUInt();
}
146

147 148 149 150 151
// 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
152 153
typedef QPair<int, QString> ColorNumberToolTip;
typedef QVector<ColorNumberToolTip> ColorNumberToolTips;
154

hjk's avatar
hjk committed
155 156
static QString variableToolTip(const QString &name, const QString &type,
    quint64 offset)
157 158 159
{
    return offset ?
           //: HTML tooltip of a variable in the memory editor
hjk's avatar
hjk committed
160
           WatchTreeView::tr("<i>%1</i> %2 at #%3").
161 162
               arg(type, name).arg(offset) :
           //: HTML tooltip of a variable in the memory editor
hjk's avatar
hjk committed
163
           WatchTreeView::tr("<i>%1</i> %2").arg(type, name);
164 165
}

166 167 168 169 170
static int memberVariableRecursion(const QAbstractItemModel *model,
                                   const QModelIndex &modelIndex,
                                   const QString &name,
                                   quint64 start, quint64 end,
                                   int *colorNumberIn,
hjk's avatar
hjk committed
171
                                   ColorNumberToolTips *cnmv)
172
{
173
    int childCount = 0;
174 175
    QTC_ASSERT(modelIndex.isValid(), return childCount );
    const int rowCount = model->rowCount(modelIndex);
176
    if (!rowCount)
177
        return childCount;
178
    const QString nameRoot = name.isEmpty() ? name : name +  QLatin1Char('.');
179
    for (int r = 0; r < rowCount; r++) {
180
        const QModelIndex childIndex = modelIndex.child(r, 0);
181 182 183
        const quint64 childAddress = addressOf(childIndex);
        const uint childSize = sizeOf(childIndex);
        if (childAddress && childAddress >= start
184 185 186
                && (childAddress + childSize) <= end) { // Non-static, within area?
            const QString childName = nameRoot + nameOf(childIndex);
            const quint64 childOffset = childAddress - start;
187 188
            const QString toolTip
                = variableToolTip(childName, typeOf(childIndex), childOffset);
hjk's avatar
hjk committed
189 190
            const ColorNumberToolTip colorNumberNamePair((*colorNumberIn)++, toolTip);
            const ColorNumberToolTips::iterator begin = cnmv->begin() + childOffset;
191 192
            qFill(begin, begin + childSize, colorNumberNamePair);
            childCount++;
hjk's avatar
hjk committed
193 194
            childCount += memberVariableRecursion(model, childIndex,
                            childName, start, end, colorNumberIn, cnmv);
195 196
        }
    }
197
    return childCount;
198 199
}

200
typedef QList<MemoryMarkup> MemoryMarkupList;
201

202
/*!
Leena Miettinen's avatar
Leena Miettinen committed
203
    Creates markup for a variable in the memory view.
204

205
    Marks the visible children with alternating colors in the parent, that is, for
206 207 208 209 210
    \code
    struct Foo {
    char c1
    char c2
    int x2;
211
    QPair<int, int> pair
212 213 214 215 216 217 218 219 220 221
    }
    \endcode
    create something like:
    \code
    0 memberColor1
    1 memberColor2
    2 base color (padding area of parent)
    3 base color
    4 member color1
    ...
222 223 224
    8 memberColor2 (pair.first)
    ...
    12 memberColor1 (pair.second)
225 226
    \endcode

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

229 230 231 232 233 234
   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.

235 236 237
   \note To recurse over the top level items of the model, pass an invalid model
   index.

238 239
    \sa Debugger::Internal::MemoryViewWidget
*/
hjk's avatar
hjk committed
240
static MemoryMarkupList
241 242 243 244 245 246
    variableMemoryMarkup(const QAbstractItemModel *model,
                         const QModelIndex &modelIndex,
                         const QString &rootName,
                         const QString &rootToolTip,
                         quint64 address, quint64 size,
                         const RegisterMap &registerMap,
247 248 249 250
                         bool sizeIsEstimate,
                         const QColor &defaultBackground)
{
    enum { debug = 0 };
251
    enum { registerColorNumber = 0x3453 };
252

253 254 255 256
    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,
257
    // leaving the padding areas of the parent colored with the base color.
258
    MemoryMarkupList result;
259
    int colorNumber = 0;
hjk's avatar
hjk committed
260
    ColorNumberToolTips ranges(size, ColorNumberToolTip(colorNumber, rootToolTip));
261
    colorNumber++;
262 263 264
    const int childCount = memberVariableRecursion(model, modelIndex,
                                                   rootName, address, address + size,
                                                   &colorNumber, &ranges);
265 266
    if (sizeIsEstimate && !childCount)
        return result; // Fixme: Exact size not known, no point in filling if no children.
267
    // Punch in registers as 1-byte markers on top.
hjk's avatar
hjk committed
268
    for (auto it = registerMap.constBegin(), end = registerMap.constEnd(); it != end; ++it) {
269 270 271
        if (it.key() >= address) {
            const quint64 offset = it.key() - address;
            if (offset < size) {
hjk's avatar
hjk committed
272
                ranges[offset] = ColorNumberToolTip(registerColorNumber,
hjk's avatar
hjk committed
273
                           WatchTreeView::tr("Register <i>%1</i>").arg(QString::fromUtf8(it.value())));
274 275 276 277 278
            } else {
                break; // Sorted.
            }
        }
    } // for registers.
279 280
    if (debug) {
        QDebug dbg = qDebug().nospace();
281
        dbg << rootToolTip << ' ' << address << ' ' << size << '\n';
282
        QString name;
283
        for (unsigned i = 0; i < size; ++i)
284
            if (name != ranges.at(i).second) {
hjk's avatar
hjk committed
285 286
                dbg << ",[" << i << ' ' << ranges.at(i).first << ' '
                    << ranges.at(i).second << ']';
287 288 289 290
                name = ranges.at(i).second;
            }
    }

291
    // Assign colors from a list, use base color for 0 (contrast to black text).
292 293
    // Overwrite the first color (which is usually very bright) by the base color.
    QList<QColor> colors = TextEditor::SyntaxHighlighter::generateColors(colorNumber + 2,
294
                                                                         QColor(Qt::black));
295
    colors[0] = sizeIsEstimate ? defaultBackground : Qt::lightGray;
296
    const QColor registerColor = Qt::green;
297
    int lastColorNumber = 0;
298
    for (unsigned i = 0; i < size; ++i) {
hjk's avatar
hjk committed
299
        const ColorNumberToolTip &range = ranges.at(i);
300 301
        if (result.isEmpty() || lastColorNumber != range.first) {
            lastColorNumber = range.first;
302 303
            const QColor color = range.first == registerColorNumber ?
                         registerColor : colors.at(range.first);
304
            result.push_back(MemoryMarkup(address + i, 1, color, range.second));
305
        } else {
306
            result.back().length++;
307 308 309 310 311
        }
    }

    if (debug) {
        QDebug dbg = qDebug().nospace();
312
        dbg << rootName << ' ' << address << ' ' << size << '\n';
313
        QString name;
314
        for (unsigned i = 0; i < size; ++i)
315
            if (name != ranges.at(i).second) {
hjk's avatar
hjk committed
316 317
                dbg << ',' << i << ' ' << ranges.at(i).first << ' '
                    << ranges.at(i).second;
318 319 320
                name = ranges.at(i).second;
            }
        dbg << '\n';
321 322
        foreach (const MemoryMarkup &m, result)
            dbg << m.address <<  ' ' << m.length << ' '  << m.toolTip << '\n';
323 324 325 326 327 328
    }

    return result;
}

// Convenience to create a memory view of a variable.
hjk's avatar
hjk committed
329
static void addVariableMemoryView(DebuggerEngine *engine, bool separateView,
330
    const QModelIndex &m, bool atPointerAddress,
hjk's avatar
hjk committed
331
    const QPoint &p, QWidget *parent)
332 333
{
    const QColor background = parent->palette().color(QPalette::Normal, QPalette::Base);
hjk's avatar
hjk committed
334 335 336 337
    MemoryViewSetupData data;
    data.startAddress = atPointerAddress ? pointerAddressOf(m) : addressOf(m);
    if (!data.startAddress)
         return;
338
    // Fixme: Get the size of pointee (see variableMemoryMarkup())?
339
    const QString rootToolTip = variableToolTip(nameOf(m), typeOf(m), 0);
340
    const quint64 typeSize = sizeOf(m);
341
    const bool sizeIsEstimate = atPointerAddress || !typeSize;
342
    const quint64 size    = sizeIsEstimate ? 1024 : typeSize;
hjk's avatar
hjk committed
343 344 345
    data.markup = variableMemoryMarkup(m.model(), m, nameOf(m), rootToolTip,
                             data.startAddress, size,
                             engine->registerHandler()->registerMap(),
346
                             sizeIsEstimate, background);
hjk's avatar
hjk committed
347 348
    data.flags = separateView ? DebuggerEngine::MemoryView|DebuggerEngine::MemoryReadOnly : 0;
    QString pat = atPointerAddress
349
        ?  WatchTreeView::tr("Memory at Pointer's Address \"%1\" (0x%2)")
hjk's avatar
hjk committed
350 351 352 353 354
        : WatchTreeView::tr("Memory at Object's Address \"%1\" (0x%2)");
    data.title = pat.arg(nameOf(m)).arg(data.startAddress, 0, 16);
    data.pos = p;
    data.parent = parent;
    engine->openMemoryView(data);
355 356
}

357 358
// Add a memory view of the stack layout showing local variables
// and registers.
hjk's avatar
hjk committed
359 360
static void addStackLayoutMemoryView(DebuggerEngine *engine, bool separateView,
    const QAbstractItemModel *m, const QPoint &p, QWidget *parent)
361
{
362
    QTC_ASSERT(engine && m, return);
363

hjk's avatar
hjk committed
364
    // Determine suitable address range from locals.
dt_'s avatar
dt_ committed
365
    quint64 start = Q_UINT64_C(0xFFFFFFFFFFFFFFFF);
366
    quint64 end = 0;
367 368 369
    const QModelIndex localsIndex = m->index(0, 0);
    QTC_ASSERT(localsIndex.data(LocalsINameRole).toString() == QLatin1String("local"), return);
    const int localsItemCount = m->rowCount(localsIndex);
370 371
    // Note: Unsorted by default. Exclude 'Automatically dereferenced
    // pointer' items as they are outside the address range.
372 373
    for (int r = 0; r < localsItemCount; r++) {
        const QModelIndex idx = localsIndex.child(r, 0);
374 375
        const quint64 pointerAddress = pointerAddressOf(idx);
        if (pointerAddress == 0) {
376 377 378 379
            const quint64 address = addressOf(idx);
            if (address) {
                if (address < start)
                    start = address;
380 381 382
                const uint size = qMax(1u, sizeOf(idx));
                if (address + size > end)
                    end = address + size;
383
            }
384 385
        }
    }
386 387
    if (const quint64 remainder = end % 8)
        end += 8 - remainder;
388 389
    // Anything found and everything in a sensible range (static data in-between)?
    if (end <= start || end - start > 100 * 1024) {
390
        Core::AsynchronousMessageBox::information(
hjk's avatar
hjk committed
391 392
            WatchTreeView::tr("Cannot Display Stack Layout"),
            WatchTreeView::tr("Could not determine a suitable address range."));
393 394 395 396
        return;
    }
    // Take a look at the register values. Extend the range a bit if suitable
    // to show stack/stack frame pointers.
hjk's avatar
hjk committed
397 398
    const RegisterMap regMap = engine->registerHandler()->registerMap();
    for (auto it = regMap.constBegin(), cend = regMap.constEnd(); it != cend; ++it) {
399
        const quint64 value = it.key();
400
        if (value < start && start - value < 512)
401
            start = value;
402
        else if (value > end && value - end < 512)
403 404 405
            end = value + 1;
    }
    // Indicate all variables.
hjk's avatar
hjk committed
406
    MemoryViewSetupData data;
407
    const QColor background = parent->palette().color(QPalette::Normal, QPalette::Base);
hjk's avatar
hjk committed
408 409
    data.startAddress = start;
    data.markup = variableMemoryMarkup(m, localsIndex, QString(),
410 411
                             QString(), start, end - start,
                             regMap, true, background);
hjk's avatar
hjk committed
412
    data.flags = separateView
hjk's avatar
hjk committed
413
        ? (DebuggerEngine::MemoryView|DebuggerEngine::MemoryReadOnly) : 0;
hjk's avatar
hjk committed
414 415 416 417
    data.title = WatchTreeView::tr("Memory Layout of Local Variables at 0x%1").arg(start, 0, 16);
    data.pos = p;
    data.parent = parent;
    engine->openMemoryView(data);
418 419
}

con's avatar
con committed
420 421 422 423 424 425
/////////////////////////////////////////////////////////////////////
//
// WatchWindow
//
/////////////////////////////////////////////////////////////////////

426
WatchTreeView::WatchTreeView(WatchType type)
427
  : m_type(type), m_sliderPosition(0)
con's avatar
con committed
428
{
429
    setObjectName(QLatin1String("WatchWindow"));
430
    m_grabbing = false;
431
    setWindowTitle(tr("Locals and Expressions"));
hjk's avatar
hjk committed
432 433 434 435 436 437
    setIndentation(indentation() * 9/10);
    setUniformRowHeights(true);
    setItemDelegate(new WatchDelegate(this));
    setDragEnabled(true);
    setAcceptDrops(true);
    setDropIndicatorShown(true);
con's avatar
con committed
438

439 440
    connect(this, &QTreeView::expanded, this, &WatchTreeView::expandNode);
    connect(this, &QTreeView::collapsed, this, &WatchTreeView::collapseNode);
441 442
}

hjk's avatar
hjk committed
443
void WatchTreeView::expandNode(const QModelIndex &idx)
444
{
445
    setModelData(LocalsExpandedRole, true, idx);
446 447
}

hjk's avatar
hjk committed
448
void WatchTreeView::collapseNode(const QModelIndex &idx)
449
{
450
    setModelData(LocalsExpandedRole, false, idx);
con's avatar
con committed
451 452
}

hjk's avatar
hjk committed
453
void WatchTreeView::keyPressEvent(QKeyEvent *ev)
454
{
hjk's avatar
hjk committed
455
    if (ev->key() == Qt::Key_Delete && m_type == WatchersType) {
456
        WatchHandler *handler = currentEngine()->watchHandler();
457
        foreach (const QModelIndex &idx, activeRows())
458
            handler->removeItemByIName(idx.data(LocalsINameRole).toByteArray());
459 460 461
    } else if (ev->key() == Qt::Key_Return
            && ev->modifiers() == Qt::ControlModifier
            && m_type == LocalsType) {
hjk's avatar
hjk committed
462
        QModelIndex idx = currentIndex();
hjk's avatar
hjk committed
463 464
        QModelIndex idx1 = idx.sibling(idx.row(), 0);
        QString exp = model()->data(idx1).toString();
465
        watchExpression(exp);
466
    }
hjk's avatar
hjk committed
467
    BaseTreeView::keyPressEvent(ev);
468 469
}

hjk's avatar
hjk committed
470
void WatchTreeView::dragEnterEvent(QDragEnterEvent *ev)
471
{
hjk's avatar
hjk committed
472
    //BaseTreeView::dragEnterEvent(ev);
473
    if (ev->mimeData()->hasText()) {
474 475 476 477 478
        ev->setDropAction(Qt::CopyAction);
        ev->accept();
    }
}

hjk's avatar
hjk committed
479
void WatchTreeView::dragMoveEvent(QDragMoveEvent *ev)
480
{
hjk's avatar
hjk committed
481
    //BaseTreeView::dragMoveEvent(ev);
482
    if (ev->mimeData()->hasText()) {
483 484 485 486 487
        ev->setDropAction(Qt::CopyAction);
        ev->accept();
    }
}

hjk's avatar
hjk committed
488
void WatchTreeView::dropEvent(QDropEvent *ev)
489
{
490
    if (ev->mimeData()->hasText()) {
491 492 493 494
        QString exp;
        QString data = ev->mimeData()->text();
        foreach (const QChar c, data)
            exp.append(c.isPrint() ? c : QChar(QLatin1Char(' ')));
495
        currentEngine()->watchHandler()->watchVariable(exp);
496 497 498 499
        //ev->acceptProposedAction();
        ev->setDropAction(Qt::CopyAction);
        ev->accept();
    }
hjk's avatar
hjk committed
500
    //BaseTreeView::dropEvent(ev);
501 502
}

hjk's avatar
hjk committed
503
void WatchTreeView::mouseDoubleClickEvent(QMouseEvent *ev)
504 505 506
{
    const QModelIndex idx = indexAt(ev->pos());
    if (!idx.isValid()) {
507
        inputNewExpression();
508 509
        return;
    }
hjk's avatar
hjk committed
510
    BaseTreeView::mouseDoubleClickEvent(ev);
511 512
}

hjk's avatar
hjk committed
513 514
// Text for add watch action with truncated expression.
static QString addWatchActionText(QString exp)
515 516
{
    if (exp.isEmpty())
517
        return WatchTreeView::tr("Add Expression Evaluator");
518 519 520 521
    if (exp.size() > 30) {
        exp.truncate(30);
        exp.append(QLatin1String("..."));
    }
522
    return WatchTreeView::tr("Add Expression Evaluator for \"%1\"").arg(exp);
523 524
}

hjk's avatar
hjk committed
525 526
// Text for add watch action with truncated expression.
static QString removeWatchActionText(QString exp)
527 528
{
    if (exp.isEmpty())
529
        return WatchTreeView::tr("Remove Expression Evaluator");
530 531 532 533
    if (exp.size() > 30) {
        exp.truncate(30);
        exp.append(QLatin1String("..."));
    }
534 535
    return WatchTreeView::tr("Remove Expression Evaluator for \"%1\"")
        .arg(exp.replace(QLatin1Char('&'), QLatin1String("&&")));
536 537
}

hjk's avatar
hjk committed
538
static void copyToClipboard(const QString &clipboardText)
539 540 541 542 543 544
{
    QClipboard *clipboard = QApplication::clipboard();
    clipboard->setText(clipboardText, QClipboard::Selection);
    clipboard->setText(clipboardText, QClipboard::Clipboard);
}

hjk's avatar
hjk committed
545
void WatchTreeView::fillFormatMenu(QMenu *formatMenu, const QModelIndex &mi)
con's avatar
con committed
546
{
hjk's avatar
hjk committed
547 548 549
    QTC_CHECK(mi.isValid());

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

552 553 554 555
    const DisplayFormats alternativeFormats =
        mi.data(LocalsTypeFormatListRole).value<DisplayFormats>();
    const int typeFormat = mi.data(LocalsTypeFormatRole).toInt();
    const int individualFormat = mi.data(LocalsIndividualFormatRole).toInt();
556
    const int unprintableBase = WatchHandler::unprintableBase();
557

558
    QAction *showUnprintableUnicode = 0;
559
    QAction *showUnprintableEscape = 0;
560 561
    QAction *showUnprintableOctal = 0;
    QAction *showUnprintableHexadecimal = 0;
562
    showUnprintableUnicode =
hjk's avatar
hjk committed
563
        formatMenu->addAction(tr("Treat All Characters as Printable"));
564 565
    showUnprintableUnicode->setCheckable(true);
    showUnprintableUnicode->setChecked(unprintableBase == 0);
566
    showUnprintableEscape =
hjk's avatar
hjk committed
567
        formatMenu->addAction(tr("Show Unprintable Characters as Escape Sequences"));
568 569
    showUnprintableEscape->setCheckable(true);
    showUnprintableEscape->setChecked(unprintableBase == -1);
570
    showUnprintableOctal =
hjk's avatar
hjk committed
571
        formatMenu->addAction(tr("Show Unprintable Characters as Octal"));
572 573 574
    showUnprintableOctal->setCheckable(true);
    showUnprintableOctal->setChecked(unprintableBase == 8);
    showUnprintableHexadecimal =
hjk's avatar
hjk committed
575
        formatMenu->addAction(tr("Show Unprintable Characters as Hexadecimal"));
576 577
    showUnprintableHexadecimal->setCheckable(true);
    showUnprintableHexadecimal->setChecked(unprintableBase == 16);
hjk's avatar
hjk committed
578

579 580 581 582
    connect(showUnprintableUnicode, &QAction::triggered, [this] { showUnprintable(0); });
    connect(showUnprintableEscape, &QAction::triggered, [this] { showUnprintable(-1); });
    connect(showUnprintableOctal, &QAction::triggered, [this] { showUnprintable(8); });
    connect(showUnprintableHexadecimal, &QAction::triggered, [this] { showUnprintable(16); });
hjk's avatar
hjk committed
583 584 585 586 587 588 589 590

    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)")
591
            .arg(WatchHandler::nameForFormat(typeFormat))
Thiago Macieira's avatar
Thiago Macieira committed
592
        : QString(tr("Use Display Format Based on Type") + QLatin1Char(' '));
hjk's avatar
hjk committed
593 594 595 596

    QAction *clearIndividualFormatAction = formatMenu->addAction(spacer + msg);
    clearIndividualFormatAction->setCheckable(true);
    clearIndividualFormatAction->setChecked(individualFormat == AutomaticFormat);
597 598 599 600 601
    connect(clearIndividualFormatAction, &QAction::triggered, [this] {
        const QModelIndexList active = activeRows();
        foreach (const QModelIndex &idx, active)
            setModelData(LocalsIndividualFormatRole, AutomaticFormat, idx);
    });
hjk's avatar
hjk committed
602 603

    for (int i = 0; i != alternativeFormats.size(); ++i) {
604 605
        const int format = alternativeFormats.at(i);
        const QString display = spacer + WatchHandler::nameForFormat(format);
hjk's avatar
hjk committed
606 607 608 609
        QAction *act = new QAction(display, formatMenu);
        act->setCheckable(true);
        act->setChecked(format == individualFormat);
        formatMenu->addAction(act);
610 611 612
        connect(act, &QAction::triggered, [this, act, format, mi] {
            setModelData(LocalsIndividualFormatRole, format, mi);
        });
613 614
    }

hjk's avatar
hjk committed
615 616 617 618 619 620 621
    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);
622 623 624 625 626
    connect(clearTypeFormatAction, &QAction::triggered, [this] {
        const QModelIndexList active = activeRows();
        foreach (const QModelIndex &idx, active)
            setModelData(LocalsTypeFormatRole, AutomaticFormat, idx);
    });
hjk's avatar
hjk committed
627 628

    for (int i = 0; i != alternativeFormats.size(); ++i) {
629 630
        const int format = alternativeFormats.at(i);
        const QString display = spacer + WatchHandler::nameForFormat(format);
hjk's avatar
hjk committed
631 632 633 634
        QAction *act = new QAction(display, formatMenu);
        act->setCheckable(true);
        act->setChecked(format == typeFormat);
        formatMenu->addAction(act);
635 636 637
        connect(act, &QAction::triggered, [this, act, format, mi] {
            setModelData(LocalsTypeFormatRole, format, mi);
        });
hjk's avatar
hjk committed
638 639 640
    }
}

641
void WatchTreeView::showUnprintable(int base)
hjk's avatar
hjk committed
642 643 644
{
    DebuggerEngine *engine = currentEngine();
    WatchHandler *handler = engine->watchHandler();
645
    handler->setUnprintableBase(base);
hjk's avatar
hjk committed
646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664
}

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
665
    const bool actionsEnabled = engine->debuggerActionsEnabled();
666
    const bool canHandleWatches = engine->hasCapability(AddWatcherCapability);
hjk's avatar
hjk committed
667
    const DebuggerState state = engine->state();
668
    const bool canInsertWatches = state == InferiorStopOk
669
        || state == DebuggerNotReady
670
        || state == InferiorUnrunnable
671
        || (state == InferiorRunOk && engine->hasCapability(AddWatcherWhileRunningCapability));
672

673 674
    QAction actSetWatchpointAtObjectAddress(0);
    QAction actSetWatchpointAtPointerAddress(0);
675 676
    actSetWatchpointAtPointerAddress.setText(tr("Add Data Breakpoint at Pointer's Address"));
    actSetWatchpointAtPointerAddress.setEnabled(false);
677
    const bool canSetWatchpoint = engine->hasCapability(WatchpointByAddressCapability);
678
    if (canSetWatchpoint && address) {
679 680 681 682
        actSetWatchpointAtObjectAddress
            .setText(tr("Add Data Breakpoint at Object's Address (0x%1)").arg(address, 0, 16));
        actSetWatchpointAtObjectAddress
            .setChecked(mi0.data(LocalsIsWatchpointAtObjectAddressRole).toBool());
683
        if (createPointerActions) {
684 685 686 687 688
            actSetWatchpointAtPointerAddress
                .setText(tr("Add Data Breakpoint at Pointer's Address (0x%1)")
                    .arg(pointerAddress, 0, 16));
            actSetWatchpointAtPointerAddress
                .setChecked(mi0.data(LocalsIsWatchpointAtPointerAddressRole).toBool());
689
            actSetWatchpointAtPointerAddress.setEnabled(true);
690
        }
691
    } else {
692 693
        actSetWatchpointAtObjectAddress.setText(tr("Add Data Breakpoint"));
        actSetWatchpointAtObjectAddress.setEnabled(false);
694
    }
695
    actSetWatchpointAtObjectAddress.setToolTip(
696 697 698
        tr("Setting a data breakpoint on an address will cause the program "
           "to stop when the data at the address is modified."));

699
    QAction actSetWatchpointAtExpression(0);
700
    const bool canSetWatchpointAtExpression = engine->hasCapability(WatchpointByExpressionCapability);
701
    if (name.isEmpty() || !canSetWatchpointAtExpression) {
702 703
        actSetWatchpointAtExpression.setText(tr("Add Data Breakpoint at Expression"));
        actSetWatchpointAtExpression.setEnabled(false);
704
    } else {
705
        actSetWatchpointAtExpression.setText(tr("Add Data Breakpoint at Expression \"%1\"").arg(name));
706
    }
707
    actSetWatchpointAtExpression.setToolTip(
708 709 710 711
        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."));

712 713
    QAction actInsertNewWatchItem(tr("Add New Expression Evaluator..."), 0);
    actInsertNewWatchItem.setEnabled(canHandleWatches && canInsertWatches);
714

715 716
    QAction actSelectWidgetToWatch(tr("Select Widget to Add into Expression Evaluator"), 0);
    actSelectWidgetToWatch.setEnabled(canHandleWatches && canInsertWatches
717 718
           && engine->hasCapability(WatchWidgetsCapability));

719 720 721 722
    bool canAddWatches = canHandleWatches && !exp.isEmpty();
    // Suppress for top-level watchers.
    if (m_type == WatchersType && mi0.parent().isValid() && !mi0.parent().parent().isValid())
        canAddWatches = false;
723
    QAction actWatchExpression(addWatchActionText(exp), 0);
724
    actWatchExpression.setEnabled(canAddWatches);
725

726
    // Can remove watch if engine can handle it or session engine.
727
    QModelIndex p = mi0;
728 729 730 731 732 733
    while (true) {
        QModelIndex pp = p.parent();
        if (!pp.isValid() || !pp.parent().isValid())
            break;
        p = pp;
    }
734

hjk's avatar
hjk committed
735 736
    bool canRemoveWatches = ((canHandleWatches && canInsertWatches) || state == DebuggerNotReady)
                             && m_type == WatchersType;
737

738
    QString removeExp = p.data(LocalsExpressionRole).toString();
739
    QAction actRemoveWatchExpression(removeWatchActionText(removeExp), 0);
hjk's avatar
hjk committed
740
    actRemoveWatchExpression.setEnabled(canRemoveWatches && !exp.isEmpty());
741 742

    QAction actRemoveAllWatchExpression(tr("Remove All Expression Evaluators"), 0);
hjk's avatar
hjk committed
743
    actRemoveAllWatchExpression.setEnabled(canRemoveWatches
744
                                           && !handler->watchedExpressions().isEmpty());
745

746
    QMenu formatMenu(tr("Change Value Display Format"));
747
    if (mi0.isValid())
hjk's avatar
hjk committed
748
        fillFormatMenu(&formatMenu, mi0);
749 750
    else
        formatMenu.setEnabled(false);
hjk's avatar
hjk committed
751

752
    QMenu memoryMenu(tr("Open Memory Editor"));
753 754 755 756 757 758
    QAction actOpenMemoryEditAtObjectAddress(0);
    QAction actOpenMemoryEditAtPointerAddress(0);
    QAction actOpenMemoryEditor(0);
    QAction actOpenMemoryEditorStackLayout(0);
    QAction actOpenMemoryViewAtObjectAddress(0);
    QAction actOpenMemoryViewAtPointerAddress(0);
759
    if (engine->hasCapability(ShowMemoryCapability)) {
760
        actOpenMemoryEditor.setText(tr("Open Memory Editor..."));
761
        if (address) {
762
            actOpenMemoryEditAtObjectAddress.setText(
763 764
                tr("Open Memory Editor at Object's Address (0x%1)")
                    .arg(address, 0, 16));
765
            actOpenMemoryViewAtObjectAddress.setText(
766 767
                    tr("Open Memory View at Object's Address (0x%1)")
                        .arg(address, 0, 16));
768
        } else {
769
            actOpenMemoryEditAtObjectAddress.setText(
770
                tr("Open Memory Editor at Object's Address"));
771 772
            actOpenMemoryEditAtObjectAddress.setEnabled(false);
            actOpenMemoryViewAtObjectAddress.setText(
773
                    tr("Open Memory View at Object's Address"));
774
            actOpenMemoryViewAtObjectAddress.setEnabled(false);
775 776
        }
        if (createPointerActions) {
777
            actOpenMemoryEditAtPointerAddress.setText(
778 779
                tr("Open Memory Editor at Pointer's Address (0x%1)")
                    .arg(pointerAddress, 0, 16));
780
            actOpenMemoryViewAtPointerAddress.setText(
781 782
                tr("Open Memory View at Pointer's Address (0x%1)")
                    .arg(pointerAddress, 0, 16));
783
        } else {
784
            actOpenMemoryEditAtPointerAddress.setText(
785
                tr("Open Memory Editor at Pointer's Address"));
786 787
            actOpenMemoryEditAtPointerAddress.setEnabled(false);
            actOpenMemoryViewAtPointerAddress.setText(
788
                tr("Open Memory View at Pointer's Address"));
789
            actOpenMemoryViewAtPointerAddress.setEnabled(false);
790
        }
791
        actOpenMemoryEditorStackLayout.setText(
792
            tr("Open Memory Editor Showing Stack Layout"));
793 794 795 796 797 798 799
        actOpenMemoryEditorStackLayout.setEnabled(m_type == LocalsType);
        memoryMenu.addAction(&actOpenMemoryViewAtObjectAddress);
        memoryMenu.addAction(&actOpenMemoryViewAtPointerAddress);
        memoryMenu.addAction(&actOpenMemoryEditAtObjectAddress);
        memoryMenu.addAction(&actOpenMemoryEditAtPointerAddress);
        memoryMenu.addAction(&actOpenMemoryEditorStackLayout);
        memoryMenu.addAction(&actOpenMemoryEditor);
800 801 802 803
    } else {
        memoryMenu.setEnabled(false);
    }

804
    QMenu breakpointMenu;
805
    breakpointMenu.setTitle(tr("Add Data Breakpoint"));
806
    breakpointMenu.addAction(&actSetWatchpointAtObjectAddress);
807
    breakpointMenu.addAction(&actSetWatchpointAtPointerAddress);