Files
qt-creator/src/plugins/debugger/watchwindow.cpp

634 lines
22 KiB
C++
Raw Normal View History

/**************************************************************************
2008-12-02 12:01:29 +01:00
**
** This file is part of Qt Creator
**
2011-01-11 16:28:15 +01:00
** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
2008-12-02 12:01:29 +01:00
**
** Contact: Nokia Corporation (qt-info@nokia.com)
2008-12-02 12:01:29 +01:00
**
2010-12-17 16:01:08 +01:00
** No Commercial Usage
**
2010-12-17 16:01:08 +01:00
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
**
** GNU Lesser General Public License Usage
**
** 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.
**
2010-12-17 16:01:08 +01:00
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
2008-12-02 12:01:29 +01:00
**
**************************************************************************/
2008-12-02 15:08:31 +01:00
2008-12-02 12:01:29 +01:00
#include "watchwindow.h"
#include "breakhandler.h"
#include "debuggeractions.h"
#include "debuggerconstants.h"
#include "debuggercore.h"
#include "debuggerdialogs.h"
#include "debuggerengine.h"
#include "watchdelegatewidgets.h"
#include "watchhandler.h"
#include "debuggertooltipmanager.h"
#include <utils/qtcassert.h>
#include <utils/savedaction.h>
2008-12-02 12:01:29 +01:00
#include <QtCore/QDebug>
#include <QtCore/QMetaObject>
#include <QtCore/QMetaProperty>
#include <QtCore/QVariant>
2008-12-02 12:01:29 +01:00
#include <QtGui/QApplication>
#include <QtGui/QClipboard>
2008-12-02 12:01:29 +01:00
#include <QtGui/QContextMenuEvent>
#include <QtGui/QHeaderView>
#include <QtGui/QItemDelegate>
2008-12-02 12:01:29 +01:00
#include <QtGui/QMenu>
#include <QtGui/QPainter>
2008-12-02 12:01:29 +01:00
#include <QtGui/QResizeEvent>
/////////////////////////////////////////////////////////////////////
//
// WatchDelegate
//
/////////////////////////////////////////////////////////////////////
namespace Debugger {
namespace Internal {
static DebuggerEngine *currentEngine()
{
return debuggerCore()->currentEngine();
}
class WatchDelegate : public QItemDelegate
{
public:
explicit WatchDelegate(WatchWindow *parent)
: QItemDelegate(parent), m_watchWindow(parent)
{}
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &,
const QModelIndex &index) const
{
// Value column: Custom editor. Apply integer-specific settings.
if (index.column() == 1) {
const QVariant::Type type =
static_cast<QVariant::Type>(index.data(LocalsEditTypeRole).toInt());
switch (type) {
case QVariant::Bool:
return new BooleanComboBox(parent);
default:
break;
}
WatchLineEdit *edit = WatchLineEdit::create(type, parent);
edit->setFrame(false);
IntegerWatchLineEdit *intEdit
= qobject_cast<IntegerWatchLineEdit *>(edit);
if (intEdit)
intEdit->setBase(index.data(LocalsIntegerBaseRole).toInt());
return edit;
}
// Standard line edits for the rest.
QLineEdit *lineEdit = new QLineEdit(parent);
lineEdit->setFrame(false);
return lineEdit;
}
void setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const
{
// 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);
return;
}
const QMetaProperty userProperty = editor->metaObject()->userProperty();
QTC_ASSERT(userProperty.isValid(), return);
const QString value = editor->property(userProperty.name()).toString();
const QString exp = index.data(LocalsExpressionRole).toString();
if (exp == value)
return;
m_watchWindow->removeWatchExpression(exp);
m_watchWindow->watchExpression(value);
}
void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option,
const QModelIndex &) const
{
editor->setGeometry(option.rect);
}
private:
WatchWindow *m_watchWindow;
};
2008-12-02 12:01:29 +01:00
/////////////////////////////////////////////////////////////////////
//
// WatchWindow
//
/////////////////////////////////////////////////////////////////////
WatchWindow::WatchWindow(Type type, QWidget *parent)
: QTreeView(parent),
m_alwaysResizeColumnsToContents(true),
m_type(type)
2008-12-02 12:01:29 +01:00
{
m_grabbing = false;
QAction *act = debuggerCore()->action(UseAlternatingRowColors);
setFrameStyle(QFrame::NoFrame);
setAttribute(Qt::WA_MacShowFocusRect, false);
2008-12-02 12:01:29 +01:00
setWindowTitle(tr("Locals and Watchers"));
setAlternatingRowColors(act->isChecked());
2008-12-02 12:01:29 +01:00
setIndentation(indentation() * 9/10);
setUniformRowHeights(true);
setItemDelegate(new WatchDelegate(this));
setDragEnabled(true);
setAcceptDrops(true);
setDropIndicatorShown(true);
2008-12-02 12:01:29 +01:00
connect(act, SIGNAL(toggled(bool)),
this, SLOT(setAlternatingRowColorsHelper(bool)));
connect(this, SIGNAL(expanded(QModelIndex)),
this, SLOT(expandNode(QModelIndex)));
connect(this, SIGNAL(collapsed(QModelIndex)),
this, SLOT(collapseNode(QModelIndex)));
}
void WatchWindow::expandNode(const QModelIndex &idx)
{
setModelData(LocalsExpandedRole, true, idx);
}
void WatchWindow::collapseNode(const QModelIndex &idx)
{
setModelData(LocalsExpandedRole, false, idx);
2008-12-02 12:01:29 +01:00
}
void WatchWindow::keyPressEvent(QKeyEvent *ev)
{
if (ev->key() == Qt::Key_Delete && m_type == WatchersType) {
QModelIndex idx = currentIndex();
QModelIndex idx1 = idx.sibling(idx.row(), 0);
QString exp = idx1.data().toString();
removeWatchExpression(exp);
} else if (ev->key() == Qt::Key_Return
&& ev->modifiers() == Qt::ControlModifier
&& m_type == LocalsType) {
QModelIndex idx = currentIndex();
QModelIndex idx1 = idx.sibling(idx.row(), 0);
QString exp = model()->data(idx1).toString();
watchExpression(exp);
}
QTreeView::keyPressEvent(ev);
}
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")) {
watchExpression(ev->mimeData()->text());
//ev->acceptProposedAction();
ev->setDropAction(Qt::CopyAction);
ev->accept();
}
//QTreeView::dropEvent(ev);
}
void WatchWindow::mouseDoubleClickEvent(QMouseEvent *ev)
{
const QModelIndex idx = indexAt(ev->pos());
if (!idx.isValid()) {
// The "<Edit>" case.
watchExpression(QString());
return;
}
QTreeView::mouseDoubleClickEvent(ev);
}
// Text for add watch action with truncated expression
static inline QString addWatchActionText(QString exp)
{
if (exp.isEmpty())
return WatchWindow::tr("Watch Expression");
if (exp.size() > 30) {
exp.truncate(30);
exp.append(QLatin1String("..."));
}
return WatchWindow::tr("Watch Expression \"%1\"").arg(exp);
}
// Text for add watch action with truncated expression
static inline QString removeWatchActionText(QString exp)
{
if (exp.isEmpty())
return WatchWindow::tr("Remove Watch Expression");
if (exp.size() > 30) {
exp.truncate(30);
exp.append(QLatin1String("..."));
}
return WatchWindow::tr("Remove Watch Expression \"%1\"").arg(exp);
}
2008-12-02 12:01:29 +01:00
void WatchWindow::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 QModelIndex mi2 = idx.sibling(idx.row(), 2);
const quint64 address = mi0.data(LocalsAddressRole).toULongLong();
const quint64 pointerValue = mi0.data(LocalsPointerValueRole).toULongLong();
const QString exp = mi0.data(LocalsExpressionRole).toString();
const QString type = mi2.data().toString();
const QStringList alternativeFormats =
mi0.data(LocalsTypeFormatListRole).toStringList();
const int typeFormat =
mi0.data(LocalsTypeFormatRole).toInt();
const int individualFormat =
mi0.data(LocalsIndividualFormatRole).toInt();
const int effectiveIndividualFormat =
individualFormat == -1 ? typeFormat : individualFormat;
QMenu formatMenu;
QList<QAction *> typeFormatActions;
QList<QAction *> individualFormatActions;
QAction *clearTypeFormatAction = 0;
QAction *clearIndividualFormatAction = 0;
formatMenu.setTitle(tr("Change Display Format..."));
if (idx.isValid() && !alternativeFormats.isEmpty()) {
QAction *dummy = formatMenu.addAction(
tr("Change Display for Type \"%1\"").arg(type));
dummy->setEnabled(false);
formatMenu.addSeparator();
clearTypeFormatAction = formatMenu.addAction(tr("Automatic"));
//clearTypeFormatAction->setEnabled(typeFormat != -1);
//clearTypeFormatAction->setEnabled(individualFormat != -1);
clearTypeFormatAction->setCheckable(true);
clearTypeFormatAction->setChecked(typeFormat == -1);
formatMenu.addSeparator();
for (int i = 0; i != alternativeFormats.size(); ++i) {
const QString format = alternativeFormats.at(i);
QAction *act = new QAction(format, &formatMenu);
act->setCheckable(true);
//act->setEnabled(individualFormat != -1);
if (i == typeFormat)
act->setChecked(true);
formatMenu.addAction(act);
typeFormatActions.append(act);
2010-03-17 13:03:34 +01:00
}
formatMenu.addSeparator();
dummy = formatMenu.addAction(
tr("Change Display for Object Named \"%1\"").arg(mi0.data().toString()));
dummy->setEnabled(false);
formatMenu.addSeparator();
clearIndividualFormatAction
= formatMenu.addAction(tr("Use Display Format Based on Type"));
//clearIndividualFormatAction->setEnabled(individualFormat != -1);
clearIndividualFormatAction->setCheckable(true);
clearIndividualFormatAction->setChecked(effectiveIndividualFormat == -1);
formatMenu.addSeparator();
for (int i = 0; i != alternativeFormats.size(); ++i) {
const QString format = alternativeFormats.at(i);
QAction *act = new QAction(format, &formatMenu);
act->setCheckable(true);
if (i == effectiveIndividualFormat)
act->setChecked(true);
formatMenu.addAction(act);
individualFormatActions.append(act);
}
} else {
formatMenu.setEnabled(false);
}
2010-11-25 18:49:26 +01:00
const bool actionsEnabled = engine->debuggerActionsEnabled();
const unsigned engineCapabilities = engine->debuggerCapabilities();
const bool canHandleWatches =
actionsEnabled && (engineCapabilities & AddWatcherCapability);
2010-11-25 18:49:26 +01:00
const DebuggerState state = engine->state();
QMenu menu;
QAction *actInsertNewWatchItem = menu.addAction(tr("Insert New Watch Item"));
actInsertNewWatchItem->setEnabled(canHandleWatches);
QAction *actSelectWidgetToWatch = menu.addAction(tr("Select Widget to Watch"));
actSelectWidgetToWatch->setEnabled(canHandleWatches);
// Offer to open address pointed to or variable address.
const bool createPointerActions = pointerValue && pointerValue != address;
menu.addSeparator();
QAction *actSetWatchpointAtVariableAddress = 0;
QAction *actSetWatchpointAtPointerValue = 0;
const bool canSetWatchpoint = engineCapabilities & WatchpointCapability;
if (canSetWatchpoint && address) {
actSetWatchpointAtVariableAddress =
new QAction(tr("Add Watchpoint at Object's Address (0x%1)")
.arg(address, 0, 16), &menu);
actSetWatchpointAtVariableAddress->
setChecked(mi0.data(LocalsIsWatchpointAtAddressRole).toBool());
if (createPointerActions) {
actSetWatchpointAtPointerValue =
new QAction(tr("Add Watchpoint at Referenced Address (0x%1)")
.arg(pointerValue, 0, 16), &menu);
actSetWatchpointAtPointerValue->setCheckable(true);
actSetWatchpointAtPointerValue->
setChecked(mi0.data(LocalsIsWatchpointAtPointerValueRole).toBool());
}
} else {
actSetWatchpointAtVariableAddress =
2010-11-26 09:58:34 +01:00
new QAction(tr("Add Watchpoint"), &menu);
actSetWatchpointAtVariableAddress->setEnabled(false);
}
actSetWatchpointAtVariableAddress->setToolTip(
tr("Setting a watchpoint on an address will cause the program "
2010-11-04 14:03:44 +01:00
"to stop when the data at the address it modified."));
QAction *actWatchExpression = new QAction(addWatchActionText(exp), &menu);
actWatchExpression->setEnabled(canHandleWatches && !exp.isEmpty());
// Can remove watch if engine can handle it or session engine.
QAction *actRemoveWatchExpression = new QAction(removeWatchActionText(exp), &menu);
actRemoveWatchExpression->setEnabled(
(canHandleWatches || state == DebuggerNotReady) && !exp.isEmpty());
QAction *actRemoveWatches = new QAction(tr("Remove All Watch Items"), &menu);
actRemoveWatches->setEnabled(!WatchHandler::watcherNames().isEmpty());
if (m_type == LocalsType)
menu.addAction(actWatchExpression);
else {
menu.addAction(actRemoveWatchExpression);
menu.addAction(actRemoveWatches);
}
QMenu memoryMenu;
memoryMenu.setTitle(tr("Open Memory Editor..."));
QAction *actOpenMemoryEditAtVariableAddress = new QAction(&memoryMenu);
QAction *actOpenMemoryEditAtPointerValue = new QAction(&memoryMenu);
QAction *actOpenMemoryEditor = new QAction(&memoryMenu);
if (engineCapabilities & ShowMemoryCapability) {
actOpenMemoryEditor->setText(tr("Open Memory Editor..."));
if (address) {
actOpenMemoryEditAtVariableAddress->setText(
tr("Open Memory Editor at Object's Address (0x%1)")
.arg(address, 0, 16));
} else {
actOpenMemoryEditAtVariableAddress->setText(
tr("Open Memory Editor at Object's Address"));
actOpenMemoryEditAtVariableAddress->setEnabled(false);
}
if (createPointerActions) {
actOpenMemoryEditAtPointerValue->setText(
tr("Open Memory Editor at Referenced Address (0x%1)")
.arg(pointerValue, 0, 16));
} else {
actOpenMemoryEditAtPointerValue->setText(
tr("Open Memory Editor at Referenced Address"));
actOpenMemoryEditAtPointerValue->setEnabled(false);
}
memoryMenu.addAction(actOpenMemoryEditAtVariableAddress);
memoryMenu.addAction(actOpenMemoryEditAtPointerValue);
memoryMenu.addAction(actOpenMemoryEditor);
} else {
memoryMenu.setEnabled(false);
}
QAction *actCopy = new QAction(tr("Copy Contents to Clipboard"), &menu);
menu.addAction(actInsertNewWatchItem);
menu.addAction(actSelectWidgetToWatch);
menu.addMenu(&formatMenu);
menu.addMenu(&memoryMenu);
menu.addAction(actSetWatchpointAtVariableAddress);
if (actSetWatchpointAtPointerValue)
menu.addAction(actSetWatchpointAtPointerValue);
menu.addAction(actCopy );
menu.addSeparator();
menu.addAction(debuggerCore()->action(UseDebuggingHelpers));
menu.addAction(debuggerCore()->action(UseToolTipsInLocalsView));
menu.addAction(debuggerCore()->action(AutoDerefPointers));
menu.addAction(debuggerCore()->action(ShowStdNamespace));
menu.addAction(debuggerCore()->action(ShowQtNamespace));
menu.addAction(debuggerCore()->action(SortStructMembers));
QAction *actAdjustColumnWidths =
menu.addAction(tr("Adjust Column Widths to Contents"));
QAction *actAlwaysAdjustColumnWidth =
menu.addAction(tr("Always Adjust Column Widths to Contents"));
actAlwaysAdjustColumnWidth->setCheckable(true);
actAlwaysAdjustColumnWidth->setChecked(m_alwaysResizeColumnsToContents);
menu.addSeparator();
QAction *actClearCodeModelSnapshot
= new QAction(tr("Refresh Code Model Snapshot"), &menu);
actClearCodeModelSnapshot->setEnabled(actionsEnabled
&& debuggerCore()->action(UseCodeModel)->isChecked());
menu.addAction(actClearCodeModelSnapshot);
QAction *actShowInEditor
= new QAction(tr("Show View Contents in Editor"), &menu);
actShowInEditor->setEnabled(actionsEnabled);
menu.addAction(actShowInEditor);
menu.addAction(debuggerCore()->action(SettingsDialog));
2008-12-02 12:01:29 +01:00
QAction *act = menu.exec(ev->globalPos());
if (act == 0)
return;
2008-12-02 12:01:29 +01:00
if (act == actAdjustColumnWidths) {
2008-12-02 12:01:29 +01:00
resizeColumnsToContents();
} else if (act == actAlwaysAdjustColumnWidth) {
2008-12-02 12:01:29 +01:00
setAlwaysResizeColumnsToContents(!m_alwaysResizeColumnsToContents);
} else if (act == actInsertNewWatchItem) {
watchExpression(QString());
} else if (act == actOpenMemoryEditAtVariableAddress) {
currentEngine()->openMemoryView(address);
} else if (act == actOpenMemoryEditAtPointerValue) {
currentEngine()->openMemoryView(pointerValue);
} else if (act == actOpenMemoryEditor) {
AddressDialog dialog;
if (dialog.exec() == QDialog::Accepted)
currentEngine()->openMemoryView(dialog.address());
} else if (act == actSetWatchpointAtVariableAddress) {
setWatchpoint(address);
} else if (act == actSetWatchpointAtPointerValue) {
setWatchpoint(pointerValue);
} else if (act == actSelectWidgetToWatch) {
grabMouse(Qt::CrossCursor);
m_grabbing = true;
} else if (act == actWatchExpression) {
watchExpression(exp);
} else if (act == actRemoveWatchExpression) {
removeWatchExpression(exp);
} else if (act == actCopy ) {
const QString clipboardText = DebuggerTreeViewToolTipWidget::treeModelClipboardContents(model());
QClipboard *clipboard = QApplication::clipboard();
#ifdef Q_WS_X11
clipboard->setText(clipboardText, QClipboard::Selection);
#endif
clipboard->setText(clipboardText, QClipboard::Clipboard);
} else if (act == actRemoveWatches) {
currentEngine()->watchHandler()->clearWatches();
} else if (act == actClearCodeModelSnapshot) {
debuggerCore()->clearCppCodeModelSnapshot();
} else if (act == clearTypeFormatAction) {
setModelData(LocalsTypeFormatRole, -1, mi1);
} else if (act == clearIndividualFormatAction) {
setModelData(LocalsIndividualFormatRole, -1, mi1);
} else if (act == actShowInEditor) {
QString contents = handler->editorContents();
debuggerCore()->openTextEditor(tr("Locals & Watchers"), contents);
} else {
2010-03-17 13:03:34 +01:00
for (int i = 0; i != typeFormatActions.size(); ++i) {
if (act == typeFormatActions.at(i))
setModelData(LocalsTypeFormatRole, i, mi1);
2010-03-17 13:03:34 +01:00
}
for (int i = 0; i != individualFormatActions.size(); ++i) {
if (act == individualFormatActions.at(i))
setModelData(LocalsIndividualFormatRole, i, mi1);
}
}
2008-12-02 12:01:29 +01:00
}
void WatchWindow::resizeColumnsToContents()
{
resizeColumnToContents(0);
resizeColumnToContents(1);
}
void WatchWindow::setAlwaysResizeColumnsToContents(bool on)
{
if (!header())
return;
m_alwaysResizeColumnsToContents = on;
QHeaderView::ResizeMode mode = on
2008-12-02 12:01:29 +01:00
? QHeaderView::ResizeToContents : QHeaderView::Interactive;
header()->setResizeMode(0, mode);
header()->setResizeMode(1, mode);
}
bool WatchWindow::event(QEvent *ev)
{
if (m_grabbing && ev->type() == QEvent::MouseButtonPress) {
QMouseEvent *mev = static_cast<QMouseEvent *>(ev);
m_grabbing = false;
releaseMouse();
currentEngine()->watchPoint(mapToGlobal(mev->pos()));
}
return QTreeView::event(ev);
}
2008-12-02 12:01:29 +01:00
void WatchWindow::editItem(const QModelIndex &idx)
{
Q_UNUSED(idx) // FIXME
2008-12-02 12:01:29 +01:00
}
void WatchWindow::setModel(QAbstractItemModel *model)
2008-12-02 12:01:29 +01:00
{
QTreeView::setModel(model);
2008-12-02 12:01:29 +01:00
setRootIsDecorated(true);
header()->setDefaultAlignment(Qt::AlignLeft);
header()->setResizeMode(QHeaderView::ResizeToContents);
if (m_type != LocalsType)
header()->hide();
connect(model, SIGNAL(layoutChanged()), SLOT(resetHelper()));
connect(model, SIGNAL(enableUpdates(bool)), SLOT(setUpdatesEnabled(bool)));
}
void WatchWindow::setUpdatesEnabled(bool enable)
{
//qDebug() << "ENABLING UPDATES: " << enable;
QTreeView::setUpdatesEnabled(enable);
}
void WatchWindow::resetHelper()
{
bool old = updatesEnabled();
setUpdatesEnabled(false);
resetHelper(model()->index(0, 0));
setUpdatesEnabled(old);
2008-12-02 12:01:29 +01:00
}
void WatchWindow::resetHelper(const QModelIndex &idx)
2008-12-02 12:01:29 +01:00
{
if (idx.data(LocalsExpandedRole).toBool()) {
//qDebug() << "EXPANDING " << model()->data(idx, INameRole);
if (!isExpanded(idx)) {
expand(idx);
for (int i = 0, n = model()->rowCount(idx); i != n; ++i) {
QModelIndex idx1 = model()->index(i, 0, idx);
resetHelper(idx1);
}
2008-12-02 12:01:29 +01:00
}
} else {
//qDebug() << "COLLAPSING " << model()->data(idx, INameRole);
if (isExpanded(idx))
collapse(idx);
2008-12-02 12:01:29 +01:00
}
}
void WatchWindow::watchExpression(const QString &exp)
{
currentEngine()->watchHandler()->watchExpression(exp);
}
void WatchWindow::removeWatchExpression(const QString &exp)
{
currentEngine()->watchHandler()->removeWatchExpression(exp);
}
void WatchWindow::setModelData
(int role, const QVariant &value, const QModelIndex &index)
{
QTC_ASSERT(model(), return);
model()->setData(index, value, role);
}
void WatchWindow::setWatchpoint(quint64 address)
{
breakHandler()->setWatchpointByAddress(address);
}
} // namespace Internal
} // namespace Debugger