You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2003 lines
58 KiB
2003 lines
58 KiB
15 years ago
|
// **************************************************************************
|
||
|
// begin : Sun Aug 8 1999
|
||
|
// copyright : (C) 1999 by John Birch
|
||
|
// email : jbb@kdevelop.org
|
||
|
// **************************************************************************
|
||
|
|
||
|
// **************************************************************************
|
||
|
// * *
|
||
|
// * This program is free software; you can redistribute it and/or modify *
|
||
|
// * it under the terms of the GNU General Public License as published by *
|
||
|
// * the Free Software Foundation; either version 2 of the License, or *
|
||
|
// * (at your option) any later version. *
|
||
|
// * *
|
||
|
// **************************************************************************
|
||
|
|
||
|
#include "variablewidget.h"
|
||
|
#include "gdbparser.h"
|
||
|
#include "gdbcommand.h"
|
||
|
#include "gdbbreakpointwidget.h"
|
||
|
|
||
|
#include <kdebug.h>
|
||
|
#include <kpopupmenu.h>
|
||
|
#include <klineedit.h>
|
||
|
#include <kdeversion.h>
|
||
|
#include <kiconloader.h>
|
||
|
|
||
|
#include <qheader.h>
|
||
|
#include <qlabel.h>
|
||
|
#include <qlayout.h>
|
||
|
#include <qhbox.h>
|
||
|
#include <qpainter.h>
|
||
|
#include <qpushbutton.h>
|
||
|
#include <qregexp.h>
|
||
|
#include <qcursor.h>
|
||
|
#include <qwhatsthis.h>
|
||
|
#include <klocale.h>
|
||
|
|
||
|
#include <qpoint.h>
|
||
|
#include <qclipboard.h>
|
||
|
#include <kapplication.h>
|
||
|
#include <kmessagebox.h>
|
||
|
|
||
|
#include <cctype>
|
||
|
#include <set>
|
||
|
#include <typeinfo>
|
||
|
#include <cctype>
|
||
|
|
||
|
/** The variables widget is passive, and is invoked by the rest of the
|
||
|
code via two main slots:
|
||
|
- slotDbgStatus
|
||
|
- slotCurrentFrame
|
||
|
|
||
|
The first is received the program status changes and the second is
|
||
|
recieved after current frame in the debugger can possibly changes.
|
||
|
|
||
|
The widget has a list item for each frame/thread combination, with
|
||
|
variables as children. However, at each moment only one item is shown.
|
||
|
When handling the slotCurrentFrame, we check if variables for the
|
||
|
current frame are available. If yes, we simply show the corresponding item.
|
||
|
Otherwise, we fetch the new data from debugger.
|
||
|
|
||
|
Fetching the data is done by emitting the produceVariablesInfo signal.
|
||
|
In response, we get slotParametersReady and slotLocalsReady signal,
|
||
|
in that order.
|
||
|
|
||
|
The data is parsed and changed variables are highlighted. After that,
|
||
|
we 'trim' variable items that were not reported by gdb -- that is, gone
|
||
|
out of scope.
|
||
|
*/
|
||
|
|
||
|
// **************************************************************************
|
||
|
// **************************************************************************
|
||
|
// **************************************************************************
|
||
|
|
||
|
namespace GDBDebugger
|
||
|
{
|
||
|
|
||
|
VariableWidget::VariableWidget(GDBController* controller,
|
||
|
GDBBreakpointWidget* breakpointWidget,
|
||
|
QWidget *parent, const char *name)
|
||
|
: QWidget(parent, name)
|
||
|
{
|
||
|
setIcon(SmallIcon("math_brace"));
|
||
|
setCaption(i18n("Variable Tree"));
|
||
|
|
||
|
varTree_ = new VariableTree(this, controller, breakpointWidget);
|
||
|
|
||
|
watchVarEditor_ = new KHistoryCombo( this,
|
||
|
"var-to-watch editor");
|
||
|
|
||
|
QHBoxLayout* buttons = new QHBoxLayout();
|
||
|
|
||
|
buttons->addStretch();
|
||
|
|
||
|
QPushButton *evalButton = new QPushButton(i18n("&Evaluate"), this );
|
||
|
buttons->addWidget(evalButton);
|
||
|
|
||
|
QPushButton *addButton = new QPushButton(i18n("&Watch"), this );
|
||
|
buttons->addWidget(addButton);
|
||
|
|
||
|
QVBoxLayout *topLayout = new QVBoxLayout(this, 2);
|
||
|
topLayout->addWidget(varTree_, 10);
|
||
|
topLayout->addWidget(watchVarEditor_);
|
||
|
topLayout->addItem(buttons);
|
||
|
|
||
|
|
||
|
connect( addButton, SIGNAL(clicked()), SLOT(slotAddWatchVariable()) );
|
||
|
connect( evalButton, SIGNAL(clicked()), SLOT(slotEvaluateExpression()) );
|
||
|
|
||
|
connect( watchVarEditor_, SIGNAL(returnPressed()),
|
||
|
SLOT(slotEvaluateExpression()) );
|
||
|
|
||
|
connect(controller, SIGNAL(event(GDBController::event_t)),
|
||
|
varTree_, SLOT(slotEvent(GDBController::event_t)));
|
||
|
|
||
|
|
||
|
// Setup help items.
|
||
|
|
||
|
QWhatsThis::add(this, i18n(
|
||
|
"<b>Variable tree</b><p>"
|
||
|
"The variable tree allows you to see the values of local "
|
||
|
"variables and arbitrary expressions."
|
||
|
"<p>Local variables are displayed automatically and are updated "
|
||
|
"as you step through your program. "
|
||
|
"For each expression you enter, you can either evaluate it once, "
|
||
|
"or \"watch\" it (make it auto-updated). Expressions that are not "
|
||
|
"auto-updated can be updated manually from the context menu. "
|
||
|
"Expressions can be renamed to more descriptive names by clicking "
|
||
|
"on the name column."
|
||
|
"<p>To change the value of a variable or an expression, "
|
||
|
"click on the value."));
|
||
|
|
||
|
QWhatsThis::add(watchVarEditor_,
|
||
|
i18n("<b>Expression entry</b>"
|
||
|
"<p>Type in expression to evaluate."));
|
||
|
|
||
|
QWhatsThis::add(evalButton,
|
||
|
i18n("Evaluate the expression."));
|
||
|
|
||
|
QWhatsThis::add(addButton,
|
||
|
i18n("Evaluate the expression and "
|
||
|
"auto-update the value when stepping."));
|
||
|
}
|
||
|
|
||
|
void VariableWidget::slotAddWatchVariable()
|
||
|
{
|
||
|
// QString watchVar(watchVarEntry_->text());
|
||
|
QString watchVar(watchVarEditor_->currentText());
|
||
|
if (!watchVar.isEmpty())
|
||
|
{
|
||
|
slotAddWatchVariable(watchVar);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// **************************************************************************
|
||
|
|
||
|
void VariableWidget::slotAddWatchVariable(const QString &ident)
|
||
|
{
|
||
|
if (!ident.isEmpty())
|
||
|
{
|
||
|
watchVarEditor_->addToHistory(ident);
|
||
|
varTree_->slotAddWatchVariable(ident);
|
||
|
watchVarEditor_->clearEdit();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void VariableWidget::slotEvaluateExpression()
|
||
|
{
|
||
|
QString exp(watchVarEditor_->currentText());
|
||
|
if (!exp.isEmpty())
|
||
|
{
|
||
|
slotEvaluateExpression(exp);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void VariableWidget::slotEvaluateExpression(const QString &ident)
|
||
|
{
|
||
|
if (!ident.isEmpty())
|
||
|
{
|
||
|
watchVarEditor_->addToHistory(ident);
|
||
|
varTree_->slotEvaluateExpression(ident);
|
||
|
watchVarEditor_->clearEdit();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// **************************************************************************
|
||
|
|
||
|
void VariableWidget::focusInEvent(QFocusEvent */*e*/)
|
||
|
{
|
||
|
varTree_->setFocus();
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
// **************************************************************************
|
||
|
// **************************************************************************
|
||
|
// **************************************************************************
|
||
|
|
||
|
VariableTree::VariableTree(VariableWidget *parent,
|
||
|
GDBController* controller,
|
||
|
GDBBreakpointWidget* breakpointWidget,
|
||
|
const char *name)
|
||
|
: KListView(parent, name),
|
||
|
QToolTip( viewport() ),
|
||
|
controller_(controller),
|
||
|
breakpointWidget_(breakpointWidget),
|
||
|
activeFlag_(0),
|
||
|
recentExpressions_(0),
|
||
|
currentFrameItem(0),
|
||
|
activePopup_(0)
|
||
|
{
|
||
|
setRootIsDecorated(true);
|
||
|
setAllColumnsShowFocus(true);
|
||
|
setSorting(-1);
|
||
|
QListView::setSelectionMode(QListView::Single);
|
||
|
|
||
|
// Note: it might be reasonable to set width of value
|
||
|
// column to 10 characters ('0x12345678'), and rely on
|
||
|
// tooltips for showing larger values. Currently, both
|
||
|
// columns will get roughly equal width.
|
||
|
addColumn(i18n("Variable"));
|
||
|
addColumn(i18n("Value"));
|
||
|
// setResizeMode(AllColumns);
|
||
|
|
||
|
connect( this, SIGNAL(contextMenu(KListView*, QListViewItem*, const QPoint&)),
|
||
|
SLOT(slotContextMenu(KListView*, QListViewItem*)) );
|
||
|
connect( this, SIGNAL(itemRenamed( QListViewItem*, int, const QString&)),
|
||
|
this, SLOT(slotItemRenamed( QListViewItem*, int, const QString&)));
|
||
|
}
|
||
|
|
||
|
// **************************************************************************
|
||
|
|
||
|
VariableTree::~VariableTree()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
// **************************************************************************
|
||
|
|
||
|
void VariableTree::slotContextMenu(KListView *, QListViewItem *item)
|
||
|
{
|
||
|
if (!item)
|
||
|
return;
|
||
|
|
||
|
setSelected(item, true); // Need to select this item.
|
||
|
|
||
|
if (item->parent())
|
||
|
{
|
||
|
KPopupMenu popup(this);
|
||
|
KPopupMenu format(this);
|
||
|
|
||
|
int idRemember = -2;
|
||
|
int idRemove = -2;
|
||
|
int idReevaluate = -2;
|
||
|
int idWatch = -2;
|
||
|
|
||
|
int idNatural = -2;
|
||
|
int idHex = -2;
|
||
|
int idDecimal = -2;
|
||
|
int idCharacter = -2;
|
||
|
int idBinary = -2;
|
||
|
|
||
|
#define MAYBE_DISABLE(id) if (!var->isAlive()) popup.setItemEnabled(id, false)
|
||
|
|
||
|
VarItem* var;
|
||
|
if ((var = dynamic_cast<VarItem*>(item)))
|
||
|
{
|
||
|
popup.insertTitle(var->gdbExpression());
|
||
|
|
||
|
|
||
|
format.setCheckable(true);
|
||
|
idNatural = format.insertItem(i18n("Natural"),
|
||
|
(int)VarItem::natural);
|
||
|
format.setAccel(Qt::Key_N, idNatural);
|
||
|
idHex = format.insertItem(i18n("Hexadecimal"),
|
||
|
(int)VarItem::hexadecimal);
|
||
|
format.setAccel(Qt::Key_X, idHex);
|
||
|
idDecimal = format.insertItem(i18n("Decimal"),
|
||
|
(int)VarItem::decimal);
|
||
|
format.setAccel(Qt::Key_D, idDecimal);
|
||
|
idCharacter = format.insertItem(i18n("Character"),
|
||
|
(int)VarItem::character);
|
||
|
format.setAccel(Qt::Key_C, idCharacter);
|
||
|
idBinary = format.insertItem(i18n("Binary"),
|
||
|
(int)VarItem::binary);
|
||
|
format.setAccel(Qt::Key_T, idBinary);
|
||
|
|
||
|
|
||
|
format.setItemChecked((int)(var->format()), true);
|
||
|
|
||
|
int id = popup.insertItem(i18n("Format"), &format);
|
||
|
MAYBE_DISABLE(id);
|
||
|
}
|
||
|
|
||
|
|
||
|
QListViewItem* root = findRoot(item);
|
||
|
|
||
|
if (root != recentExpressions_)
|
||
|
{
|
||
|
idRemember = popup.insertItem(
|
||
|
SmallIcon("pencil"), i18n("Remember Value"));
|
||
|
MAYBE_DISABLE(idRemember);
|
||
|
}
|
||
|
|
||
|
if (dynamic_cast<WatchRoot*>(root)) {
|
||
|
idRemove = popup.insertItem(
|
||
|
SmallIcon("editdelete"), i18n("Remove Watch Variable") );
|
||
|
popup.setAccel(Qt::Key_Delete, idRemove);
|
||
|
} else if (root != recentExpressions_) {
|
||
|
idWatch = popup.insertItem(
|
||
|
i18n("Watch Variable"));
|
||
|
MAYBE_DISABLE(idWatch);
|
||
|
}
|
||
|
if (root == recentExpressions_) {
|
||
|
idReevaluate = popup.insertItem(
|
||
|
SmallIcon("reload"), i18n("Reevaluate Expression") );
|
||
|
MAYBE_DISABLE(idReevaluate);
|
||
|
idRemove = popup.insertItem(
|
||
|
SmallIcon("editdelete"), i18n("Remove Expression") );
|
||
|
popup.setAccel(Qt::Key_Delete, idRemove);
|
||
|
}
|
||
|
|
||
|
if (var)
|
||
|
{
|
||
|
popup.insertItem( i18n("Data write breakpoint"), idToggleWatch );
|
||
|
popup.setItemEnabled(idToggleWatch, false);
|
||
|
}
|
||
|
|
||
|
int idCopyToClipboard = popup.insertItem(
|
||
|
SmallIcon("editcopy"), i18n("Copy Value") );
|
||
|
popup.setAccel(Qt::CTRL + Qt::Key_C, idCopyToClipboard);
|
||
|
|
||
|
activePopup_ = &popup;
|
||
|
/* This code can be executed when debugger is stopped,
|
||
|
and we invoke popup menu on a var under "recent expressions"
|
||
|
just to delete it. */
|
||
|
if (var && var->isAlive() && !controller()->stateIsOn(s_dbgNotStarted))
|
||
|
controller_->addCommand(
|
||
|
new GDBCommand(
|
||
|
QString("-data-evaluate-expression &%1")
|
||
|
.arg(var->gdbExpression()),
|
||
|
this,
|
||
|
&VariableTree::handleAddressComputed,
|
||
|
true /*handles error*/));
|
||
|
|
||
|
|
||
|
int res = popup.exec(QCursor::pos());
|
||
|
|
||
|
activePopup_ = 0;
|
||
|
|
||
|
|
||
|
if (res == idNatural || res == idHex || res == idDecimal
|
||
|
|| res == idCharacter || res == idBinary)
|
||
|
{
|
||
|
// Change format.
|
||
|
VarItem* var_item = static_cast<VarItem*>(item);
|
||
|
var_item->setFormat(static_cast<VarItem::format_t>(res));
|
||
|
}
|
||
|
else if (res == idRemember)
|
||
|
{
|
||
|
if (VarItem *item = dynamic_cast<VarItem*>(currentItem()))
|
||
|
{
|
||
|
((VariableWidget*)parent())->
|
||
|
slotEvaluateExpression(item->gdbExpression());
|
||
|
}
|
||
|
}
|
||
|
else if (res == idWatch)
|
||
|
{
|
||
|
if (VarItem *item = dynamic_cast<VarItem*>(currentItem()))
|
||
|
{
|
||
|
((VariableWidget*)parent())->
|
||
|
slotAddWatchVariable(item->gdbExpression());
|
||
|
}
|
||
|
}
|
||
|
else if (res == idRemove)
|
||
|
delete item;
|
||
|
else if (res == idCopyToClipboard)
|
||
|
{
|
||
|
copyToClipboard(item);
|
||
|
}
|
||
|
else if (res == idToggleWatch)
|
||
|
{
|
||
|
if (VarItem *item = dynamic_cast<VarItem*>(currentItem()))
|
||
|
emit toggleWatchpoint(item->gdbExpression());
|
||
|
}
|
||
|
else if (res == idReevaluate)
|
||
|
{
|
||
|
if (VarItem* item = dynamic_cast<VarItem*>(currentItem()))
|
||
|
{
|
||
|
item->recreate();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if (item == recentExpressions_)
|
||
|
{
|
||
|
KPopupMenu popup(this);
|
||
|
popup.insertTitle(i18n("Recent Expressions"));
|
||
|
int idRemove = popup.insertItem(
|
||
|
SmallIcon("editdelete"), i18n("Remove All"));
|
||
|
int idReevaluate = popup.insertItem(
|
||
|
SmallIcon("reload"), i18n("Reevaluate All"));
|
||
|
if (controller()->stateIsOn(s_dbgNotStarted))
|
||
|
popup.setItemEnabled(idReevaluate, false);
|
||
|
int res = popup.exec(QCursor::pos());
|
||
|
|
||
|
if (res == idRemove)
|
||
|
{
|
||
|
delete recentExpressions_;
|
||
|
recentExpressions_ = 0;
|
||
|
}
|
||
|
else if (res == idReevaluate)
|
||
|
{
|
||
|
for(QListViewItem* child = recentExpressions_->firstChild();
|
||
|
child; child = child->nextSibling())
|
||
|
{
|
||
|
static_cast<VarItem*>(child)->recreate();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void VariableTree::slotEvent(GDBController::event_t event)
|
||
|
{
|
||
|
switch(event)
|
||
|
{
|
||
|
case GDBController::program_exited:
|
||
|
case GDBController::debugger_exited:
|
||
|
{
|
||
|
// Remove all locals.
|
||
|
QListViewItem *child = firstChild();
|
||
|
|
||
|
while (child) {
|
||
|
QListViewItem *nextChild = child->nextSibling();
|
||
|
|
||
|
// don't remove the watch root, or 'recent expressions' root.
|
||
|
if (!(dynamic_cast<WatchRoot*> (child))
|
||
|
&& child != recentExpressions_)
|
||
|
{
|
||
|
delete child;
|
||
|
}
|
||
|
child = nextChild;
|
||
|
}
|
||
|
currentFrameItem = 0;
|
||
|
|
||
|
if (recentExpressions_)
|
||
|
{
|
||
|
for(QListViewItem* child = recentExpressions_->firstChild();
|
||
|
child; child = child->nextSibling())
|
||
|
{
|
||
|
static_cast<VarItem*>(child)->unhookFromGdb();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (WatchRoot* w = findWatch())
|
||
|
{
|
||
|
for(QListViewItem* child = w->firstChild();
|
||
|
child; child = child->nextSibling())
|
||
|
{
|
||
|
static_cast<VarItem*>(child)->unhookFromGdb();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case GDBController::program_state_changed:
|
||
|
|
||
|
// Fall-through intended.
|
||
|
|
||
|
case GDBController::thread_or_frame_changed:
|
||
|
{
|
||
|
VarFrameRoot *frame = demand_frame_root(
|
||
|
controller_->currentFrame(), controller_->currentThread());
|
||
|
|
||
|
if (frame->isOpen())
|
||
|
{
|
||
|
updateCurrentFrame();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
frame->setDirty();
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void VariableTree::updateCurrentFrame()
|
||
|
{
|
||
|
// In GDB 6.4, the -stack-list-locals command is broken.
|
||
|
// If there's any local reference variable which is not
|
||
|
// initialized yet, for example because it's in the middle
|
||
|
// of function, gdb will still print it and try to dereference
|
||
|
// it. If the address in not accessible, the MI command will
|
||
|
// exit with an error, and we won't be able to see *any*
|
||
|
// locals. A patch is submitted:
|
||
|
// http://sourceware.org/ml/gdb-patches/2006-04/msg00069.html
|
||
|
// but we need to work with 6.4, not with some future version. So,
|
||
|
// we just -stack-list-locals to get just names of the locals,
|
||
|
// but not their values.
|
||
|
// We'll fetch values separately:
|
||
|
|
||
|
controller_->addCommand(
|
||
|
new GDBCommand(QString("-stack-list-arguments 0 %1 %2")
|
||
|
.arg(controller_->currentFrame())
|
||
|
.arg(controller_->currentFrame())
|
||
|
.ascii(),
|
||
|
this,
|
||
|
&VariableTree::argumentsReady));
|
||
|
|
||
|
|
||
|
controller_->addCommand(
|
||
|
new GDBCommand("-stack-list-locals 0",
|
||
|
this,
|
||
|
&VariableTree::localsReady));
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
// **************************************************************************
|
||
|
|
||
|
void VariableTree::slotAddWatchVariable(const QString &watchVar)
|
||
|
{
|
||
|
VarItem *varItem = 0;
|
||
|
varItem = new VarItem(findWatch(), watchVar);
|
||
|
}
|
||
|
|
||
|
void VariableTree::slotEvaluateExpression(const QString &expression)
|
||
|
{
|
||
|
if (recentExpressions_ == 0)
|
||
|
{
|
||
|
recentExpressions_ = new TrimmableItem(this);
|
||
|
recentExpressions_->setText(0, "Recent");
|
||
|
recentExpressions_->setOpen(true);
|
||
|
}
|
||
|
|
||
|
VarItem *varItem = new VarItem(recentExpressions_,
|
||
|
expression,
|
||
|
true /* freeze */);
|
||
|
varItem->setRenameEnabled(0, 1);
|
||
|
}
|
||
|
|
||
|
// **************************************************************************
|
||
|
|
||
|
QListViewItem *VariableTree::findRoot(QListViewItem *item) const
|
||
|
{
|
||
|
while (item->parent())
|
||
|
item = item->parent();
|
||
|
|
||
|
return item;
|
||
|
}
|
||
|
|
||
|
// **************************************************************************
|
||
|
|
||
|
VarFrameRoot *VariableTree::findFrame(int frameNo, int threadNo) const
|
||
|
{
|
||
|
QListViewItem *sibling = firstChild();
|
||
|
|
||
|
// frames only exist on th top level so we only need to
|
||
|
// check the siblings
|
||
|
while (sibling) {
|
||
|
VarFrameRoot *frame = dynamic_cast<VarFrameRoot*> (sibling);
|
||
|
if (frame && frame->matchDetails(frameNo, threadNo))
|
||
|
return frame;
|
||
|
|
||
|
sibling = sibling->nextSibling();
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
// **************************************************************************
|
||
|
|
||
|
WatchRoot *VariableTree::findWatch()
|
||
|
{
|
||
|
QListViewItem *sibling = firstChild();
|
||
|
|
||
|
while (sibling) {
|
||
|
if (WatchRoot *watch = dynamic_cast<WatchRoot*> (sibling))
|
||
|
return watch;
|
||
|
|
||
|
sibling = sibling->nextSibling();
|
||
|
}
|
||
|
|
||
|
return new WatchRoot(this);
|
||
|
}
|
||
|
|
||
|
// **************************************************************************
|
||
|
|
||
|
QListViewItem *VariableTree::lastChild() const
|
||
|
{
|
||
|
QListViewItem *child = firstChild();
|
||
|
if (child)
|
||
|
while (QListViewItem *nextChild = child->nextSibling())
|
||
|
child = nextChild;
|
||
|
|
||
|
return child;
|
||
|
}
|
||
|
|
||
|
// **************************************************************************
|
||
|
|
||
|
void VariableTree::maybeTip(const QPoint &p)
|
||
|
{
|
||
|
VarItem * item = dynamic_cast<VarItem*>( itemAt( p ) );
|
||
|
if ( item )
|
||
|
{
|
||
|
QRect r = itemRect( item );
|
||
|
if ( r.isValid() )
|
||
|
tip( r, item->tipText() );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class ValueSpecialRepresentationCommand : public QObject, public CliCommand
|
||
|
{
|
||
|
public:
|
||
|
ValueSpecialRepresentationCommand(VarItem* item, const QString& command)
|
||
|
: CliCommand(command.ascii(),
|
||
|
this,
|
||
|
&ValueSpecialRepresentationCommand::handleReply,
|
||
|
true),
|
||
|
item_(item)
|
||
|
{}
|
||
|
|
||
|
private:
|
||
|
|
||
|
VarItem* item_;
|
||
|
|
||
|
void handleReply(const QValueVector<QString>& lines)
|
||
|
{
|
||
|
QString s;
|
||
|
for(unsigned i = 1; i < lines.count(); ++i)
|
||
|
s += lines[i];
|
||
|
item_->updateSpecialRepresentation(s.local8Bit());
|
||
|
}
|
||
|
};
|
||
|
|
||
|
void VariableTree::slotVarobjNameChanged(
|
||
|
const QString& from, const QString& to)
|
||
|
{
|
||
|
if (!from.isEmpty())
|
||
|
varobj2varitem.erase(from);
|
||
|
|
||
|
if (!to.isEmpty())
|
||
|
varobj2varitem[to] =
|
||
|
const_cast<VarItem*>(
|
||
|
static_cast<const VarItem*>(sender()));
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
VarFrameRoot* VariableTree::demand_frame_root(int frameNo, int threadNo)
|
||
|
{
|
||
|
VarFrameRoot *frame = findFrame(frameNo, threadNo);
|
||
|
if (!frame)
|
||
|
{
|
||
|
frame = new VarFrameRoot(this, frameNo, threadNo);
|
||
|
frame->setFrameName(i18n("Locals"));
|
||
|
// Make sure "Locals" item is always the top item, before
|
||
|
// "watch" and "recent experessions" items.
|
||
|
this->takeItem(frame);
|
||
|
this->insertItem(frame);
|
||
|
frame->setOpen(true);
|
||
|
}
|
||
|
return frame;
|
||
|
}
|
||
|
|
||
|
void VariableTree::argumentsReady(const GDBMI::ResultRecord& r)
|
||
|
{
|
||
|
const GDBMI::Value& args = r["stack-args"][0]["args"];
|
||
|
|
||
|
fetch_time.start();
|
||
|
|
||
|
locals_and_arguments.clear();
|
||
|
for(unsigned i = 0; i < args.size(); ++i)
|
||
|
{
|
||
|
locals_and_arguments.push_back(args[i].literal());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void VariableTree::localsReady(const GDBMI::ResultRecord& r)
|
||
|
{
|
||
|
const GDBMI::Value& locals = r["locals"];
|
||
|
|
||
|
for(unsigned i = 0; i < locals.size(); ++i)
|
||
|
{
|
||
|
QString val = locals[i].literal();
|
||
|
|
||
|
// Check ada internal variables like <R45b>, <L23R> ...
|
||
|
bool is_ada_variable = (val[0] == '<' && val[val.length() - 1] == '>');
|
||
|
|
||
|
if (!is_ada_variable)
|
||
|
{
|
||
|
locals_and_arguments.push_back(val);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
controller_->addCommand(new CliCommand("info frame",
|
||
|
this,
|
||
|
&VariableTree::frameIdReady));
|
||
|
}
|
||
|
|
||
|
void VariableTree::frameIdReady(const QValueVector<QString>& lines)
|
||
|
{
|
||
|
//kdDebug(9012) << "localAddresses: " << lines[1] << "\n";
|
||
|
|
||
|
QString frame_info;
|
||
|
for(unsigned i = 1; i < lines.size(); ++i)
|
||
|
frame_info += lines[i];
|
||
|
|
||
|
kdDebug(9012) << "frame info: " << frame_info << "\n";
|
||
|
frame_info.replace('\n', "");
|
||
|
|
||
|
static QRegExp frame_base_rx("frame at 0x([0-9a-fA-F]*)");
|
||
|
static QRegExp frame_code_rx("saved [a-zA-Z0-9]* 0x([0-9a-fA-F]*)");
|
||
|
|
||
|
int i = frame_base_rx.search(frame_info);
|
||
|
int i2 = frame_code_rx.search(frame_info);
|
||
|
|
||
|
bool frameIdChanged = false;
|
||
|
|
||
|
VarFrameRoot *frame = demand_frame_root(
|
||
|
controller_->currentFrame(), controller_->currentThread());
|
||
|
|
||
|
if (frame != currentFrameItem)
|
||
|
{
|
||
|
if (currentFrameItem)
|
||
|
{
|
||
|
currentFrameItem->setVisible(false);
|
||
|
}
|
||
|
}
|
||
|
currentFrameItem = frame;
|
||
|
currentFrameItem->setVisible(true);
|
||
|
|
||
|
|
||
|
if (i != -1 && i2 != -1)
|
||
|
{
|
||
|
unsigned long long new_frame_base =
|
||
|
frame_base_rx.cap(1).toULongLong(0, 16);
|
||
|
unsigned long long new_code_address =
|
||
|
frame_code_rx.cap(1).toULongLong(0, 16);
|
||
|
kdDebug(9012) << "Frame base = " << QString::number(new_frame_base, 16)
|
||
|
<< " code = " << QString::number(new_code_address, 16)
|
||
|
<< "\n";
|
||
|
kdDebug(9012) << "Previous frame " <<
|
||
|
QString::number(frame->currentFrameBase, 16)
|
||
|
<< " code = " << QString::number(
|
||
|
frame->currentFrameCodeAddress, 16)
|
||
|
<< "\n";
|
||
|
|
||
|
frameIdChanged = (new_frame_base != frame->currentFrameBase ||
|
||
|
new_code_address != frame->currentFrameCodeAddress);
|
||
|
|
||
|
frame->currentFrameBase = new_frame_base;
|
||
|
frame->currentFrameCodeAddress = new_code_address;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
KMessageBox::information(
|
||
|
0,
|
||
|
"<b>Can't get frame id</b>"
|
||
|
"Could not found frame id from output of 'info frame'. "
|
||
|
"Further debugging can be unreliable. ",
|
||
|
i18n("Internal error"), "gdb_error");
|
||
|
}
|
||
|
|
||
|
if (frameIdChanged)
|
||
|
{
|
||
|
// Remove all variables.
|
||
|
// FIXME: probably, need to do this in all frames.
|
||
|
QListViewItem* child = frame->firstChild();
|
||
|
QListViewItem* next;
|
||
|
for(; child; child = next)
|
||
|
{
|
||
|
next = child->nextSibling();
|
||
|
delete child;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
setUpdatesEnabled(false);
|
||
|
|
||
|
std::set<QListViewItem*> alive;
|
||
|
|
||
|
for(unsigned i = 0; i < locals_and_arguments.size(); ++i)
|
||
|
{
|
||
|
QString name = locals_and_arguments[i];
|
||
|
|
||
|
// See if we've got VarItem for this one already.
|
||
|
VarItem* var = 0;
|
||
|
for(QListViewItem *child = frame->firstChild();
|
||
|
child;
|
||
|
child = child->nextSibling())
|
||
|
{
|
||
|
if (child->text(VarNameCol) == name)
|
||
|
{
|
||
|
var = dynamic_cast<VarItem*>(child);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (!var)
|
||
|
{
|
||
|
var = new VarItem(frame, name);
|
||
|
}
|
||
|
alive.insert(var);
|
||
|
|
||
|
var->clearHighlight();
|
||
|
}
|
||
|
|
||
|
// Remove VarItems that don't correspond to any local
|
||
|
// variables any longer. Perform type/address updates
|
||
|
// for others.
|
||
|
for(QListViewItem* child = frame->firstChild(); child;)
|
||
|
{
|
||
|
QListViewItem* current = child;
|
||
|
child = current->nextSibling();
|
||
|
if (!alive.count(current))
|
||
|
delete current;
|
||
|
else
|
||
|
static_cast<VarItem*>(current)->recreateLocallyMaybe();
|
||
|
}
|
||
|
|
||
|
for(QListViewItem* child = findWatch()->firstChild();
|
||
|
child; child = child->nextSibling())
|
||
|
{
|
||
|
VarItem* var = static_cast<VarItem*>(child);
|
||
|
var->clearHighlight();
|
||
|
// For watched expressions, we don't have an easy way
|
||
|
// to check if their meaning is still the same, so
|
||
|
// unconditionally recreate them.
|
||
|
var->recreate();
|
||
|
}
|
||
|
|
||
|
// Note: can't use --all-values in this command, because gdb will
|
||
|
// die if there's any uninitialized variable. Ouch!
|
||
|
controller_->addCommand(new GDBCommand(
|
||
|
"-var-update *",
|
||
|
this,
|
||
|
&VariableTree::handleVarUpdate));
|
||
|
|
||
|
controller_->addCommand(new SentinelCommand(
|
||
|
this,
|
||
|
&VariableTree::variablesFetchDone));
|
||
|
}
|
||
|
|
||
|
void VariableTree::handleVarUpdate(const GDBMI::ResultRecord& r)
|
||
|
{
|
||
|
const GDBMI::Value& changed = r["changelist"];
|
||
|
|
||
|
std::set<QString> names_to_update;
|
||
|
|
||
|
for(unsigned i = 0; i < changed.size(); ++i)
|
||
|
{
|
||
|
const GDBMI::Value& c = changed[i];
|
||
|
|
||
|
QString name = c["name"].literal();
|
||
|
if (c.hasField("in_scope") && c["in_scope"].literal() == "false")
|
||
|
continue;
|
||
|
|
||
|
names_to_update.insert(name);
|
||
|
}
|
||
|
|
||
|
QMap<QString, VarItem*>::iterator i, e;
|
||
|
for (i = varobj2varitem.begin(), e = varobj2varitem.end(); i != e; ++i)
|
||
|
{
|
||
|
if (names_to_update.count(i.key())
|
||
|
|| i.data()->updateUnconditionally())
|
||
|
{
|
||
|
i.data()->updateValue();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void VarItem::handleCliPrint(const QValueVector<QString>& lines)
|
||
|
{
|
||
|
static QRegExp r("(\\$[0-9]+)");
|
||
|
if (lines.size() >= 2)
|
||
|
{
|
||
|
int i = r.search(lines[1]);
|
||
|
if (i == 0)
|
||
|
{
|
||
|
controller_->addCommand(
|
||
|
new GDBCommand(QString("-var-create %1 * \"%2\"")
|
||
|
.arg(varobjName_)
|
||
|
.arg(r.cap(1)),
|
||
|
this,
|
||
|
&VarItem::varobjCreated,
|
||
|
// On initial create, errors get reported
|
||
|
// by generic code. After then, errors
|
||
|
// are swallowed by varobjCreated.
|
||
|
initialCreation_ ? false : true));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// FIXME: merge all output lines together.
|
||
|
// FIXME: add 'debuggerError' to debuggerpart.
|
||
|
KMessageBox::information(
|
||
|
0,
|
||
|
i18n("<b>Debugger error</b><br>") + lines[1],
|
||
|
i18n("Debugger error"), "gdb_error");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void VariableTree::variablesFetchDone()
|
||
|
{
|
||
|
// During parsing of fetched variable values, we might have issued
|
||
|
// extra command to handle 'special values', like QString.
|
||
|
// We don't want to enable updates just yet, because this will cause
|
||
|
// flicker, so add a sentinel command just to enable updates.
|
||
|
//
|
||
|
// We need this intermediate hook because commands for special
|
||
|
// representation are issues when responses to orginary fetch
|
||
|
// values commands are received, so we can add sentinel command after
|
||
|
// special representation fetch only when commands for ordinary
|
||
|
// fetch are all executed.
|
||
|
controller_->addCommand(new SentinelCommand(
|
||
|
this,
|
||
|
&VariableTree::fetchSpecialValuesDone));
|
||
|
|
||
|
}
|
||
|
|
||
|
void VariableTree::fetchSpecialValuesDone()
|
||
|
{
|
||
|
// FIXME: can currentFrame_ or currentThread_ change between
|
||
|
// start of var fetch and call of 'variablesFetchDone'?
|
||
|
VarFrameRoot *frame = demand_frame_root(
|
||
|
controller_->currentFrame(), controller_->currentThread());
|
||
|
|
||
|
// frame->trim();
|
||
|
|
||
|
frame->needLocals_ = false;
|
||
|
|
||
|
setUpdatesEnabled(true);
|
||
|
triggerUpdate();
|
||
|
|
||
|
kdDebug(9012) << "Time to fetch variables: " << fetch_time.elapsed() <<
|
||
|
"ms\n";
|
||
|
}
|
||
|
|
||
|
void
|
||
|
VariableTree::slotItemRenamed(QListViewItem* item, int col, const QString& text)
|
||
|
{
|
||
|
if (col == ValueCol)
|
||
|
{
|
||
|
VarItem* v = dynamic_cast<VarItem*>(item);
|
||
|
Q_ASSERT(v);
|
||
|
if (v)
|
||
|
{
|
||
|
v->setValue(text);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void VariableTree::keyPressEvent(QKeyEvent* e)
|
||
|
{
|
||
|
if (VarItem* item = dynamic_cast<VarItem*>(currentItem()))
|
||
|
{
|
||
|
QString text = e->text();
|
||
|
|
||
|
if (text == "n" || text == "x" || text == "d" || text == "c"
|
||
|
|| text == "t")
|
||
|
{
|
||
|
item->setFormat(
|
||
|
item->formatFromGdbModifier(text[0].latin1()));
|
||
|
}
|
||
|
|
||
|
if (e->key() == Qt::Key_Delete)
|
||
|
{
|
||
|
QListViewItem* root = findRoot(item);
|
||
|
|
||
|
if (dynamic_cast<WatchRoot*>(root) || root == recentExpressions_)
|
||
|
{
|
||
|
delete item;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (e->key() == Qt::Key_C && e->state() == Qt::ControlButton)
|
||
|
{
|
||
|
copyToClipboard(item);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void VariableTree::copyToClipboard(QListViewItem* item)
|
||
|
{
|
||
|
QClipboard *qb = KApplication::clipboard();
|
||
|
QString text = item->text( 1 );
|
||
|
|
||
|
qb->setText( text, QClipboard::Clipboard );
|
||
|
}
|
||
|
|
||
|
void VariableTree::handleAddressComputed(const GDBMI::ResultRecord& r)
|
||
|
{
|
||
|
if (r.reason == "error")
|
||
|
{
|
||
|
// Not lvalue, leave item disabled.
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (activePopup_)
|
||
|
{
|
||
|
activePopup_->setItemEnabled(idToggleWatch, true);
|
||
|
|
||
|
unsigned long long address = r["value"].literal().toULongLong(0, 16);
|
||
|
if (breakpointWidget_->hasWatchpointForAddress(address))
|
||
|
{
|
||
|
activePopup_->setItemChecked(idToggleWatch, true);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// **************************************************************************
|
||
|
// **************************************************************************
|
||
|
// **************************************************************************
|
||
|
|
||
|
TrimmableItem::TrimmableItem(VariableTree *parent)
|
||
|
: KListViewItem (parent, parent->lastChild())
|
||
|
{
|
||
|
}
|
||
|
|
||
|
// **************************************************************************
|
||
|
|
||
|
TrimmableItem::TrimmableItem(TrimmableItem *parent)
|
||
|
: KListViewItem (parent, parent->lastChild())
|
||
|
{
|
||
|
}
|
||
|
|
||
|
// **************************************************************************
|
||
|
|
||
|
TrimmableItem::~TrimmableItem()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
// **************************************************************************
|
||
|
|
||
|
void TrimmableItem::paintCell(QPainter *p, const QColorGroup &cg,
|
||
|
int column, int width, int align)
|
||
|
{
|
||
|
if ( !p )
|
||
|
return;
|
||
|
// make toplevel item (watch and frame items) names bold
|
||
|
if (column == 0 && !parent())
|
||
|
{
|
||
|
QFont f = p->font();
|
||
|
f.setBold(true);
|
||
|
p->setFont(f);
|
||
|
}
|
||
|
QListViewItem::paintCell( p, cg, column, width, align );
|
||
|
}
|
||
|
|
||
|
QListViewItem *TrimmableItem::lastChild() const
|
||
|
{
|
||
|
QListViewItem *child = firstChild();
|
||
|
if (child)
|
||
|
while (QListViewItem *nextChild = child->nextSibling())
|
||
|
child = nextChild;
|
||
|
|
||
|
return child;
|
||
|
}
|
||
|
|
||
|
// **************************************************************************
|
||
|
// **************************************************************************
|
||
|
// **************************************************************************
|
||
|
|
||
|
int VarItem::varobjIndex = 0;
|
||
|
|
||
|
VarItem::VarItem(TrimmableItem *parent,
|
||
|
const QString& expression,
|
||
|
bool frozen)
|
||
|
: TrimmableItem (parent),
|
||
|
expression_(expression),
|
||
|
highlight_(false),
|
||
|
oldSpecialRepresentationSet_(false),
|
||
|
format_(natural),
|
||
|
numChildren_(0),
|
||
|
childrenFetched_(false),
|
||
|
updateUnconditionally_(false),
|
||
|
frozen_(frozen),
|
||
|
initialCreation_(true),
|
||
|
baseClassMember_(false),
|
||
|
alive_(true)
|
||
|
{
|
||
|
connect(this, SIGNAL(varobjNameChange(const QString&, const QString&)),
|
||
|
varTree(),
|
||
|
SLOT(slotVarobjNameChanged(const QString&, const QString&)));
|
||
|
|
||
|
|
||
|
// User might have entered format together with expression: like
|
||
|
// /x i1+i2
|
||
|
// If we do nothing, it will be impossible to watch the variable in
|
||
|
// different format, as we'll just add extra format specifier.
|
||
|
// So:
|
||
|
// - detect initial value of format_
|
||
|
// - remove the format specifier from the string.
|
||
|
|
||
|
static QRegExp explicit_format("^\\s*/(.)\\s*(.*)");
|
||
|
if (explicit_format.search(expression_) == 0)
|
||
|
{
|
||
|
format_ = formatFromGdbModifier(explicit_format.cap(1)[0].latin1());
|
||
|
expression_ = explicit_format.cap(2);
|
||
|
}
|
||
|
|
||
|
setText(VarNameCol, expression_);
|
||
|
// Allow to change variable name by editing.
|
||
|
setRenameEnabled(ValueCol, true);
|
||
|
|
||
|
// Need to store this locally, since varTree() is 0 in
|
||
|
// destructor.
|
||
|
controller_ = varTree()->controller();
|
||
|
|
||
|
createVarobj();
|
||
|
}
|
||
|
|
||
|
VarItem::VarItem(TrimmableItem *parent, const GDBMI::Value& varobj,
|
||
|
format_t format, bool baseClassMember)
|
||
|
: TrimmableItem (parent),
|
||
|
highlight_(false),
|
||
|
oldSpecialRepresentationSet_(false),
|
||
|
format_(format),
|
||
|
numChildren_(0),
|
||
|
childrenFetched_(false),
|
||
|
updateUnconditionally_(false),
|
||
|
frozen_(false),
|
||
|
initialCreation_(false),
|
||
|
baseClassMember_(baseClassMember),
|
||
|
alive_(true)
|
||
|
{
|
||
|
connect(this, SIGNAL(varobjNameChange(const QString&, const QString&)),
|
||
|
varTree(),
|
||
|
SLOT(slotVarobjNameChanged(const QString&, const QString&)));
|
||
|
|
||
|
expression_ = varobj["exp"].literal();
|
||
|
varobjName_ = varobj["name"].literal();
|
||
|
|
||
|
varobjNameChange("", varobjName_);
|
||
|
|
||
|
setText(VarNameCol, displayName());
|
||
|
|
||
|
// Allow to change variable name by editing.
|
||
|
setRenameEnabled(ValueCol, true);
|
||
|
|
||
|
controller_ = varTree()->controller();
|
||
|
|
||
|
// Set type and children.
|
||
|
originalValueType_ = varobj["type"].literal();
|
||
|
numChildren_ = varobj["numchild"].literal().toInt();
|
||
|
setExpandable(numChildren_ != 0);
|
||
|
|
||
|
|
||
|
// Get the initial value.
|
||
|
updateValue();
|
||
|
}
|
||
|
|
||
|
void VarItem::createVarobj()
|
||
|
{
|
||
|
QString old = varobjName_;
|
||
|
varobjName_ = QString("KDEV%1").arg(varobjIndex++);
|
||
|
emit varobjNameChange(old, varobjName_);
|
||
|
|
||
|
if (frozen_)
|
||
|
{
|
||
|
// MI has no way to freeze a variable object. So, we
|
||
|
// issue print command that returns $NN convenience
|
||
|
// variable and we create variable object from that.
|
||
|
controller_->addCommand(
|
||
|
new CliCommand(
|
||
|
QString("print %1").arg(expression_),
|
||
|
this,
|
||
|
&VarItem::handleCliPrint));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
controller_->addCommand(
|
||
|
new CliCommand(
|
||
|
QString("print /x &%1").arg(expression_),
|
||
|
this,
|
||
|
&VarItem::handleCurrentAddress,
|
||
|
true));
|
||
|
|
||
|
controller_->addCommand(
|
||
|
// Need to quote expression, otherwise gdb won't like
|
||
|
// spaces inside it.
|
||
|
new GDBCommand(QString("-var-create %1 * \"%2\"")
|
||
|
.arg(varobjName_)
|
||
|
.arg(expression_),
|
||
|
this,
|
||
|
&VarItem::varobjCreated,
|
||
|
initialCreation_ ? false : true));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void VarItem::varobjCreated(const GDBMI::ResultRecord& r)
|
||
|
{
|
||
|
// If we've tried to recreate varobj (for example for watched expression)
|
||
|
// after step, and it's no longer valid, it's fine.
|
||
|
if (r.reason == "error")
|
||
|
{
|
||
|
varobjName_ = "";
|
||
|
return;
|
||
|
}
|
||
|
setAliveRecursively(true);
|
||
|
|
||
|
QString oldType = originalValueType_;
|
||
|
originalValueType_ = r["type"].literal();
|
||
|
if (!oldType.isEmpty() && oldType != originalValueType_)
|
||
|
{
|
||
|
// Type changed, the children might be no longer valid,
|
||
|
// so delete them.
|
||
|
for(QListViewItem* child = firstChild(); child; )
|
||
|
{
|
||
|
QListViewItem* cur = child;
|
||
|
child = child->nextSibling();
|
||
|
delete cur;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (r.hasField("exp"))
|
||
|
expression_ = r["exp"].literal();
|
||
|
numChildren_ = r["numchild"].literal().toInt();
|
||
|
setExpandable(numChildren_ != 0);
|
||
|
currentAddress_ = lastObtainedAddress_;
|
||
|
|
||
|
setVarobjName(varobjName_);
|
||
|
}
|
||
|
|
||
|
void VarItem::setVarobjName(const QString& name)
|
||
|
{
|
||
|
if (varobjName_ != name)
|
||
|
emit varobjNameChange(varobjName_, name);
|
||
|
|
||
|
varobjName_ = name;
|
||
|
|
||
|
if (format_ != natural)
|
||
|
{
|
||
|
controller_->addCommand(
|
||
|
new GDBCommand(QString("-var-set-format \"%1\" %2")
|
||
|
.arg(varobjName_).arg(varobjFormatName())));
|
||
|
}
|
||
|
|
||
|
// Get the initial value.
|
||
|
updateValue();
|
||
|
|
||
|
if (isOpen())
|
||
|
{
|
||
|
// This regets children list.
|
||
|
setOpen(true);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void VarItem::valueDone(const GDBMI::ResultRecord& r)
|
||
|
{
|
||
|
if (r.reason == "done")
|
||
|
{
|
||
|
QString s = GDBParser::getGDBParser()->undecorateValue(
|
||
|
r["value"].literal());
|
||
|
|
||
|
if (format_ == character)
|
||
|
{
|
||
|
QString encoded = s;
|
||
|
bool ok;
|
||
|
int value = s.toInt(&ok);
|
||
|
if (ok)
|
||
|
{
|
||
|
char c = (char)value;
|
||
|
encoded += " '";
|
||
|
if (std::isprint(c))
|
||
|
encoded += c;
|
||
|
else {
|
||
|
// Try common escape characters.
|
||
|
static char backslashed[] = {'a', 'b', 'f', 'n',
|
||
|
'r', 't', 'v', '0'};
|
||
|
static char represented[] = "\a\b\f\n\r\t\v";
|
||
|
|
||
|
const char* ix = strchr (represented, c);
|
||
|
if (ix) {
|
||
|
encoded += "\\";
|
||
|
encoded += backslashed[ix - represented];
|
||
|
}
|
||
|
else
|
||
|
encoded += "\\" + s;
|
||
|
}
|
||
|
encoded += "'";
|
||
|
s = encoded;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (format_ == binary)
|
||
|
{
|
||
|
// For binary format, split the value at 4-bit boundaries
|
||
|
static QRegExp r("^[01]+$");
|
||
|
int i = r.search(s);
|
||
|
if (i == 0)
|
||
|
{
|
||
|
QString split;
|
||
|
for(unsigned i = 0; i < s.length(); ++i)
|
||
|
{
|
||
|
// For string 11111, we should split it as
|
||
|
// 1 1111, not as 1111 1.
|
||
|
|
||
|
// 0 is past the end character
|
||
|
int distance = i - s.length();
|
||
|
|
||
|
if (distance % 4 == 0 && !split.isEmpty())
|
||
|
split.append(' ');
|
||
|
split.append(s[i]);
|
||
|
}
|
||
|
s = split;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
setText(ValueCol, s);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
QString s = r["msg"].literal();
|
||
|
// Error response.
|
||
|
if (s.startsWith("Cannot access memory"))
|
||
|
{
|
||
|
s = "(inaccessible)";
|
||
|
setExpandable(false);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
setExpandable(numChildren_ != 0);
|
||
|
}
|
||
|
setText(ValueCol, s);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void VarItem::createChildren(const GDBMI::ResultRecord& r,
|
||
|
bool children_of_fake)
|
||
|
{
|
||
|
const GDBMI::Value& children = r["children"];
|
||
|
|
||
|
/* In order to figure out which variable objects correspond
|
||
|
to base class subobject, we first must detect if *this
|
||
|
is a structure type. We use present of 'public'/'private'/'protected'
|
||
|
fake child as an indicator. */
|
||
|
bool structureType = false;
|
||
|
if (!children_of_fake && children.size() > 0)
|
||
|
{
|
||
|
QString exp = children[0]["exp"].literal();
|
||
|
bool ok = false;
|
||
|
exp.toInt(&ok);
|
||
|
if (!ok || exp[0] != '*')
|
||
|
{
|
||
|
structureType = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for (unsigned i = 0; i < children.size(); ++i)
|
||
|
{
|
||
|
QString exp = children[i]["exp"].literal();
|
||
|
// For artificial accessibility nodes,
|
||
|
// fetch their children.
|
||
|
if (exp == "public" || exp == "protected" || exp == "private")
|
||
|
{
|
||
|
QString name = children[i]["name"].literal();
|
||
|
controller_->addCommand(new GDBCommand(
|
||
|
"-var-list-children \"" +
|
||
|
name + "\"",
|
||
|
this,
|
||
|
&VarItem::childrenOfFakesDone));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
/* All children of structures that are not artifical
|
||
|
are base subobjects. */
|
||
|
bool baseObject = structureType;
|
||
|
|
||
|
VarItem* existing = 0;
|
||
|
for(QListViewItem* child = firstChild();
|
||
|
child; child = child->nextSibling())
|
||
|
{
|
||
|
VarItem* v = static_cast<VarItem*>(child);
|
||
|
kdDebug(9012) << "Child exp : " << v->expression_ <<
|
||
|
" new exp " << exp << "\n";
|
||
|
|
||
|
if (v->expression_ == exp)
|
||
|
{
|
||
|
existing = v;
|
||
|
}
|
||
|
}
|
||
|
if (existing)
|
||
|
{
|
||
|
existing->setVarobjName(children[i]["name"].literal());
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
kdDebug(9012) << "Creating new varobj "
|
||
|
<< exp << " " << baseObject << "\n";
|
||
|
// Propagate format from parent.
|
||
|
VarItem* v = 0;
|
||
|
v = new VarItem(this, children[i], format_, baseObject);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void VarItem::childrenDone(const GDBMI::ResultRecord& r)
|
||
|
{
|
||
|
createChildren(r, false);
|
||
|
childrenFetched_ = true;
|
||
|
}
|
||
|
|
||
|
void VarItem::childrenOfFakesDone(const GDBMI::ResultRecord& r)
|
||
|
{
|
||
|
createChildren(r, true);
|
||
|
}
|
||
|
|
||
|
void VarItem::handleCurrentAddress(const QValueVector<QString>& lines)
|
||
|
{
|
||
|
lastObtainedAddress_ = "";
|
||
|
if (lines.count() > 1)
|
||
|
{
|
||
|
static QRegExp r("\\$\\d+ = ([^\n]*)");
|
||
|
int i = r.search(lines[1]);
|
||
|
if (i == 0)
|
||
|
{
|
||
|
lastObtainedAddress_ = r.cap(1);
|
||
|
kdDebug(9012) << "new address " << lastObtainedAddress_ << "\n";
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void VarItem::handleType(const QValueVector<QString>& lines)
|
||
|
{
|
||
|
bool recreate = false;
|
||
|
|
||
|
if (lastObtainedAddress_ != currentAddress_)
|
||
|
{
|
||
|
kdDebug(9012) << "Address changed from " << currentAddress_
|
||
|
<< " to " << lastObtainedAddress_ << "\n";
|
||
|
recreate = true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// FIXME: add error diagnostic.
|
||
|
if (lines.count() > 1)
|
||
|
{
|
||
|
static QRegExp r("type = ([^\n]*)");
|
||
|
int i = r.search(lines[1]);
|
||
|
if (i == 0)
|
||
|
{
|
||
|
kdDebug(9012) << "found type: " << r.cap(1) << "\n";
|
||
|
kdDebug(9012) << "original Type: " << originalValueType_ << "\n";
|
||
|
|
||
|
if (r.cap(1) != originalValueType_)
|
||
|
{
|
||
|
recreate = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (recreate)
|
||
|
{
|
||
|
this->recreate();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
QString VarItem::displayName() const
|
||
|
{
|
||
|
if (expression_[0] != '*')
|
||
|
return expression_;
|
||
|
|
||
|
if (const VarItem* parent =
|
||
|
dynamic_cast<const VarItem*>(TrimmableItem::parent()))
|
||
|
{
|
||
|
return "*" + parent->displayName();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return expression_;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void VarItem::setAliveRecursively(bool enable)
|
||
|
{
|
||
|
setEnabled(enable);
|
||
|
alive_ = true;
|
||
|
|
||
|
for(QListViewItem* child = firstChild();
|
||
|
child; child = child->nextSibling())
|
||
|
{
|
||
|
static_cast<VarItem*>(child)->setAliveRecursively(enable);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
VarItem::~VarItem()
|
||
|
{
|
||
|
unhookFromGdb();
|
||
|
}
|
||
|
|
||
|
QString VarItem::gdbExpression() const
|
||
|
{
|
||
|
// The expression for this item can be either:
|
||
|
// - number, for array element
|
||
|
// - identifier, for member,
|
||
|
// - ***intentifier, for derefenreced pointer.
|
||
|
const VarItem* parent = dynamic_cast<const VarItem*>(TrimmableItem::parent());
|
||
|
|
||
|
bool ok = false;
|
||
|
expression_.toInt(&ok);
|
||
|
if (ok)
|
||
|
{
|
||
|
// Array, parent always exists.
|
||
|
return parent->gdbExpression() + "[" + expression_ + "]";
|
||
|
}
|
||
|
else if (expression_[0] == '*')
|
||
|
{
|
||
|
if (parent)
|
||
|
{
|
||
|
// For MI, expression_ can be "*0" (meaing
|
||
|
// references 0-th element of some array).
|
||
|
// So, we really need to get to the parent to computed the right
|
||
|
// gdb expression.
|
||
|
return "*" + parent->gdbExpression();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Parent can be null for watched expressions. In that case,
|
||
|
// expression_ should be a valid C++ expression.
|
||
|
return expression_;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (parent)
|
||
|
/* This is varitem corresponds to a base suboject,
|
||
|
the expression should cast parent to the base's
|
||
|
type. */
|
||
|
if (baseClassMember_)
|
||
|
return "((" + expression_ + ")" + parent->gdbExpression() + ")";
|
||
|
else
|
||
|
return parent->gdbExpression() + "." + expression_;
|
||
|
else
|
||
|
return expression_;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// **************************************************************************
|
||
|
|
||
|
|
||
|
// FIXME: we have two method to set VarItem: this one
|
||
|
// and updateValue below. That's bad, must have just one.
|
||
|
void VarItem::setText(int column, const QString &data)
|
||
|
{
|
||
|
QString strData=data;
|
||
|
|
||
|
if (column == ValueCol) {
|
||
|
QString oldValue(text(column));
|
||
|
if (!oldValue.isEmpty()) // Don't highlight new items
|
||
|
{
|
||
|
highlight_ = (oldValue != QString(data));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
QListViewItem::setText(column, strData);
|
||
|
}
|
||
|
|
||
|
void VarItem::clearHighlight()
|
||
|
{
|
||
|
highlight_ = false;
|
||
|
|
||
|
for(QListViewItem* child = firstChild();
|
||
|
child; child = child->nextSibling())
|
||
|
{
|
||
|
static_cast<VarItem*>(child)->clearHighlight();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// **************************************************************************
|
||
|
|
||
|
void VarItem::updateValue()
|
||
|
{
|
||
|
if (handleSpecialTypes())
|
||
|
{
|
||
|
// 1. Gdb never includes structures in output from -var-update
|
||
|
// 2. Even if it did, the internal state of object can be
|
||
|
// arbitrary complex and gdb can't detect if pretty-printed
|
||
|
// value remains the same.
|
||
|
// So, we need to reload value on each step.
|
||
|
updateUnconditionally_ = true;
|
||
|
return;
|
||
|
}
|
||
|
updateUnconditionally_ = false;
|
||
|
|
||
|
controller_->addCommand(
|
||
|
new GDBCommand(
|
||
|
"-var-evaluate-expression \"" + varobjName_ + "\"",
|
||
|
this,
|
||
|
&VarItem::valueDone,
|
||
|
true /* handle error */));
|
||
|
}
|
||
|
|
||
|
void VarItem::setValue(const QString& new_value)
|
||
|
{
|
||
|
controller_->addCommand(
|
||
|
new GDBCommand(QString("-var-assign \"%1\" %2").arg(varobjName_)
|
||
|
.arg(new_value)));
|
||
|
|
||
|
// And immediately reload it from gdb,
|
||
|
// so that it's display format is the one gdb uses,
|
||
|
// not the one user has typed. Otherwise, on the next
|
||
|
// step, the visible value might change and be highlighted
|
||
|
// as changed, which is bogus.
|
||
|
updateValue();
|
||
|
}
|
||
|
|
||
|
void VarItem::updateSpecialRepresentation(const QString& xs)
|
||
|
{
|
||
|
QString s(xs);
|
||
|
if (s[0] == '$')
|
||
|
{
|
||
|
int i = s.find('=');
|
||
|
if (i != -1)
|
||
|
s = s.mid(i+2);
|
||
|
}
|
||
|
|
||
|
// A hack to nicely display QStrings. The content of QString is unicode
|
||
|
// for for ASCII only strings we get ascii character mixed with \000.
|
||
|
// Remove those \000 now.
|
||
|
|
||
|
// This is not very nice, becuse we're doing this unconditionally
|
||
|
// and this method can be called twice: first with data that gdb sends
|
||
|
// for a variable, and second after we request the string data. In theory
|
||
|
// the data sent by gdb might contain \000 that should not be translated.
|
||
|
//
|
||
|
// What's even worse, ideally we should convert the string data from
|
||
|
// gdb into a QString again, handling all other escapes and composing
|
||
|
// one QChar from two characters from gdb. But to do that, we *should*
|
||
|
// now if the data if generic gdb value, and result of request for string
|
||
|
// data. Fixing is is for later.
|
||
|
s.replace( QRegExp("\\\\000|\\\\0"), "" );
|
||
|
|
||
|
// FIXME: for now, assume that all special representations are
|
||
|
// just strings.
|
||
|
|
||
|
s = GDBParser::getGDBParser()->undecorateValue(s);
|
||
|
|
||
|
setText(ValueCol, s);
|
||
|
// On the first stop, when VarItem was just created,
|
||
|
// don't show it in red.
|
||
|
if (oldSpecialRepresentationSet_)
|
||
|
highlight_ = (oldSpecialRepresentation_ != s);
|
||
|
else
|
||
|
highlight_ = false;
|
||
|
|
||
|
oldSpecialRepresentationSet_ = true;
|
||
|
oldSpecialRepresentation_ = s;
|
||
|
}
|
||
|
|
||
|
void VarItem::recreateLocallyMaybe()
|
||
|
{
|
||
|
controller_->addCommand(
|
||
|
new CliCommand(
|
||
|
QString("print /x &%1").arg(expression_),
|
||
|
this,
|
||
|
&VarItem::handleCurrentAddress,
|
||
|
true));
|
||
|
|
||
|
controller_->addCommand(
|
||
|
new CliCommand(
|
||
|
QString("whatis %1").arg(expression_),
|
||
|
this,
|
||
|
&VarItem::handleType));
|
||
|
}
|
||
|
|
||
|
void VarItem::recreate()
|
||
|
{
|
||
|
unhookFromGdb();
|
||
|
|
||
|
initialCreation_ = false;
|
||
|
createVarobj();
|
||
|
}
|
||
|
|
||
|
|
||
|
// **************************************************************************
|
||
|
|
||
|
void VarItem::setOpen(bool open)
|
||
|
{
|
||
|
QListViewItem::setOpen(open);
|
||
|
|
||
|
if (open && !childrenFetched_)
|
||
|
{
|
||
|
controller_->addCommand(new GDBCommand(
|
||
|
"-var-list-children \"" + varobjName_ + "\"",
|
||
|
this,
|
||
|
&VarItem::childrenDone));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool VarItem::handleSpecialTypes()
|
||
|
{
|
||
|
kdDebug(9012) << "handleSpecialTypes: " << originalValueType_ << "\n";
|
||
|
if (originalValueType_.isEmpty())
|
||
|
return false;
|
||
|
|
||
|
static QRegExp qstring("^(const)?[ ]*QString[ ]*&?$");
|
||
|
|
||
|
if (qstring.exactMatch(originalValueType_)) {
|
||
|
|
||
|
VariableTree* varTree = static_cast<VariableTree*>(listView());
|
||
|
if( !varTree->controller() )
|
||
|
return false;
|
||
|
varTree->controller()->addCommand(
|
||
|
new ResultlessCommand(QString("print $kdev_d=%1.d")
|
||
|
.arg(gdbExpression()),
|
||
|
true /* ignore error */));
|
||
|
|
||
|
if (varTree->controller()->qtVersion() >= 4)
|
||
|
varTree->controller()->addCommand(
|
||
|
new ResultlessCommand(QString("print $kdev_s=$kdev_d.size"),
|
||
|
true));
|
||
|
else
|
||
|
varTree->controller()->addCommand(
|
||
|
new ResultlessCommand(QString("print $kdev_s=$kdev_d.len"),
|
||
|
true));
|
||
|
|
||
|
varTree->controller()->addCommand(
|
||
|
new ResultlessCommand(
|
||
|
QString("print $kdev_s= ($kdev_s > 0)? ($kdev_s > 100 ? 200 : 2*$kdev_s) : 0"),
|
||
|
true));
|
||
|
|
||
|
if (varTree->controller()->qtVersion() >= 4)
|
||
|
varTree->controller()->addCommand(
|
||
|
new ValueSpecialRepresentationCommand(
|
||
|
this, "print ($kdev_s>0) ? (*((char*)&$kdev_d.data[0])@$kdev_s) : \"\""));
|
||
|
else
|
||
|
varTree->controller()->addCommand(
|
||
|
new ValueSpecialRepresentationCommand(
|
||
|
this, "print ($kdev_s>0) ? (*((char*)&$kdev_d.unicode[0])@$kdev_s) : \"\""));
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// **************************************************************************
|
||
|
|
||
|
VarItem::format_t VarItem::format() const
|
||
|
{
|
||
|
return format_;
|
||
|
}
|
||
|
|
||
|
void VarItem::setFormat(format_t f)
|
||
|
{
|
||
|
if (f == format_)
|
||
|
return;
|
||
|
|
||
|
format_ = f;
|
||
|
|
||
|
if (numChildren_)
|
||
|
{
|
||
|
// If variable has children, change format for children.
|
||
|
// - for structures, that's clearly right
|
||
|
// - for arrays, that's clearly right
|
||
|
// - for pointers, this can be confusing, but nobody ever wants to
|
||
|
// see the pointer in decimal!
|
||
|
for(QListViewItem* child = firstChild();
|
||
|
child; child = child->nextSibling())
|
||
|
{
|
||
|
static_cast<VarItem*>(child)->setFormat(f);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
controller_->addCommand(
|
||
|
new GDBCommand(QString("-var-set-format \"%1\" %2")
|
||
|
.arg(varobjName_).arg(varobjFormatName())));
|
||
|
|
||
|
updateValue();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
VarItem::format_t VarItem::formatFromGdbModifier(char c) const
|
||
|
{
|
||
|
format_t nf;
|
||
|
switch(c)
|
||
|
{
|
||
|
case 'n': // Not quite gdb modifier, but used in our UI.
|
||
|
nf = natural; break;
|
||
|
case 'x':
|
||
|
nf = hexadecimal; break;
|
||
|
case 'd':
|
||
|
nf = decimal; break;
|
||
|
case 'c':
|
||
|
nf = character; break;
|
||
|
case 't':
|
||
|
nf = binary; break;
|
||
|
default:
|
||
|
nf = natural; break;
|
||
|
}
|
||
|
return nf;
|
||
|
}
|
||
|
|
||
|
QString VarItem::varobjFormatName() const
|
||
|
{
|
||
|
switch(format_)
|
||
|
{
|
||
|
case natural:
|
||
|
return "natural";
|
||
|
break;
|
||
|
|
||
|
case hexadecimal:
|
||
|
return "hexadecimal";
|
||
|
break;
|
||
|
|
||
|
case decimal:
|
||
|
return "decimal";
|
||
|
break;
|
||
|
|
||
|
// Note: gdb does not support 'character' natively,
|
||
|
// so we'll generate appropriate representation
|
||
|
// ourselfs.
|
||
|
case character:
|
||
|
return "decimal";
|
||
|
break;
|
||
|
|
||
|
case binary:
|
||
|
return "binary";
|
||
|
break;
|
||
|
}
|
||
|
return "<undefined>";
|
||
|
}
|
||
|
|
||
|
|
||
|
// **************************************************************************
|
||
|
|
||
|
// Overridden to highlight the changed items
|
||
|
void VarItem::paintCell(QPainter *p, const QColorGroup &cg,
|
||
|
int column, int width, int align)
|
||
|
{
|
||
|
if ( !p )
|
||
|
return;
|
||
|
|
||
|
// Draw values in fixed font. For example, when there are several
|
||
|
// pointer variables, it's nicer if they are aligned -- it allows
|
||
|
// to easy see the diferrence between the pointers.
|
||
|
if (column == ValueCol)
|
||
|
{
|
||
|
p->setFont(KGlobalSettings::fixedFont());
|
||
|
}
|
||
|
|
||
|
if (!alive_)
|
||
|
{
|
||
|
/* Draw this as disabled. */
|
||
|
QListViewItem::paintCell(p, varTree()->QWidget::palette().disabled(),
|
||
|
column, width, align);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (column == ValueCol && highlight_)
|
||
|
{
|
||
|
QColorGroup hl_cg( cg.foreground(), cg.background(), cg.light(),
|
||
|
cg.dark(), cg.mid(), red, cg.base());
|
||
|
QListViewItem::paintCell( p, hl_cg, column, width, align );
|
||
|
} else
|
||
|
QListViewItem::paintCell( p, cg, column, width, align );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
VariableTree* VarItem::varTree() const
|
||
|
{
|
||
|
return static_cast<VariableTree*>(listView());
|
||
|
}
|
||
|
|
||
|
void VarItem::unhookFromGdb()
|
||
|
{
|
||
|
// Unhook children first, so that child varitems are deleted
|
||
|
// before parent. Strictly speaking, we can avoid calling
|
||
|
// -var-delete on child varitems, but that's a bit cheesy,
|
||
|
for(QListViewItem* child = firstChild();
|
||
|
child; child = child->nextSibling())
|
||
|
{
|
||
|
static_cast<VarItem*>(child)->unhookFromGdb();
|
||
|
}
|
||
|
|
||
|
alive_ = false;
|
||
|
childrenFetched_ = false;
|
||
|
|
||
|
emit varobjNameChange(varobjName_, "");
|
||
|
|
||
|
if (!controller_->stateIsOn(s_dbgNotStarted) && !varobjName_.isEmpty())
|
||
|
{
|
||
|
controller_->addCommand(
|
||
|
new GDBCommand(
|
||
|
QString("-var-delete \"%1\"").arg(varobjName_)));
|
||
|
}
|
||
|
|
||
|
varobjName_ = "";
|
||
|
}
|
||
|
|
||
|
// **************************************************************************
|
||
|
|
||
|
QString VarItem::tipText() const
|
||
|
{
|
||
|
const unsigned int maxTooltipSize = 70;
|
||
|
QString tip = text( ValueCol );
|
||
|
|
||
|
if (tip.length() > maxTooltipSize)
|
||
|
tip = tip.mid(0, maxTooltipSize - 1 ) + " [...]";
|
||
|
|
||
|
if (!tip.isEmpty())
|
||
|
tip += "\n" + originalValueType_;
|
||
|
|
||
|
return tip;
|
||
|
}
|
||
|
|
||
|
bool VarItem::updateUnconditionally() const
|
||
|
{
|
||
|
return updateUnconditionally_;
|
||
|
}
|
||
|
|
||
|
bool VarItem::isAlive() const
|
||
|
{
|
||
|
return alive_;
|
||
|
}
|
||
|
|
||
|
|
||
|
// **************************************************************************
|
||
|
// **************************************************************************
|
||
|
// **************************************************************************
|
||
|
|
||
|
VarFrameRoot::VarFrameRoot(VariableTree *parent, int frameNo, int threadNo)
|
||
|
: TrimmableItem (parent),
|
||
|
needLocals_(false),
|
||
|
frameNo_(frameNo),
|
||
|
threadNo_(threadNo),
|
||
|
currentFrameBase((unsigned long long)-1),
|
||
|
currentFrameCodeAddress((unsigned long long)-1)
|
||
|
{
|
||
|
setExpandable(true);
|
||
|
}
|
||
|
|
||
|
// **************************************************************************
|
||
|
|
||
|
VarFrameRoot::~VarFrameRoot()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
void VarFrameRoot::setOpen(bool open)
|
||
|
{
|
||
|
bool frameOpened = ( isOpen()==false && open==true );
|
||
|
QListViewItem::setOpen(open);
|
||
|
|
||
|
if (frameOpened && needLocals_)
|
||
|
{
|
||
|
needLocals_ = false;
|
||
|
VariableTree* parent = static_cast<VariableTree*>(listView());
|
||
|
parent->updateCurrentFrame();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// **************************************************************************
|
||
|
|
||
|
bool VarFrameRoot::matchDetails(int frameNo, int threadNo)
|
||
|
{
|
||
|
return frameNo == frameNo_ && threadNo == threadNo_;
|
||
|
}
|
||
|
|
||
|
void VarFrameRoot::setDirty()
|
||
|
{
|
||
|
needLocals_ = true;
|
||
|
}
|
||
|
|
||
|
// **************************************************************************
|
||
|
// **************************************************************************
|
||
|
// **************************************************************************
|
||
|
// **************************************************************************
|
||
|
|
||
|
WatchRoot::WatchRoot(VariableTree *parent)
|
||
|
: TrimmableItem(parent)
|
||
|
{
|
||
|
setText(0, i18n("Watch"));
|
||
|
setOpen(true);
|
||
|
}
|
||
|
|
||
|
// **************************************************************************
|
||
|
|
||
|
WatchRoot::~WatchRoot()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
// **************************************************************************
|
||
|
// **************************************************************************
|
||
|
// **************************************************************************
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
#include "variablewidget.moc"
|
||
|
|