watchwindow.cpp 43.6 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
** 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
Eike Ziller's avatar
Eike Ziller committed
13 14
** conditions see http://www.qt.io/licensing.  For further information
** 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.
hjk's avatar
hjk committed
24 25 26
**
** 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
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 32
#include "watchwindow.h"

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

45 46
#include <texteditor/syntaxhighlighter.h>

47 48
#include <coreplugin/messagebox.h>

49
#include <utils/qtcassert.h>
50
#include <utils/savedaction.h>
hjk's avatar
hjk committed
51
#include <utils/fancylineedit.h>
52

53 54
#include <QApplication>
#include <QClipboard>
55
#include <QDebug>
56
#include <QHeaderView>
57
#include <QInputDialog>
58 59
#include <QItemDelegate>
#include <QMenu>
60 61 62 63
#include <QMetaProperty>
#include <QMimeData>
#include <QScrollBar>
#include <QTimer>
con's avatar
con committed
64

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

72 73 74 75 76 77
//#define USE_WATCH_MODEL_TEST 1

#if USE_WATCH_MODEL_TEST
#include <modeltest.h>
#endif

hjk's avatar
hjk committed
78 79
Q_DECLARE_METATYPE(QModelIndex)

80 81 82 83 84 85
/////////////////////////////////////////////////////////////////////
//
// WatchDelegate
//
/////////////////////////////////////////////////////////////////////

86 87 88
namespace Debugger {
namespace Internal {

hjk's avatar
hjk committed
89 90
const char CurrentIndex[] = "CurrentIndex";

91 92 93
class WatchDelegate : public QItemDelegate
{
public:
94 95
    explicit WatchDelegate(QObject *parent)
        : QItemDelegate(parent)
96
    {}
97 98

    QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &,
99
        const QModelIndex &index) const
100
    {
101 102
        // Value column: Custom editor. Apply integer-specific settings.
        if (index.column() == 1) {
103 104
            const QVariant::Type type =
                static_cast<QVariant::Type>(index.data(LocalsEditTypeRole).toInt());
105 106 107 108 109 110 111
            switch (type) {
            case QVariant::Bool:
                return new BooleanComboBox(parent);
            default:
                break;
            }
            WatchLineEdit *edit = WatchLineEdit::create(type, parent);
112 113 114 115
            edit->setFrame(false);
            IntegerWatchLineEdit *intEdit
                = qobject_cast<IntegerWatchLineEdit *>(edit);
            if (intEdit)
116 117 118
                intEdit->setBase(index.data(LocalsIntegerBaseRole).toInt());
            return edit;
        }
119 120

        // Standard line edits for the rest.
hjk's avatar
hjk committed
121
        Utils::FancyLineEdit *lineEdit = new Utils::FancyLineEdit(parent);
122
        lineEdit->setFrame(false);
hjk's avatar
hjk committed
123
        lineEdit->setHistoryCompleter(QLatin1String("WatchItems"));
124
        return lineEdit;
125 126
    }

127
    void setModelData(QWidget *editor, QAbstractItemModel *model,
128
                      const QModelIndex &index) const
129
    {
130 131 132 133 134
        // 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);
135 136
            return;
        }
137 138
        const QMetaProperty userProperty = editor->metaObject()->userProperty();
        QTC_ASSERT(userProperty.isValid(), return);
139
        const QString value = editor->property(userProperty.name()).toString();
140
        const QString exp = index.data(LocalsExpressionRole).toString();
141 142
        if (exp == value)
            return;
143 144
        WatchHandler *handler = currentEngine()->watchHandler();
        handler->removeData(index.data(LocalsINameRole).toByteArray());
145
        handler->watchExpression(value);
146 147 148 149 150 151 152 153 154
    }

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

155 156
// Watch model query helpers.
static inline quint64 addressOf(const QModelIndex &m)
157
{
158
    return m.data(LocalsObjectAddressRole).toULongLong();
159 160
}

161
static inline quint64 pointerAddressOf(const QModelIndex &m)
162
{
163
    return m.data(LocalsPointerAddressRole).toULongLong();
164 165
}

166
static inline QString nameOf(const QModelIndex &m)
167 168 169 170
{
    return m.data().toString();
}

171
static inline QString typeOf(const QModelIndex &m)
172 173 174 175
{
    return m.data(LocalsTypeRole).toString();
}

176
static inline uint sizeOf(const QModelIndex &m)
177 178 179
{
    return m.data(LocalsSizeRole).toUInt();
}
180

hjk's avatar
hjk committed
181
// Create a map of value->name for register markup.
182 183 184 185 186 187 188 189 190
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)
191
            result.insert(v.toULongLong(), QString::fromLatin1(reg.name));
192 193 194 195
    }
    return result;
}

196 197 198 199 200
// 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
201 202
typedef QPair<int, QString> ColorNumberToolTip;
typedef QVector<ColorNumberToolTip> ColorNumberToolTips;
203

hjk's avatar
hjk committed
204 205
static QString variableToolTip(const QString &name, const QString &type,
    quint64 offset)
206 207 208
{
    return offset ?
           //: HTML tooltip of a variable in the memory editor
hjk's avatar
hjk committed
209
           WatchTreeView::tr("<i>%1</i> %2 at #%3").
210 211
               arg(type, name).arg(offset) :
           //: HTML tooltip of a variable in the memory editor
hjk's avatar
hjk committed
212
           WatchTreeView::tr("<i>%1</i> %2").arg(type, name);
213 214
}

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

249
typedef QList<MemoryMarkup> MemoryMarkupList;
250

251
/*!
Leena Miettinen's avatar
Leena Miettinen committed
252
    Creates markup for a variable in the memory view.
253

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

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

278 279 280 281 282 283
   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.

284 285 286
   \note To recurse over the top level items of the model, pass an invalid model
   index.

287 288
    \sa Debugger::Internal::MemoryViewWidget
*/
hjk's avatar
hjk committed
289
static MemoryMarkupList
290 291 292 293 294 295
    variableMemoryMarkup(const QAbstractItemModel *model,
                         const QModelIndex &modelIndex,
                         const QString &rootName,
                         const QString &rootToolTip,
                         quint64 address, quint64 size,
                         const RegisterMap &registerMap,
296 297 298 299
                         bool sizeIsEstimate,
                         const QColor &defaultBackground)
{
    enum { debug = 0 };
300
    enum { registerColorNumber = 0x3453 };
301

302 303 304 305
    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,
306
    // leaving the padding areas of the parent colored with the base color.
307
    MemoryMarkupList result;
308
    int colorNumber = 0;
hjk's avatar
hjk committed
309
    ColorNumberToolTips ranges(size, ColorNumberToolTip(colorNumber, rootToolTip));
310
    colorNumber++;
311 312 313
    const int childCount = memberVariableRecursion(model, modelIndex,
                                                   rootName, address, address + size,
                                                   &colorNumber, &ranges);
314 315
    if (sizeIsEstimate && !childCount)
        return result; // Fixme: Exact size not known, no point in filling if no children.
316 317
    // Punch in registers as 1-byte markers on top.
    const RegisterMapConstIt regcEnd = registerMap.constEnd();
318 319 320 321
    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
322
                ranges[offset] = ColorNumberToolTip(registerColorNumber,
hjk's avatar
hjk committed
323
                           WatchTreeView::tr("Register <i>%1</i>").arg(it.value()));
324 325 326 327 328
            } else {
                break; // Sorted.
            }
        }
    } // for registers.
329 330
    if (debug) {
        QDebug dbg = qDebug().nospace();
331
        dbg << rootToolTip << ' ' << address << ' ' << size << '\n';
332
        QString name;
333
        for (unsigned i = 0; i < size; ++i)
334
            if (name != ranges.at(i).second) {
hjk's avatar
hjk committed
335 336
                dbg << ",[" << i << ' ' << ranges.at(i).first << ' '
                    << ranges.at(i).second << ']';
337 338 339 340
                name = ranges.at(i).second;
            }
    }

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

    if (debug) {
        QDebug dbg = qDebug().nospace();
362
        dbg << rootName << ' ' << address << ' ' << size << '\n';
363
        QString name;
364
        for (unsigned i = 0; i < size; ++i)
365
            if (name != ranges.at(i).second) {
hjk's avatar
hjk committed
366 367
                dbg << ',' << i << ' ' << ranges.at(i).first << ' '
                    << ranges.at(i).second;
368 369 370
                name = ranges.at(i).second;
            }
        dbg << '\n';
371 372
        foreach (const MemoryMarkup &m, result)
            dbg << m.address <<  ' ' << m.length << ' '  << m.toolTip << '\n';
373 374 375 376 377 378
    }

    return result;
}

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

407 408
// Add a memory view of the stack layout showing local variables
// and registers.
hjk's avatar
hjk committed
409 410
static void addStackLayoutMemoryView(DebuggerEngine *engine, bool separateView,
    const QAbstractItemModel *m, const QPoint &p, QWidget *parent)
411
{
412
    QTC_ASSERT(engine && m, return);
413

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

con's avatar
con committed
469 470 471 472 473 474
/////////////////////////////////////////////////////////////////////
//
// WatchWindow
//
/////////////////////////////////////////////////////////////////////

475
WatchTreeView::WatchTreeView(WatchType type)
476
  : m_type(type), m_sliderPosition(0)
con's avatar
con committed
477
{
478
    setObjectName(QLatin1String("WatchWindow"));
479
    m_grabbing = false;
480
    setWindowTitle(tr("Locals and Expressions"));
hjk's avatar
hjk committed
481 482 483 484 485 486
    setIndentation(indentation() * 9/10);
    setUniformRowHeights(true);
    setItemDelegate(new WatchDelegate(this));
    setDragEnabled(true);
    setAcceptDrops(true);
    setDropIndicatorShown(true);
con's avatar
con committed
487

hjk's avatar
hjk committed
488
    connect(this, SIGNAL(expanded(QModelIndex)),
489
        SLOT(expandNode(QModelIndex)));
hjk's avatar
hjk committed
490
    connect(this, SIGNAL(collapsed(QModelIndex)),
491
        SLOT(collapseNode(QModelIndex)));
492 493
}

hjk's avatar
hjk committed
494
void WatchTreeView::expandNode(const QModelIndex &idx)
495
{
496
    setModelData(LocalsExpandedRole, true, idx);
497 498
}

hjk's avatar
hjk committed
499
void WatchTreeView::collapseNode(const QModelIndex &idx)
500
{
501
    setModelData(LocalsExpandedRole, false, idx);
con's avatar
con committed
502 503
}

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

hjk's avatar
hjk committed
521
void WatchTreeView::dragEnterEvent(QDragEnterEvent *ev)
522
{
hjk's avatar
hjk committed
523
    //BaseTreeView::dragEnterEvent(ev);
524
    if (ev->mimeData()->hasText()) {
525 526 527 528 529
        ev->setDropAction(Qt::CopyAction);
        ev->accept();
    }
}

hjk's avatar
hjk committed
530
void WatchTreeView::dragMoveEvent(QDragMoveEvent *ev)
531
{
hjk's avatar
hjk committed
532
    //BaseTreeView::dragMoveEvent(ev);
533
    if (ev->mimeData()->hasText()) {
534 535 536 537 538
        ev->setDropAction(Qt::CopyAction);
        ev->accept();
    }
}

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

hjk's avatar
hjk committed
554
void WatchTreeView::mouseDoubleClickEvent(QMouseEvent *ev)
555 556 557
{
    const QModelIndex idx = indexAt(ev->pos());
    if (!idx.isValid()) {
558
        inputNewExpression();
559 560
        return;
    }
hjk's avatar
hjk committed
561
    BaseTreeView::mouseDoubleClickEvent(ev);
562 563
}

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

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

hjk's avatar
hjk committed
589
static void copyToClipboard(const QString &clipboardText)
590 591 592 593 594 595
{
    QClipboard *clipboard = QApplication::clipboard();
    clipboard->setText(clipboardText, QClipboard::Selection);
    clipboard->setText(clipboardText, QClipboard::Clipboard);
}

hjk's avatar
hjk committed
596
void WatchTreeView::fillFormatMenu(QMenu *formatMenu, const QModelIndex &mi)
con's avatar
con committed
597
{
hjk's avatar
hjk committed
598 599 600
    QTC_CHECK(mi.isValid());

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

hjk's avatar
hjk committed
603 604
    const TypeFormatList alternativeFormats =
        mi.data(LocalsTypeFormatListRole).value<TypeFormatList>();
605
    int typeFormat =
hjk's avatar
hjk committed
606
        mi.data(LocalsTypeFormatRole).toInt();
607
    const int individualFormat =
hjk's avatar
hjk committed
608
        mi.data(LocalsIndividualFormatRole).toInt();
609
    const int unprintableBase = WatchHandler::unprintableBase();
610

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

hjk's avatar
hjk committed
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 743 744 745 746 747
    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
748
    const bool actionsEnabled = engine->debuggerActionsEnabled();
749
    const bool canHandleWatches = engine->hasCapability(AddWatcherCapability);
hjk's avatar
hjk committed
750
    const DebuggerState state = engine->state();
751
    const bool canInsertWatches = state == InferiorStopOk
752
        || state == DebuggerNotReady
753
        || state == InferiorUnrunnable
754
        || (state == InferiorRunOk && engine->hasCapability(AddWatcherWhileRunningCapability));
755

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

782
    QAction actSetWatchpointAtExpression(0);
783
    const bool canSetWatchpointAtExpression = engine->hasCapability(WatchpointByExpressionCapability);
784
    if (name.isEmpty() || !canSetWatchpointAtExpression) {
785 786
        actSetWatchpointAtExpression.setText(tr("Add Data Breakpoint at Expression"));
        actSetWatchpointAtExpression.setEnabled(false);
787
    } else {
788
        actSetWatchpointAtExpression.setText(tr("Add Data Breakpoint at Expression \"%1\"").arg(name));
789
    }
790
    actSetWatchpointAtExpression.setToolTip(
791 792 793 794
        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."));

795 796
    QAction actInsertNewWatchItem(tr("Add New Expression Evaluator..."), 0);
    actInsertNewWatchItem.setEnabled(canHandleWatches && canInsertWatches);
797

798 799
    QAction actSelectWidgetToWatch(tr("Select Widget to Add into Expression Evaluator"), 0);
    actSelectWidgetToWatch.setEnabled(canHandleWatches && canInsertWatches
800 801
           && engine->hasCapability(WatchWidgetsCapability));

802 803
    QAction actWatchExpression(addWatchActionText(exp), 0);
    actWatchExpression.setEnabled(
804
        canHandleWatches && !exp.isEmpty() && m_type == LocalsType);
805

806
    // Can remove watch if engine can handle it or session engine.
807
    QModelIndex p = mi0;
808 809 810 811 812 813
    while (true) {
        QModelIndex pp = p.parent();
        if (!pp.isValid() || !pp.parent().isValid())
            break;
        p = pp;
    }
814 815 816

    bool showExpressionActions = (canHandleWatches || state == DebuggerNotReady) && m_type == WatchersType;

817
    QString removeExp = p.data(LocalsExpressionRole).toString();
818
    QAction actRemoveWatchExpression(removeWatchActionText(removeExp), 0);
819 820 821 822 823
    actRemoveWatchExpression.setEnabled(showExpressionActions && !exp.isEmpty());

    QAction actRemoveAllWatchExpression(tr("Remove All Expression Evaluators"), 0);
    actRemoveAllWatchExpression.setEnabled(showExpressionActions
                                           && !handler->watchedExpressions().isEmpty());
824

825
    QMenu formatMenu(tr("Change Local Display Format..."));
826
    if (mi0.isValid())
hjk's avatar
hjk committed
827 828
        fillFormatMenu(&formatMenu, mi0);

829 830 831 832 833 834 835
    QMenu memoryMenu(tr("Open Memory Editor..."));
    QAction actOpenMemoryEditAtObjectAddress(0);
    QAction actOpenMemoryEditAtPointerAddress(0);
    QAction actOpenMemoryEditor(0);
    QAction actOpenMemoryEditorStackLayout(0);
    QAction actOpenMemoryViewAtObjectAddress(0);
    QAction actOpenMemoryViewAtPointerAddress(0);
836
    if (engine->hasCapability(ShowMemoryCapability)) {
837
        actOpenMemoryEditor.setText(tr("Open Memory Editor..."));
838
        if (address) {
839
            actOpenMemoryEditAtObjectAddress.setText(
840 841
                tr("Open Memory Editor at Object's Address (0x%1)")
                    .arg(address, 0, 16));
842
            actOpenMemoryViewAtObjectAddress.setText(
843 844
                    tr("Open Memory View at Object's Address (0x%1)")
                        .arg(address, 0, 16));
845
        } else {
846
            actOpenMemoryEditAtObjectAddress.setText(
847
                tr("Open Memory Editor at Object's Address"));
848 849
            actOpenMemoryEditAtObjectAddress.setEnabled(false);
            actOpenMemoryViewAtObjectAddress.setText(
850
                    tr("Open Memory View at Object's Address"));
851
            actOpenMemoryViewAtObjectAddress.setEnabled(false);
852 853
        }
        if (createPointerActions) {
854
            actOpenMemoryEditAtPointerAddress.setText(
855 856
                tr("Open Memory Editor at Pointer's Address (0x%1)")
                    .arg(pointerAddress, 0, 16));
857
            actOpenMemoryViewAtPointerAddress.setText(
858 859
                tr("Open Memory View at Pointer's Address (0x%1)")
                    .arg(pointerAddress, 0, 16));