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
// **************************************************************************
|
|
// begin : Sun Aug 8 1999
|
|
// copyright : (C) 1999 by John Birch
|
|
// email : jbb@tdevelop.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 <tdeversion.h>
|
|
#include <kiconloader.h>
|
|
|
|
#include <tqheader.h>
|
|
#include <tqlabel.h>
|
|
#include <tqlayout.h>
|
|
#include <tqhbox.h>
|
|
#include <tqpainter.h>
|
|
#include <tqpushbutton.h>
|
|
#include <tqregexp.h>
|
|
#include <tqcursor.h>
|
|
#include <tqwhatsthis.h>
|
|
#include <klocale.h>
|
|
|
|
#include <tqpoint.h>
|
|
#include <tqclipboard.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,
|
|
TQWidget *parent, const char *name)
|
|
: TQWidget(parent, name)
|
|
{
|
|
setIcon(SmallIcon("math_brace"));
|
|
setCaption(i18n("Variable Tree"));
|
|
|
|
varTree_ = new VariableTree(this, controller, breakpointWidget);
|
|
|
|
watchVarEditor_ = new KHistoryCombo( this,
|
|
"var-to-watch editor");
|
|
|
|
TQHBoxLayout* buttons = new TQHBoxLayout();
|
|
|
|
buttons->addStretch();
|
|
|
|
TQPushButton *evalButton = new TQPushButton(i18n("&Evaluate"), this );
|
|
buttons->addWidget(evalButton);
|
|
|
|
TQPushButton *addButton = new TQPushButton(i18n("&Watch"), this );
|
|
buttons->addWidget(addButton);
|
|
|
|
TQVBoxLayout *topLayout = new TQVBoxLayout(this, 2);
|
|
topLayout->addWidget(varTree_, 10);
|
|
topLayout->addWidget(watchVarEditor_);
|
|
topLayout->addItem(buttons);
|
|
|
|
|
|
connect( addButton, TQT_SIGNAL(clicked()), TQT_SLOT(slotAddWatchVariable()) );
|
|
connect( evalButton, TQT_SIGNAL(clicked()), TQT_SLOT(slotEvaluateExpression()) );
|
|
|
|
connect( watchVarEditor_, TQT_SIGNAL(returnPressed()),
|
|
TQT_SLOT(slotEvaluateExpression()) );
|
|
|
|
connect(controller, TQT_SIGNAL(event(GDBController::event_t)),
|
|
varTree_, TQT_SLOT(slotEvent(GDBController::event_t)));
|
|
|
|
|
|
// Setup help items.
|
|
|
|
TQWhatsThis::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."));
|
|
|
|
TQWhatsThis::add(watchVarEditor_,
|
|
i18n("<b>Expression entry</b>"
|
|
"<p>Type in expression to evaluate."));
|
|
|
|
TQWhatsThis::add(evalButton,
|
|
i18n("Evaluate the expression."));
|
|
|
|
TQWhatsThis::add(addButton,
|
|
i18n("Evaluate the expression and "
|
|
"auto-update the value when stepping."));
|
|
}
|
|
|
|
void VariableWidget::slotAddWatchVariable()
|
|
{
|
|
// TQString watchVar(watchVarEntry_->text());
|
|
TQString watchVar(watchVarEditor_->currentText());
|
|
if (!watchVar.isEmpty())
|
|
{
|
|
slotAddWatchVariable(watchVar);
|
|
}
|
|
}
|
|
|
|
// **************************************************************************
|
|
|
|
void VariableWidget::slotAddWatchVariable(const TQString &ident)
|
|
{
|
|
if (!ident.isEmpty())
|
|
{
|
|
watchVarEditor_->addToHistory(ident);
|
|
varTree_->slotAddWatchVariable(ident);
|
|
watchVarEditor_->clearEdit();
|
|
}
|
|
}
|
|
|
|
void VariableWidget::slotEvaluateExpression()
|
|
{
|
|
TQString exp(watchVarEditor_->currentText());
|
|
if (!exp.isEmpty())
|
|
{
|
|
slotEvaluateExpression(exp);
|
|
}
|
|
}
|
|
|
|
void VariableWidget::slotEvaluateExpression(const TQString &ident)
|
|
{
|
|
if (!ident.isEmpty())
|
|
{
|
|
watchVarEditor_->addToHistory(ident);
|
|
varTree_->slotEvaluateExpression(ident);
|
|
watchVarEditor_->clearEdit();
|
|
}
|
|
}
|
|
|
|
// **************************************************************************
|
|
|
|
void VariableWidget::focusInEvent(TQFocusEvent */*e*/)
|
|
{
|
|
varTree_->setFocus();
|
|
}
|
|
|
|
|
|
|
|
|
|
// **************************************************************************
|
|
// **************************************************************************
|
|
// **************************************************************************
|
|
|
|
VariableTree::VariableTree(VariableWidget *parent,
|
|
GDBController* controller,
|
|
GDBBreakpointWidget* breakpointWidget,
|
|
const char *name)
|
|
: KListView(parent, name),
|
|
TQToolTip( viewport() ),
|
|
controller_(controller),
|
|
breakpointWidget_(breakpointWidget),
|
|
activeFlag_(0),
|
|
recentExpressions_(0),
|
|
currentFrameItem(0),
|
|
activePopup_(0)
|
|
{
|
|
setRootIsDecorated(true);
|
|
setAllColumnsShowFocus(true);
|
|
setSorting(-1);
|
|
TQListView::setSelectionMode(TQListView::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, TQT_SIGNAL(contextMenu(KListView*, TQListViewItem*, const TQPoint&)),
|
|
TQT_SLOT(slotContextMenu(KListView*, TQListViewItem*)) );
|
|
connect( this, TQT_SIGNAL(itemRenamed( TQListViewItem*, int, const TQString&)),
|
|
this, TQT_SLOT(slotItemRenamed( TQListViewItem*, int, const TQString&)));
|
|
}
|
|
|
|
// **************************************************************************
|
|
|
|
VariableTree::~VariableTree()
|
|
{
|
|
}
|
|
|
|
// **************************************************************************
|
|
|
|
void VariableTree::slotContextMenu(KListView *, TQListViewItem *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(TQt::Key_N, idNatural);
|
|
idHex = format.insertItem(i18n("Hexadecimal"),
|
|
(int)VarItem::hexadecimal);
|
|
format.setAccel(TQt::Key_X, idHex);
|
|
idDecimal = format.insertItem(i18n("Decimal"),
|
|
(int)VarItem::decimal);
|
|
format.setAccel(TQt::Key_D, idDecimal);
|
|
idCharacter = format.insertItem(i18n("Character"),
|
|
(int)VarItem::character);
|
|
format.setAccel(TQt::Key_C, idCharacter);
|
|
idBinary = format.insertItem(i18n("Binary"),
|
|
(int)VarItem::binary);
|
|
format.setAccel(TQt::Key_T, idBinary);
|
|
|
|
|
|
format.setItemChecked((int)(var->format()), true);
|
|
|
|
int id = popup.insertItem(i18n("Format"), &format);
|
|
MAYBE_DISABLE(id);
|
|
}
|
|
|
|
|
|
TQListViewItem* 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(TQt::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(TQt::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(TQt::CTRL + TQt::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(
|
|
TQString("-data-evaluate-expression &%1")
|
|
.arg(var->gdbExpression()),
|
|
this,
|
|
&VariableTree::handleAddressComputed,
|
|
true /*handles error*/));
|
|
|
|
|
|
int res = popup.exec(TQCursor::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(TQCursor::pos());
|
|
|
|
if (res == idRemove)
|
|
{
|
|
delete recentExpressions_;
|
|
recentExpressions_ = 0;
|
|
}
|
|
else if (res == idReevaluate)
|
|
{
|
|
for(TQListViewItem* 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.
|
|
TQListViewItem *child = firstChild();
|
|
|
|
while (child) {
|
|
TQListViewItem *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(TQListViewItem* child = recentExpressions_->firstChild();
|
|
child; child = child->nextSibling())
|
|
{
|
|
static_cast<VarItem*>(child)->unhookFromGdb();
|
|
}
|
|
}
|
|
|
|
if (WatchRoot* w = findWatch())
|
|
{
|
|
for(TQListViewItem* 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(TQString("-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 TQString &watchVar)
|
|
{
|
|
VarItem *varItem = 0;
|
|
varItem = new VarItem(findWatch(), watchVar);
|
|
}
|
|
|
|
void VariableTree::slotEvaluateExpression(const TQString &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);
|
|
}
|
|
|
|
// **************************************************************************
|
|
|
|
TQListViewItem *VariableTree::findRoot(TQListViewItem *item) const
|
|
{
|
|
while (item->parent())
|
|
item = item->parent();
|
|
|
|
return item;
|
|
}
|
|
|
|
// **************************************************************************
|
|
|
|
VarFrameRoot *VariableTree::findFrame(int frameNo, int threadNo) const
|
|
{
|
|
TQListViewItem *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()
|
|
{
|
|
TQListViewItem *sibling = firstChild();
|
|
|
|
while (sibling) {
|
|
if (WatchRoot *watch = dynamic_cast<WatchRoot*> (sibling))
|
|
return watch;
|
|
|
|
sibling = sibling->nextSibling();
|
|
}
|
|
|
|
return new WatchRoot(this);
|
|
}
|
|
|
|
// **************************************************************************
|
|
|
|
TQListViewItem *VariableTree::lastChild() const
|
|
{
|
|
TQListViewItem *child = firstChild();
|
|
if (child)
|
|
while (TQListViewItem *nextChild = child->nextSibling())
|
|
child = nextChild;
|
|
|
|
return child;
|
|
}
|
|
|
|
// **************************************************************************
|
|
|
|
void VariableTree::maybeTip(const TQPoint &p)
|
|
{
|
|
VarItem * item = dynamic_cast<VarItem*>( itemAt( p ) );
|
|
if ( item )
|
|
{
|
|
TQRect r = itemRect( item );
|
|
if ( r.isValid() )
|
|
tip( r, item->tipText() );
|
|
}
|
|
}
|
|
|
|
class ValueSpecialRepresentationCommand : public TQObject, public CliCommand
|
|
{
|
|
public:
|
|
ValueSpecialRepresentationCommand(VarItem* item, const TQString& command)
|
|
: CliCommand(command.ascii(),
|
|
this,
|
|
&ValueSpecialRepresentationCommand::handleReply,
|
|
true),
|
|
item_(item)
|
|
{}
|
|
|
|
private:
|
|
|
|
VarItem* item_;
|
|
|
|
void handleReply(const TQValueVector<TQString>& lines)
|
|
{
|
|
TQString s;
|
|
for(unsigned i = 1; i < lines.count(); ++i)
|
|
s += lines[i];
|
|
item_->updateSpecialRepresentation(s.local8Bit());
|
|
}
|
|
};
|
|
|
|
void VariableTree::slotVarobjNameChanged(
|
|
const TQString& from, const TQString& 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)
|
|
{
|
|
TQString 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 TQValueVector<TQString>& lines)
|
|
{
|
|
//kdDebug(9012) << "localAddresses: " << lines[1] << "\n";
|
|
|
|
TQString 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 TQRegExp frame_base_rx("frame at 0x([0-9a-fA-F]*)");
|
|
static TQRegExp 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 = " << TQString::number(new_frame_base, 16)
|
|
<< " code = " << TQString::number(new_code_address, 16)
|
|
<< "\n";
|
|
kdDebug(9012) << "Previous frame " <<
|
|
TQString::number(frame->currentFrameBase, 16)
|
|
<< " code = " << TQString::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.
|
|
TQListViewItem* child = frame->firstChild();
|
|
TQListViewItem* next;
|
|
for(; child; child = next)
|
|
{
|
|
next = child->nextSibling();
|
|
delete child;
|
|
}
|
|
}
|
|
|
|
setUpdatesEnabled(false);
|
|
|
|
std::set<TQListViewItem*> alive;
|
|
|
|
for(unsigned i = 0; i < locals_and_arguments.size(); ++i)
|
|
{
|
|
TQString name = locals_and_arguments[i];
|
|
|
|
// See if we've got VarItem for this one already.
|
|
VarItem* var = 0;
|
|
for(TQListViewItem *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(TQListViewItem* child = frame->firstChild(); child;)
|
|
{
|
|
TQListViewItem* current = child;
|
|
child = current->nextSibling();
|
|
if (!alive.count(current))
|
|
delete current;
|
|
else
|
|
static_cast<VarItem*>(current)->recreateLocallyMaybe();
|
|
}
|
|
|
|
for(TQListViewItem* 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<TQString> names_to_update;
|
|
|
|
for(unsigned i = 0; i < changed.size(); ++i)
|
|
{
|
|
const GDBMI::Value& c = changed[i];
|
|
|
|
TQString name = c["name"].literal();
|
|
if (c.hasField("in_scope") && c["in_scope"].literal() == "false")
|
|
continue;
|
|
|
|
names_to_update.insert(name);
|
|
}
|
|
|
|
TQMap<TQString, 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 TQValueVector<TQString>& lines)
|
|
{
|
|
static TQRegExp r("(\\$[0-9]+)");
|
|
if (lines.size() >= 2)
|
|
{
|
|
int i = r.search(lines[1]);
|
|
if (i == 0)
|
|
{
|
|
controller_->addCommand(
|
|
new GDBCommand(TQString("-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 TQString.
|
|
// 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(TQListViewItem* item, int col, const TQString& text)
|
|
{
|
|
if (col == ValueCol)
|
|
{
|
|
VarItem* v = dynamic_cast<VarItem*>(item);
|
|
Q_ASSERT(v);
|
|
if (v)
|
|
{
|
|
v->setValue(text);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void VariableTree::keyPressEvent(TQKeyEvent* e)
|
|
{
|
|
if (VarItem* item = dynamic_cast<VarItem*>(currentItem()))
|
|
{
|
|
TQString text = e->text();
|
|
|
|
if (text == "n" || text == "x" || text == "d" || text == "c"
|
|
|| text == "t")
|
|
{
|
|
item->setFormat(
|
|
item->formatFromGdbModifier(text[0].latin1()));
|
|
}
|
|
|
|
if (e->key() == TQt::Key_Delete)
|
|
{
|
|
TQListViewItem* root = findRoot(item);
|
|
|
|
if (dynamic_cast<WatchRoot*>(root) || root == recentExpressions_)
|
|
{
|
|
delete item;
|
|
}
|
|
}
|
|
|
|
if (e->key() == TQt::Key_C && e->state() == TQt::ControlButton)
|
|
{
|
|
copyToClipboard(item);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void VariableTree::copyToClipboard(TQListViewItem* item)
|
|
{
|
|
TQClipboard *qb = KApplication::clipboard();
|
|
TQString text = item->text( 1 );
|
|
|
|
qb->setText( text, TQClipboard::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(TQPainter *p, const TQColorGroup &cg,
|
|
int column, int width, int align)
|
|
{
|
|
if ( !p )
|
|
return;
|
|
// make toplevel item (watch and frame items) names bold
|
|
if (column == 0 && !parent())
|
|
{
|
|
TQFont f = p->font();
|
|
f.setBold(true);
|
|
p->setFont(f);
|
|
}
|
|
TQListViewItem::paintCell( p, cg, column, width, align );
|
|
}
|
|
|
|
TQListViewItem *TrimmableItem::lastChild() const
|
|
{
|
|
TQListViewItem *child = firstChild();
|
|
if (child)
|
|
while (TQListViewItem *nextChild = child->nextSibling())
|
|
child = nextChild;
|
|
|
|
return child;
|
|
}
|
|
|
|
// **************************************************************************
|
|
// **************************************************************************
|
|
// **************************************************************************
|
|
|
|
int VarItem::varobjIndex = 0;
|
|
|
|
VarItem::VarItem(TrimmableItem *parent,
|
|
const TQString& 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, TQT_SIGNAL(varobjNameChange(const TQString&, const TQString&)),
|
|
varTree(),
|
|
TQT_SLOT(slotVarobjNameChanged(const TQString&, const TQString&)));
|
|
|
|
|
|
// 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 TQRegExp 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, TQT_SIGNAL(varobjNameChange(const TQString&, const TQString&)),
|
|
varTree(),
|
|
TQT_SLOT(slotVarobjNameChanged(const TQString&, const TQString&)));
|
|
|
|
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()
|
|
{
|
|
TQString old = varobjName_;
|
|
varobjName_ = TQString("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(
|
|
TQString("print %1").arg(expression_),
|
|
this,
|
|
&VarItem::handleCliPrint));
|
|
}
|
|
else
|
|
{
|
|
controller_->addCommand(
|
|
new CliCommand(
|
|
TQString("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(TQString("-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);
|
|
|
|
TQString oldType = originalValueType_;
|
|
originalValueType_ = r["type"].literal();
|
|
if (!oldType.isEmpty() && oldType != originalValueType_)
|
|
{
|
|
// Type changed, the children might be no longer valid,
|
|
// so delete them.
|
|
for(TQListViewItem* child = firstChild(); child; )
|
|
{
|
|
TQListViewItem* 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 TQString& name)
|
|
{
|
|
if (varobjName_ != name)
|
|
emit varobjNameChange(varobjName_, name);
|
|
|
|
varobjName_ = name;
|
|
|
|
if (format_ != natural)
|
|
{
|
|
controller_->addCommand(
|
|
new GDBCommand(TQString("-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")
|
|
{
|
|
TQString s = GDBParser::getGDBParser()->undecorateValue(
|
|
r["value"].literal());
|
|
|
|
if (format_ == character)
|
|
{
|
|
TQString 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 TQRegExp r("^[01]+$");
|
|
int i = r.search(s);
|
|
if (i == 0)
|
|
{
|
|
TQString 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
|
|
{
|
|
TQString 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)
|
|
{
|
|
TQString 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)
|
|
{
|
|
TQString exp = children[i]["exp"].literal();
|
|
// For artificial accessibility nodes,
|
|
// fetch their children.
|
|
if (exp == "public" || exp == "protected" || exp == "private")
|
|
{
|
|
TQString 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(TQListViewItem* 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 TQValueVector<TQString>& lines)
|
|
{
|
|
lastObtainedAddress_ = "";
|
|
if (lines.count() > 1)
|
|
{
|
|
static TQRegExp 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 TQValueVector<TQString>& 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 TQRegExp 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();
|
|
}
|
|
}
|
|
|
|
TQString 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(TQListViewItem* child = firstChild();
|
|
child; child = child->nextSibling())
|
|
{
|
|
static_cast<VarItem*>(child)->setAliveRecursively(enable);
|
|
}
|
|
}
|
|
|
|
|
|
VarItem::~VarItem()
|
|
{
|
|
unhookFromGdb();
|
|
}
|
|
|
|
TQString 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 TQString &data)
|
|
{
|
|
TQString strData=data;
|
|
|
|
if (column == ValueCol) {
|
|
TQString oldValue(text(column));
|
|
if (!oldValue.isEmpty()) // Don't highlight new items
|
|
{
|
|
highlight_ = (oldValue != TQString(data));
|
|
}
|
|
}
|
|
|
|
TQListViewItem::setText(column, strData);
|
|
}
|
|
|
|
void VarItem::clearHighlight()
|
|
{
|
|
highlight_ = false;
|
|
|
|
for(TQListViewItem* 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 TQString& new_value)
|
|
{
|
|
controller_->addCommand(
|
|
new GDBCommand(TQString("-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 TQString& xs)
|
|
{
|
|
TQString s(xs);
|
|
if (s[0] == '$')
|
|
{
|
|
int i = s.find('=');
|
|
if (i != -1)
|
|
s = s.mid(i+2);
|
|
}
|
|
|
|
// A hack to nicely display TQStrings. The content of TQString 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 TQString again, handling all other escapes and composing
|
|
// one TQChar 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( TQRegExp("\\\\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(
|
|
TQString("print /x &%1").arg(expression_),
|
|
this,
|
|
&VarItem::handleCurrentAddress,
|
|
true));
|
|
|
|
controller_->addCommand(
|
|
new CliCommand(
|
|
TQString("whatis %1").arg(expression_),
|
|
this,
|
|
&VarItem::handleType));
|
|
}
|
|
|
|
void VarItem::recreate()
|
|
{
|
|
unhookFromGdb();
|
|
|
|
initialCreation_ = false;
|
|
createVarobj();
|
|
}
|
|
|
|
|
|
// **************************************************************************
|
|
|
|
void VarItem::setOpen(bool open)
|
|
{
|
|
TQListViewItem::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 TQRegExp qstring("^(const)?[ ]*TQString[ ]*&?$");
|
|
|
|
if (qstring.exactMatch(originalValueType_)) {
|
|
|
|
VariableTree* varTree = static_cast<VariableTree*>(listView());
|
|
if( !varTree->controller() )
|
|
return false;
|
|
varTree->controller()->addCommand(
|
|
new ResultlessCommand(TQString("print $kdev_d=%1.d")
|
|
.arg(gdbExpression()),
|
|
true /* ignore error */));
|
|
|
|
if (varTree->controller()->qtVersion() >= 4)
|
|
varTree->controller()->addCommand(
|
|
new ResultlessCommand(TQString("print $kdev_s=$kdev_d.size"),
|
|
true));
|
|
else
|
|
varTree->controller()->addCommand(
|
|
new ResultlessCommand(TQString("print $kdev_s=$kdev_d.len"),
|
|
true));
|
|
|
|
varTree->controller()->addCommand(
|
|
new ResultlessCommand(
|
|
TQString("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(TQListViewItem* child = firstChild();
|
|
child; child = child->nextSibling())
|
|
{
|
|
static_cast<VarItem*>(child)->setFormat(f);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
controller_->addCommand(
|
|
new GDBCommand(TQString("-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;
|
|
}
|
|
|
|
TQString 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(TQPainter *p, const TQColorGroup &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. */
|
|
TQListViewItem::paintCell(p, varTree()->TQWidget::palette().disabled(),
|
|
column, width, align);
|
|
}
|
|
else
|
|
{
|
|
if (column == ValueCol && highlight_)
|
|
{
|
|
TQColorGroup hl_cg( cg.foreground(), cg.background(), cg.light(),
|
|
cg.dark(), cg.mid(), red, cg.base());
|
|
TQListViewItem::paintCell( p, hl_cg, column, width, align );
|
|
} else
|
|
TQListViewItem::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(TQListViewItem* 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(
|
|
TQString("-var-delete \"%1\"").arg(varobjName_)));
|
|
}
|
|
|
|
varobjName_ = "";
|
|
}
|
|
|
|
// **************************************************************************
|
|
|
|
TQString VarItem::tipText() const
|
|
{
|
|
const unsigned int maxTooltipSize = 70;
|
|
TQString 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 );
|
|
TQListViewItem::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"
|
|
|