watchwindow.cpp 18.8 KB
Newer Older
1
/**************************************************************************
con's avatar
con committed
2
3
4
**
** This file is part of Qt Creator
**
hjk's avatar
hjk committed
5
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
con's avatar
con committed
6
**
7
** Contact: Nokia Corporation (qt-info@nokia.com)
con's avatar
con committed
8
**
9
** Commercial Usage
10
**
11
12
13
14
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
15
**
16
** GNU Lesser General Public License Usage
17
**
18
19
20
21
22
23
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24
**
25
** If you are unsure which license is appropriate for your use, please
hjk's avatar
hjk committed
26
** contact the sales department at http://qt.nokia.com/contact.
con's avatar
con committed
27
**
28
**************************************************************************/
hjk's avatar
hjk committed
29

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

33
#include "breakpoint.h"
hjk's avatar
hjk committed
34
#include "breakhandler.h"
35
#include "debuggeractions.h"
36
#include "debuggeragents.h"
37
#include "debuggerdialogs.h"
38
#include "debuggermanager.h"
39
#include "idebuggerengine.h"
40

41
#include <utils/qtcassert.h>
42
#include <utils/savedaction.h>
43

con's avatar
con committed
44
45
46
47
48
#include <QtCore/QDebug>
#include <QtCore/QTimer>

#include <QtGui/QAction>
#include <QtGui/QContextMenuEvent>
49
50
#include <QtGui/QDialog>
#include <QtGui/QHBoxLayout>
con's avatar
con committed
51
#include <QtGui/QHeaderView>
52
#include <QtGui/QItemDelegate>
53
#include <QtGui/QLabel>
con's avatar
con committed
54
55
56
57
#include <QtGui/QLineEdit>
#include <QtGui/QMenu>
#include <QtGui/QResizeEvent>

58
using namespace Debugger;
con's avatar
con committed
59
60
using namespace Debugger::Internal;

61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
/////////////////////////////////////////////////////////////////////
//
// WatchDelegate
//
/////////////////////////////////////////////////////////////////////

class WatchDelegate : public QItemDelegate
{
public:
    WatchDelegate(QObject *parent) : QItemDelegate(parent) {}

    QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &,
        const QModelIndex &) const
    {
        return new QLineEdit(parent);
    }

    void setEditorData(QWidget *editor, const QModelIndex &index) const
    {
        QLineEdit *lineEdit = qobject_cast<QLineEdit *>(editor);
        QTC_ASSERT(lineEdit, return);
82
        if (index.column() == 1)
83
84
85
            lineEdit->setText(index.model()->data(index, Qt::DisplayRole).toString());
        else
            lineEdit->setText(index.model()->data(index, ExpressionRole).toString());
86
87
    }

88
    void setModelData(QWidget *editor, QAbstractItemModel *model,
89
90
        const QModelIndex &index) const
    {
91
        //qDebug() << "SET MODEL DATA";
92
93
94
        QLineEdit *lineEdit = qobject_cast<QLineEdit*>(editor);
        QTC_ASSERT(lineEdit, return);
        QString value = lineEdit->text();
95
96
        QString exp = model->data(index, ExpressionRole).toString();
        model->setData(index, value, Qt::EditRole);
97
        if (index.column() == 1) {
hjk's avatar
hjk committed
98
            // The value column.
hjk's avatar
hjk committed
99
            theDebuggerAction(AssignValue)->trigger(QString(exp + '=' + value));
hjk's avatar
hjk committed
100
        } else if (index.column() == 2) {
hjk's avatar
hjk committed
101
            // The type column.
hjk's avatar
hjk committed
102
            theDebuggerAction(AssignType)->trigger(QString(exp + '=' + value));
103
        } else if (index.column() == 0) {
hjk's avatar
hjk committed
104
            // The watcher name column.
hjk's avatar
hjk committed
105
            theDebuggerAction(RemoveWatchExpression)->trigger(exp);
hjk's avatar
hjk committed
106
            theDebuggerAction(WatchExpression)->trigger(value);
107
108
109
110
111
112
113
114
115
116
117
        }
    }

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


con's avatar
con committed
118
119
120
121
122
123
/////////////////////////////////////////////////////////////////////
//
// WatchWindow
//
/////////////////////////////////////////////////////////////////////

124
125
126
WatchWindow::WatchWindow(Type type, DebuggerManager *manager, QWidget *parent)
    : QTreeView(parent), m_alwaysResizeColumnsToContents(true), m_type(type),
        m_manager(manager)
con's avatar
con committed
127
{
128
129
    m_grabbing = false;

130
    QAction *act = theDebuggerAction(UseAlternatingRowColors);
131
    setFrameStyle(QFrame::NoFrame);
132
    setAttribute(Qt::WA_MacShowFocusRect, false);
con's avatar
con committed
133
    setWindowTitle(tr("Locals and Watchers"));
134
    setAlternatingRowColors(act->isChecked());
con's avatar
con committed
135
136
    setIndentation(indentation() * 9/10);
    setUniformRowHeights(true);
137
    setItemDelegate(new WatchDelegate(this));
138
139
140
    setDragEnabled(true);
    setAcceptDrops(true);
    setDropIndicatorShown(true);
con's avatar
con committed
141

142
143
    connect(act, SIGNAL(toggled(bool)),
        this, SLOT(setAlternatingRowColorsHelper(bool)));
144
145
146
147
    connect(this, SIGNAL(expanded(QModelIndex)),
        this, SLOT(expandNode(QModelIndex)));
    connect(this, SIGNAL(collapsed(QModelIndex)),
        this, SLOT(collapseNode(QModelIndex)));
148
149
150
151
}

void WatchWindow::expandNode(const QModelIndex &idx)
{
hjk's avatar
hjk committed
152
    model()->setData(idx, true, ExpandedRole);
153
154
155
156
}

void WatchWindow::collapseNode(const QModelIndex &idx)
{
hjk's avatar
hjk committed
157
    model()->setData(idx, false, ExpandedRole);
con's avatar
con committed
158
159
}

160
161
void WatchWindow::keyPressEvent(QKeyEvent *ev)
{
hjk's avatar
hjk committed
162
    if (ev->key() == Qt::Key_Delete && m_type == WatchersType) {
163
164
165
        QModelIndex idx = currentIndex();
        QModelIndex idx1 = idx.sibling(idx.row(), 0);
        QString exp = model()->data(idx1).toString();
hjk's avatar
hjk committed
166
        theDebuggerAction(RemoveWatchExpression)->trigger(exp);
167
168
169
    } else if (ev->key() == Qt::Key_Return
            && ev->modifiers() == Qt::ControlModifier
            && m_type == LocalsType) {
hjk's avatar
hjk committed
170
171
172
173
        QModelIndex idx = currentIndex();
        QModelIndex idx1 = idx.sibling(idx.row(), 0);
        QString exp = model()->data(idx1).toString();
        theDebuggerAction(WatchExpression)->trigger(exp);
174
175
176
177
    }
    QTreeView::keyPressEvent(ev);
}

178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
void WatchWindow::dragEnterEvent(QDragEnterEvent *ev)
{
    //QTreeView::dragEnterEvent(ev);
    if (ev->mimeData()->hasFormat("text/plain")) {
        ev->setDropAction(Qt::CopyAction);
        ev->accept();
    }
}

void WatchWindow::dragMoveEvent(QDragMoveEvent *ev)
{
    //QTreeView::dragMoveEvent(ev);
    if (ev->mimeData()->hasFormat("text/plain")) {
        ev->setDropAction(Qt::CopyAction);
        ev->accept();
    }
}

void WatchWindow::dropEvent(QDropEvent *ev)
{
    if (ev->mimeData()->hasFormat("text/plain")) {
        theDebuggerAction(WatchExpression)->trigger(ev->mimeData()->text());
        //ev->acceptProposedAction();
        ev->setDropAction(Qt::CopyAction);
        ev->accept();
    }
    //QTreeView::dropEvent(ev);
}

207
208
209
210
211
212
213
214
215
216
217
218
static inline void addWatchPoint(DebuggerManager *manager, quint64 address)
{
    const QByteArray addressBA = QByteArray("0x") + QByteArray::number(address, 16);
    if (manager->breakHandler()->findWatchPointByAddress(addressBA))
        return;
    BreakpointData *data = new BreakpointData;
    data->type = BreakpointData::WatchpointType;
    data->address = addressBA;
    manager->breakHandler()->appendBreakpoint(data);
    manager->attemptBreakpointSynchronization();
}

con's avatar
con committed
219
220
void WatchWindow::contextMenuEvent(QContextMenuEvent *ev)
{
221
222
223
224
    const QModelIndex idx = indexAt(ev->pos());
    const QModelIndex mi0 = idx.sibling(idx.row(), 0);
    const QModelIndex mi1 = idx.sibling(idx.row(), 1);
    const QModelIndex mi2 = idx.sibling(idx.row(), 2);
225
226
    const quint64 address = model()->data(mi0, AddressRole).toULongLong();
    const quint64 pointerValue = model()->data(mi0, PointerValue).toULongLong();
227
228
    const QString exp = model()->data(mi0, ExpressionRole).toString();
    const QString type = model()->data(mi2).toString();
229

230
    const QStringList alternativeFormats =
231
        model()->data(mi0, TypeFormatListRole).toStringList();
232
    const int typeFormat =
233
        model()->data(mi0, TypeFormatRole).toInt();
234
235
236
237
    const int individualFormat =
        model()->data(mi0, IndividualFormatRole).toInt();
    const int effectiveIndividualFormat =
        individualFormat == -1 ? typeFormat : individualFormat;
238

239
    QMenu typeFormatMenu;
240
    QList<QAction *> typeFormatActions;
241
    QAction *clearTypeFormatAction = 0;
242
    if (idx.isValid()) {
243
        typeFormatMenu.setTitle(
244
            tr("Change Format for Type \"%1\"").arg(type));
245
246
        if (alternativeFormats.isEmpty()) {
            typeFormatMenu.setEnabled(false);
247
        } else {
248
249
250
251
252
            clearTypeFormatAction = typeFormatMenu.addAction(tr("Clear"));
            clearTypeFormatAction->setEnabled(typeFormat != -1);
            clearTypeFormatAction->setCheckable(true);
            clearTypeFormatAction->setChecked(typeFormat == -1);
            typeFormatMenu.addSeparator();
253
254
255
256
257
258
259
260
            for (int i = 0; i != alternativeFormats.size(); ++i) {
                const QString format = alternativeFormats.at(i);
                QAction *act = new QAction(format, &typeFormatMenu);
                act->setCheckable(true);
                if (i == typeFormat)
                    act->setChecked(true);
                typeFormatMenu.addAction(act);
                typeFormatActions.append(act);
hjk's avatar
hjk committed
261
262
263
264
265
266
267
268
269
270
            }
        }
    } else {
        typeFormatMenu.setTitle(tr("Change Format for Type"));
        typeFormatMenu.setEnabled(false);
    }

    QMenu individualFormatMenu;
    QList<QAction *> individualFormatActions;
    QAction *clearIndividualFormatAction = 0;
271
    if (idx.isValid() && address) {
hjk's avatar
hjk committed
272
        individualFormatMenu.setTitle(
273
            tr("Change Format for Object at 0x%1").arg(address, 0, 16));
hjk's avatar
hjk committed
274
275
276
277
278
        if (alternativeFormats.isEmpty()) {
            individualFormatMenu.setEnabled(false);
        } else {
            clearIndividualFormatAction = individualFormatMenu.addAction(tr("Clear"));
            clearIndividualFormatAction->setEnabled(individualFormat != -1);
279
280
            clearIndividualFormatAction->setCheckable(true);
            clearIndividualFormatAction->setChecked(individualFormat == -1);
hjk's avatar
hjk committed
281
282
283
284
            individualFormatMenu.addSeparator();
            for (int i = 0; i != alternativeFormats.size(); ++i) {
                const QString format = alternativeFormats.at(i);
                QAction *act = new QAction(format, &individualFormatMenu);
285
286
287
288
289
290
                act->setCheckable(true);
                if (i == effectiveIndividualFormat)
                    act->setChecked(true);
                individualFormatMenu.addAction(act);
                individualFormatActions.append(act);
            }
291
        }
292
    } else {
293
        individualFormatMenu.setTitle(tr("Change Format for Object"));
294
        individualFormatMenu.setEnabled(false);
295
296
    }

con's avatar
con committed
297
    QMenu menu;
298

299
300
301
302
    const bool actionsEnabled = m_manager->debuggerActionsEnabled();
    const unsigned engineCapabilities = m_manager->debuggerCapabilities();
    const bool canHandleWatches = actionsEnabled && (engineCapabilities & AddWatcherCapability);

303
    QAction *actInsertNewWatchItem = menu.addAction(tr("Insert New Watch Item"));
304
    actInsertNewWatchItem->setEnabled(canHandleWatches);
305
    QAction *actSelectWidgetToWatch = menu.addAction(tr("Select Widget to Watch"));
306
    actSelectWidgetToWatch->setEnabled(canHandleWatches);
307

308
309
310
    QAction *actOpenMemoryEditAtVariableAddress = 0;
    QAction *actOpenMemoryEditAtPointerValue = 0;
    QAction *actOpenMemoryEditor =
311
        new QAction(tr("Open Memory Editor..."), &menu);
312
    const bool canShowMemory = engineCapabilities & ShowMemoryCapability;
313
314
315
316
317
318
319
320
321
322
323
324
    actOpenMemoryEditor->setEnabled(actionsEnabled && canShowMemory);

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

    const QString openMemoryEditorFormat = tr("Open Memory Editor at 0x%1");
    if (canShowMemory && address)
        actOpenMemoryEditAtVariableAddress =
            new QAction(openMemoryEditorFormat.arg(address, 0, 16), &menu);
    if (createPointerActions)
        actOpenMemoryEditAtPointerValue =
                new QAction(openMemoryEditorFormat.arg(pointerValue, 0, 16), &menu);
325
    menu.addSeparator();
326

327
328
    QAction *actSetWatchPointAtVariableAddress = 0;
    QAction *actSetWatchPointAtPointerValue= 0;
329
    const bool canSetWatchpoint = engineCapabilities & WatchpointCapability;
330
331
332
333
    if (canSetWatchpoint && address) {
        const QString watchPointFormat = tr("Break on Changing Contents of 0x%1");
        actSetWatchPointAtVariableAddress =
            new QAction(watchPointFormat.arg(address, 0, 16), &menu);
334
335
336
        actSetWatchPointAtVariableAddress->setCheckable(true);
        actSetWatchPointAtVariableAddress->setChecked(m_manager->breakHandler()->watchPointAt(address));
        if (createPointerActions) {
337
338
            actSetWatchPointAtPointerValue =
                    new QAction(watchPointFormat.arg(pointerValue, 0, 16), &menu);
339
340
341
            actSetWatchPointAtPointerValue->setCheckable(true);
            actSetWatchPointAtPointerValue->setChecked(m_manager->breakHandler()->watchPointAt(pointerValue));
        }
342
    } else {
343
        actSetWatchPointAtVariableAddress =
344
            new QAction(tr("Break on Changing Contents"), &menu);
345
        actSetWatchPointAtVariableAddress->setEnabled(false);
346
347
    }

348
    QAction *actWatchOrRemove;
349
    if (m_type == LocalsType) {
350
        actWatchOrRemove = theDebuggerAction(WatchExpression)->updatedAction(exp);
351
        actWatchOrRemove->setEnabled(canHandleWatches);
352
353
354
355
356
357
    } else {
        actWatchOrRemove = theDebuggerAction(RemoveWatchExpression)->updatedAction(exp);
        // Also for the case where the user cleared the expression.
        actWatchOrRemove->setEnabled(true);
    }
    menu.addAction(actWatchOrRemove);
358

359
360
    menu.addAction(actInsertNewWatchItem);
    menu.addAction(actSelectWidgetToWatch);
361
362
    menu.addMenu(&typeFormatMenu);
    menu.addMenu(&individualFormatMenu);
363
364
365
366
367
368
369
370
    if (actOpenMemoryEditAtVariableAddress)
        menu.addAction(actOpenMemoryEditAtVariableAddress);
    if (actOpenMemoryEditAtPointerValue)
        menu.addAction(actOpenMemoryEditAtPointerValue);
    menu.addAction(actOpenMemoryEditor);
    menu.addAction(actSetWatchPointAtVariableAddress);
    if (actSetWatchPointAtPointerValue)
        menu.addAction(actSetWatchPointAtPointerValue);
371
    menu.addSeparator();
372

373
374
    menu.addAction(theDebuggerAction(RecheckDebuggingHelpers));
    menu.addAction(theDebuggerAction(UseDebuggingHelpers));
375
376
377
378
    QAction *actClearCodeModelSnapshot
        = new QAction(tr("Refresh Code Model Snapshot"), &menu);
    actClearCodeModelSnapshot->setEnabled(actionsEnabled
        && theDebuggerAction(UseCodeModel)->isChecked());
379
    menu.addAction(actClearCodeModelSnapshot);
380
    menu.addSeparator();
381
    menu.addAction(theDebuggerAction(UseToolTipsInLocalsView));
382

383
    menu.addAction(theDebuggerAction(AutoDerefPointers));
384
385
    menu.addAction(theDebuggerAction(ShowStdNamespace));
    menu.addAction(theDebuggerAction(ShowQtNamespace));
386

387
    QAction *actAdjustColumnWidths =
388
        menu.addAction(tr("Adjust Column Widths to Contents"));
389
    QAction *actAlwaysAdjustColumnWidth =
390
        menu.addAction(tr("Always Adjust Column Widths to Contents"));
391
392
393
    actAlwaysAdjustColumnWidth->setCheckable(true);
    actAlwaysAdjustColumnWidth->setChecked(m_alwaysResizeColumnsToContents);

394
    menu.addSeparator();
395
    menu.addAction(theDebuggerAction(SettingsDialog));
con's avatar
con committed
396
397

    QAction *act = menu.exec(ev->globalPos());
398
399
    if (act == 0)
        return;
con's avatar
con committed
400

401
    if (act == actAdjustColumnWidths) {
con's avatar
con committed
402
        resizeColumnsToContents();
403
    } else if (act == actAlwaysAdjustColumnWidth) {
con's avatar
con committed
404
        setAlwaysResizeColumnsToContents(!m_alwaysResizeColumnsToContents);
405
    } else if (act == actInsertNewWatchItem) {
406
407
        theDebuggerAction(WatchExpression)
            ->trigger(WatchHandler::watcherEditPlaceHolder());
408
    } else if (act == actOpenMemoryEditAtVariableAddress) {
409
        (void) new MemoryViewAgent(m_manager, address);
410
411
412
    } else if (act == actOpenMemoryEditAtPointerValue) {
        (void) new MemoryViewAgent(m_manager, pointerValue);
    } else if (act == actOpenMemoryEditor) {
413
414
415
416
        AddressDialog dialog;
        if (dialog.exec() == QDialog::Accepted) {
            (void) new MemoryViewAgent(m_manager, dialog.address());
        }
417
418
419
420
    } else if (act == actSetWatchPointAtVariableAddress) {
        addWatchPoint(m_manager, address);
    } else if (act == actSetWatchPointAtPointerValue) {
        addWatchPoint(m_manager, pointerValue);
421
    } else if (act == actSelectWidgetToWatch) {
422
423
        grabMouse(Qt::CrossCursor);
        m_grabbing = true;
424
425
    } else if (act == actClearCodeModelSnapshot) {
        m_manager->clearCppCodeModelSnapshot();
426
    } else if (act == clearTypeFormatAction) {
427
        model()->setData(mi1, -1, TypeFormatRole);
428
    } else if (act == clearIndividualFormatAction) {
429
        model()->setData(mi1, -1, IndividualFormatRole);
430
    } else {
hjk's avatar
hjk committed
431
        for (int i = 0; i != typeFormatActions.size(); ++i) {
432
433
            if (act == typeFormatActions.at(i))
                model()->setData(mi1, i, TypeFormatRole);
hjk's avatar
hjk committed
434
435
436
        }
        for (int i = 0; i != individualFormatActions.size(); ++i) {
            if (act == individualFormatActions.at(i))
437
438
                model()->setData(mi1, i, IndividualFormatRole);
        }
439
    }
con's avatar
con committed
440
441
442
443
444
445
446
447
448
449
450
451
452
}

void WatchWindow::resizeColumnsToContents()
{
    resizeColumnToContents(0);
    resizeColumnToContents(1);
}

void WatchWindow::setAlwaysResizeColumnsToContents(bool on)
{
    if (!header())
        return;
    m_alwaysResizeColumnsToContents = on;
453
    QHeaderView::ResizeMode mode = on
con's avatar
con committed
454
455
456
457
458
        ? QHeaderView::ResizeToContents : QHeaderView::Interactive;
    header()->setResizeMode(0, mode);
    header()->setResizeMode(1, mode);
}

459
460
461
462
463
464
465
466
467
468
469
bool WatchWindow::event(QEvent *ev)
{
    if (m_grabbing && ev->type() == QEvent::MouseButtonPress) {
        QMouseEvent *mev = static_cast<QMouseEvent *>(ev);
        m_grabbing = false;
        releaseMouse();
        theDebuggerAction(WatchPoint)->trigger(mapToGlobal(mev->pos()));
    }
    return QTreeView::event(ev);
}

con's avatar
con committed
470
471
void WatchWindow::editItem(const QModelIndex &idx)
{
472
    Q_UNUSED(idx) // FIXME
con's avatar
con committed
473
474
}

475
void WatchWindow::setModel(QAbstractItemModel *model)
con's avatar
con committed
476
{
477
    QTreeView::setModel(model);
con's avatar
con committed
478
479
480
481
482
483

    setRootIsDecorated(true);
    header()->setDefaultAlignment(Qt::AlignLeft);
    header()->setResizeMode(QHeaderView::ResizeToContents);
    if (m_type != LocalsType)
        header()->hide();
484

485
486
487
488
489
490
491
492
493
494
    connect(model, SIGNAL(layoutChanged()),
        this, SLOT(resetHelper()));
    connect(model, SIGNAL(enableUpdates(bool)),
        this, SLOT(setUpdatesEnabled(bool)));
}

void WatchWindow::setUpdatesEnabled(bool enable)
{
    //qDebug() << "ENABLING UPDATES: " << enable;
    QTreeView::setUpdatesEnabled(enable);
495
496
497
498
499
}

void WatchWindow::resetHelper()
{
    resetHelper(model()->index(0, 0));
con's avatar
con committed
500
501
}

502
void WatchWindow::resetHelper(const QModelIndex &idx)
con's avatar
con committed
503
{
504
    if (model()->data(idx, ExpandedRole).toBool()) {
505
        //qDebug() << "EXPANDING " << model()->data(idx, INameRole);
con's avatar
con committed
506
507
508
        expand(idx);
        for (int i = 0, n = model()->rowCount(idx); i != n; ++i) {
            QModelIndex idx1 = model()->index(i, 0, idx);
509
            resetHelper(idx1);
con's avatar
con committed
510
        }
511
512
513
    } else {
        //qDebug() << "COLLAPSING " << model()->data(idx, INameRole);
        collapse(idx);
con's avatar
con committed
514
515
    }
}