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.
kdbg/kdbg/exprwnd.cpp

832 lines
21 KiB

/*
* Copyright Johannes Sixt
* This file is licensed under the GNU General Public License Version 2.
* See the file COPYING in the toplevel directory of the source directory.
*/
#include "exprwnd.h"
#include "exprwnd.moc"
#include "typetable.h"
#include <tqstringlist.h>
#include <tqpainter.h>
#include <tqscrollbar.h>
#include <tdeapplication.h>
#include <kiconloader.h> /* icons */
#include <tdelocale.h> /* i18n */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "mydebug.h"
VarTree::VarTree(VarTree* parent, TQListViewItem* after, ExprValue* v) :
TQListViewItem(parent, after),
m_varKind(v->m_varKind),
m_nameKind(v->m_nameKind),
m_type(0),
m_exprIndex(0),
m_exprIndexUseGuard(false),
m_baseValue(v->m_value),
m_baseChanged(false),
m_structChanged(false)
{
TQListViewItem::setText(0, v->m_name);
updateValueText();
setExpandable(m_varKind == VarTree::VKpointer);
setOpen(v->m_initiallyExpanded);
}
VarTree::VarTree(ExprWnd* parent, TQListViewItem* after, const TQString& name) :
TQListViewItem(parent, after),
m_varKind(VKsimple),
m_nameKind(VarTree::NKplain),
m_type(0),
m_exprIndex(0),
m_exprIndexUseGuard(false),
m_baseChanged(false),
m_structChanged(false)
{
TQListViewItem::setText(0, name);
}
VarTree::~VarTree()
{
}
void VarTree::paintCell(TQPainter* p, const TQColorGroup& cg, int column, int width, int align)
{
if (column == 1 && (m_baseChanged || m_structChanged)) {
TQColorGroup cgChg = cg;
cgChg.setColor(TQColorGroup::Text, TQt::red);
TQListViewItem::paintCell(p, cgChg, column, width, align);
} else {
TQListViewItem::paintCell(p, cg, column, width, align);
}
}
TQString VarTree::computeExpr() const
{
// top-level items are special
if (isToplevelExpr())
return getText();
// get parent expr
VarTree* par = static_cast<VarTree*>(parent());
TQString parentExpr = par->computeExpr();
// skip this item's name if it is a base class or anonymous struct or union
if (m_nameKind == NKtype || m_nameKind == NKanonymous) {
return parentExpr;
}
/* augment by this item's text */
TQString result;
/* if this is an address, dereference it */
if (m_nameKind == NKaddress) {
ASSERT(par->m_varKind == VKpointer);
result = "*" + parentExpr;
return result;
}
switch (par->m_varKind) {
case VKarray:
{
TQString index = getText();
int i = 1;
// skip past the index
while (index[i].isDigit())
i++;
/*
* Some array indices are actually ranges due to repeated array
* values. We use the first index in these cases.
*/
if (index[i] != ']') {
// remove second index
index.remove(i, index.length()-i-1);
}
result = "(" + parentExpr + ")" + index;
}
break;
case VKstruct:
result = "(" + parentExpr + ")." + getText();
break;
case VKsimple: /* parent can't be simple */
case VKpointer: /* handled in NKaddress */
case VKdummy: /* can't occur at all */
ASSERT(false);
result = parentExpr; /* paranoia */
break;
}
return result;
}
bool VarTree::isToplevelExpr() const
{
return parent() == 0;
}
bool VarTree::isAncestorEq(const VarTree* child) const
{
const TQListViewItem* c = child;
while (c != 0 && c != this) {
c = c->parent();
}
return c != 0;
}
bool VarTree::updateValue(const TQString& newValue)
{
// check whether the value changed
bool prevValueChanged = m_baseChanged;
if ((m_baseChanged = m_baseValue != newValue)) {
m_baseValue = newValue;
updateValueText();
}
/*
* We must repaint the cell if the value changed. If it did not change,
* we still must repaint the cell if the value changed previously,
* because the color of the display must be changed (from red to
* black).
*/
return m_baseChanged || prevValueChanged;
}
bool VarTree::updateStructValue(const TQString& newValue)
{
// check whether the value changed
bool prevValueChanged = m_structChanged;
if ((m_structChanged = m_structValue != newValue)) {
m_structValue = newValue;
updateValueText();
}
/*
* We must repaint the cell if the value changed. If it did not change,
* we still must repaint the cell if the value changed previously,
* because the color of the display must be changed (from red to
* black).
*/
return m_structChanged || prevValueChanged;
}
void VarTree::updateValueText()
{
if (m_baseValue.isEmpty()) {
TQListViewItem::setText(1, m_structValue);
} else if (m_structValue.isEmpty()) {
TQListViewItem::setText(1, m_baseValue);
} else {
TQListViewItem::setText(1, m_baseValue + " " + m_structValue);
}
}
void VarTree::inferTypesOfChildren(ProgramTypeTable& typeTable)
{
/*
* Type inference works like this: We use type information of those
* children that have a type name in their name (base classes) or in
* their value (pointers)
*/
// first recurse children
VarTree* child = firstChild();
while (child != 0) {
child->inferTypesOfChildren(typeTable);
child = child->nextSibling();
}
// if this is a pointer, get the type from the value (less the pointer)
if (m_varKind == VKpointer) {
if (isWcharT())
{
/*
* wchart_t pointers must be treated as struct, because the array
* of characters is printed similar to how TQStrings are decoded.
*/
m_varKind = VKstruct;
setExpandable(false);
}
// don't know how to do this cleanly
} else if (m_varKind == VKstruct) {
// check if this is a base class part
if (m_nameKind == NKtype) {
const TQString& typeName =
getText().mid(1, getText().length()-2); // strip < and >
m_type = typeTable.lookup(typeName);
/* if we don't have a type yet, get it from the base class */
if (m_type == 0) {
m_type = inferTypeFromBaseClass();
/*
* If there is a known type now, it is the one from the
* first base class whose type we know.
*/
}
/*
* If we still don't have a type, the type is really unknown.
*/
if (m_type == 0) {
m_type = TypeInfo::unknownType();
}
} // else
/*
* This is not a base class part. We don't assign a type so
* that later we can ask gdb.
*/
}
}
// the value contains the pointer type in parenthesis
bool VarTree::isWcharT() const
{
return value().startsWith("(const wchar_t *)") ||
value().startsWith("(wchar_t *)");
}
/*
* Get the type of the first base class whose type we know.
*/
TypeInfo* VarTree::inferTypeFromBaseClass()
{
if (m_varKind == VKstruct) {
VarTree* child = firstChild();
while (child != 0 &&
// only check base class parts (i.e. type names)
child->m_nameKind == NKtype)
{
if (child->m_type != 0 &&
child->m_type != TypeInfo::unknownType())
{
// got a type!
return child->m_type;
}
child = child->nextSibling();
}
}
return 0;
}
ExprValue::ExprValue(const TQString& name, VarTree::NameKind aKind) :
m_name(name),
m_varKind(VarTree::VKsimple),
m_nameKind(aKind),
m_child(0),
m_next(0),
m_initiallyExpanded(false)
{
}
ExprValue::~ExprValue()
{
delete m_child;
delete m_next;
}
void ExprValue::appendChild(ExprValue* newChild)
{
if (m_child == 0) {
m_child = newChild;
} else {
// walk chain of children to find the last one
ExprValue* last = m_child;
while (last->m_next != 0)
last = last->m_next;
last->m_next = newChild;
}
newChild->m_next = 0; // just to be sure
}
int ExprValue::childCount() const
{
int i = 0;
ExprValue* c = m_child;
while (c) {
++i;
c = c->m_next;
}
return i;
}
ExprWnd::ExprWnd(TQWidget* parent, const TQString& colHeader, const char* name) :
TQListView(parent, name),
m_edit(0)
{
addColumn(colHeader);
addColumn(i18n("Value"));
setSorting(-1); // do not sort items
setColumnWidthMode(0, Manual);
setColumnWidthMode(1, Maximum);
setRootIsDecorated(true);
setAllColumnsShowFocus(true);
m_pixPointer = UserIcon("pointer.xpm");
if (m_pixPointer.isNull())
TRACE("Can't load pointer.xpm");
}
ExprWnd::~ExprWnd()
{
}
TQStringList ExprWnd::exprList() const
{
TQStringList exprs;
VarTree* item;
for (item = firstChild(); item != 0; item = item->nextSibling()) {
exprs.append(item->getText());
}
return exprs;
}
VarTree* ExprWnd::insertExpr(ExprValue* expr, ProgramTypeTable& typeTable)
{
// append a new dummy expression
VarTree* last = 0; // last top-level item
for (VarTree* i = firstChild(); i != 0; i = i->nextSibling()) {
last = i;
}
VarTree* display = new VarTree(this, last, expr->m_name);
// replace it right away
updateExpr(display, expr, typeTable);
return display;
}
void ExprWnd::updateExpr(ExprValue* expr, ProgramTypeTable& typeTable)
{
// search the root variable
VarTree* item = firstChild();
while (item != 0 && item->getText() != expr->m_name)
item = item->nextSibling();
if (item == 0) {
return;
}
// now update it
updateExprRec(item, expr, typeTable);
collectUnknownTypes(item);
}
void ExprWnd::updateExpr(VarTree* display, ExprValue* newValues, ProgramTypeTable& typeTable)
{
updateExprRec(display, newValues, typeTable);
collectUnknownTypes(display);
}
/*
* returns true if there's a visible change
*/
void ExprWnd::updateExprRec(VarTree* display, ExprValue* newValues, ProgramTypeTable& typeTable)
{
bool isExpanded = display->isOpen();
/*
* If we are updating a pointer without children by a dummy, we don't
* collapse it, but simply insert the new children. This happens when a
* pointer has just been expanded by the user.
*/
if (display->m_varKind == VarTree::VKpointer &&
display->childCount() == 0 &&
newValues->m_varKind == VarTree::VKdummy)
{
replaceChildren(display, newValues);
return;
}
/*
* If the display and newValues have different kind or if their number
* of children is different, replace the whole sub-tree.
*/
if (// the next two lines mean: not(m_varKind remains unchanged)
!(newValues->m_varKind == VarTree::VKdummy ||
display->m_varKind == newValues->m_varKind)
||
(display->childCount() != newValues->childCount() &&
/*
* If this is a pointer and newValues doesn't have children, we
* don't replace the sub-tree; instead, below we mark this
* sub-tree for requiring an update.
*/
(display->m_varKind != VarTree::VKpointer ||
newValues->m_child != 0)))
{
if (isExpanded) {
display->setOpen(false);
}
// since children changed, it is likely that the type has also changed
display->m_type = 0; /* will re-evaluate the type */
// display the new value
updateSingleExpr(display, newValues);
replaceChildren(display, newValues);
// update the m_varKind
if (newValues->m_varKind != VarTree::VKdummy) {
display->m_varKind = newValues->m_varKind;
display->setExpandable(newValues->m_varKind == VarTree::VKpointer);
}
// get some types (after the new m_varKind has been set!)
display->inferTypesOfChildren(typeTable);
// (note that the new value might not have a sub-tree at all)
return;
}
// display the new value
updateSingleExpr(display, newValues);
/*
* If this is an expanded pointer, record it for being updated.
*/
if (display->m_varKind == VarTree::VKpointer) {
if (isExpanded &&
// if newValues is a dummy, we have already updated this pointer
newValues->m_varKind != VarTree::VKdummy)
{
m_updatePtrs.push_back(display);
}
/*
* If the visible sub-tree has children, but newValues doesn't, we
* can stop here.
*/
if (newValues->m_child == 0) {
return;
}
}
ASSERT(display->childCount() == newValues->childCount());
// go for children
VarTree* vDisplay = display->firstChild();
ExprValue* vNew = newValues->m_child;
while (vDisplay != 0) {
// check whether the names are the same
if (vDisplay->getText() != vNew->m_name) {
// set new name
vDisplay->setText(vNew->m_name);
}
// recurse
updateExprRec(vDisplay, vNew, typeTable);
vDisplay = vDisplay->nextSibling();
vNew = vNew->m_next;
}
}
void ExprWnd::updateSingleExpr(VarTree* display, ExprValue* newValue)
{
/*
* If newValues is a VKdummy, we are only interested in its children.
* No need to update anything here.
*/
if (newValue->m_varKind == VarTree::VKdummy) {
return;
}
/*
* If this node is a struct and we know its type then we know how to
* find a nested value. So register the node for an update.
*
* wchar_t types are also treated specially here: We consider them
* as struct (has been set in inferTypesOfChildren()).
*/
if (display->m_varKind == VarTree::VKstruct &&
display->m_type != 0 &&
display->m_type != TypeInfo::unknownType())
{
ASSERT(newValue->m_varKind == VarTree::VKstruct);
if (display->m_type == TypeInfo::wchartType())
{
display->m_partialValue = "L";
}
else
display->m_partialValue = display->m_type->m_displayString[0];
m_updateStruct.push_back(display);
}
if (display->updateValue(newValue->m_value)) {
triggerUpdate();
}
}
void ExprWnd::updateStructValue(VarTree* display)
{
ASSERT(display->m_varKind == VarTree::VKstruct);
if (display->updateStructValue(display->m_partialValue)) {
triggerUpdate();
}
// reset the value
display->m_partialValue = "";
display->m_exprIndex = -1;
}
void ExprWnd::replaceChildren(VarTree* display, ExprValue* newValues)
{
ASSERT(display->childCount() == 0 || display->m_varKind != VarTree::VKsimple);
// delete all children of display
while (VarTree* c = display->firstChild()) {
unhookSubtree(c);
delete c;
}
// insert copies of the newValues
VarTree* vNew = 0;
for (ExprValue* v = newValues->m_child; v != 0; v = v->m_next)
{
vNew = new VarTree(display, vNew, v);
// recurse
replaceChildren(vNew, v);
}
}
void ExprWnd::collectUnknownTypes(VarTree* var)
{
TQListViewItemIterator i(var);
for (; i.current(); ++i)
{
checkUnknownType(static_cast<VarTree*>(i.current()));
}
}
void ExprWnd::checkUnknownType(VarTree* var)
{
ASSERT(var->m_varKind != VarTree::VKpointer || var->m_nameKind != VarTree::NKtype);
if (var->m_type == 0 &&
var->m_varKind == VarTree::VKstruct &&
var->m_nameKind != VarTree::NKtype &&
var->m_nameKind != VarTree::NKanonymous)
{
if (!var->isWcharT())
{
/* this struct node doesn't have a type yet: register it */
m_updateType.push_back(var);
}
else
{
var->m_type = TypeInfo::wchartType();
var->m_partialValue = "L";
m_updateStruct.push_back(var);
}
}
// add pointer pixmap to pointers
if (var->m_varKind == VarTree::VKpointer) {
var->setPixmap(m_pixPointer);
}
}
TQString ExprWnd::formatWCharPointer(TQString value)
{
int pos = value.find(") ");
if (pos > 0)
value = value.mid(pos+2);
return value + " L";
}
VarTree* ExprWnd::topLevelExprByName(const TQString& name) const
{
VarTree* item = firstChild();
while (item != 0 && item->getText() != name)
item = item->nextSibling();
return item;
}
VarTree* ExprWnd::ptrMemberByName(VarTree* v, const TQString& name)
{
// v must be a pointer variable, must have children
if (v->m_varKind != VarTree::VKpointer || v->childCount() == 0)
return 0;
// the only child of v is the pointer value that represents the struct
VarTree* item = v->firstChild();
return memberByName(item, name);
}
VarTree* ExprWnd::memberByName(VarTree* v, const TQString& name)
{
// search immediate children for name
VarTree* item = v->firstChild();
while (item != 0 && item->getText() != name)
item = item->nextSibling();
if (item != 0)
return item;
// try in base classes and members that are anonymous structs or unions
item = v->firstChild();
while (item != 0)
{
if (item->m_nameKind == VarTree::NKtype ||
item->m_nameKind == VarTree::NKanonymous)
{
v = memberByName(item, name);
if (v != 0)
return v;
}
item = item->nextSibling();
}
return 0;
}
void ExprWnd::removeExpr(VarTree* item)
{
unhookSubtree(item);
delete item;
}
void ExprWnd::unhookSubtree(VarTree* subTree)
{
// must remove any pointers scheduled for update from the list
unhookSubtree(m_updatePtrs, subTree);
unhookSubtree(m_updateType, subTree);
unhookSubtree(m_updateStruct, subTree);
emit removingItem(subTree);
}
void ExprWnd::unhookSubtree(std::list<VarTree*>& list, VarTree* subTree)
{
if (subTree == 0)
return;
std::list<VarTree*>::iterator i = list.begin();
while (i != list.end()) {
std::list<VarTree*>::iterator checkItem = i;
++i;
if (subTree->isAncestorEq(*checkItem)) {
// checkItem is an item from subTree
list.erase(checkItem);
}
}
}
void ExprWnd::clearPendingUpdates()
{
m_updatePtrs.clear();
m_updateType.clear();
m_updateStruct.clear();
}
VarTree* ExprWnd::nextUpdatePtr()
{
VarTree* ptr = 0;
if (!m_updatePtrs.empty()) {
ptr = m_updatePtrs.front();
m_updatePtrs.pop_front();
}
return ptr;
}
VarTree* ExprWnd::nextUpdateType()
{
VarTree* ptr = 0;
if (!m_updateType.empty()) {
ptr = m_updateType.front();
m_updateType.pop_front();
}
return ptr;
}
VarTree* ExprWnd::nextUpdateStruct()
{
VarTree* ptr = 0;
if (!m_updateStruct.empty()) {
ptr = m_updateStruct.front();
m_updateStruct.pop_front();
}
return ptr;
}
void ExprWnd::editValue(VarTree* item, const TQString& text)
{
if (m_edit == 0)
m_edit = new ValueEdit(this);
TQRect r = itemRect(item);
int x = r.x()+columnWidth(0);
int y = r.y();
int w = columnWidth(1);
int h = r.height();
TQListView* lv = item->listView();
/*
* Make the edit widget at least 5 characters wide (but not wider than
* this widget). If less than half of this widget is used to display
* the text, scroll this widget so that half of it shows the text (or
* less than half of it if the text is shorter).
*/
TQFontMetrics metr = m_edit->font();
int wMin = metr.width("88888");
if (w < wMin)
w = wMin;
int wThis = lv->visibleWidth();
if (x >= wThis/2 && // less than half the width displays text
x+w > wThis) // not all text is visible
{
// scroll so that more text is visible
int wScroll = TQMIN(x-wThis/2, x+w-wThis);
lv->scrollBy(wScroll, 0);
x -= wScroll;
}
else if (x < 0)
{
// don't let the edit move out at the left
x = 0;
}
// make the edit box as wide as the visible column
TQRect rect(x,y, wThis-x,h);
m_edit->setText(text);
m_edit->selectAll();
m_edit->setGeometry(rect);
m_edit->m_finished = false;
m_edit->m_item = item;
m_edit->show();
m_edit->setFocus();
}
bool ExprWnd::isEditing() const
{
return m_edit != 0 && m_edit->isVisible();
}
ValueEdit::ValueEdit(ExprWnd* parent) :
TQLineEdit(parent->viewport(), "valueedit")
{
setFrame(false);
hide();
lower(); // lower the window below scrollbars
connect(parent, SIGNAL(selectionChanged()), SLOT(slotSelectionChanged()));
connect(parent, SIGNAL(currentChanged(TQListViewItem*)), SLOT(slotSelectionChanged()));
connect(parent, SIGNAL(expanded(TQListViewItem*)), SLOT(slotSelectionChanged()));
connect(parent, SIGNAL(collapsed(TQListViewItem*)), SLOT(slotSelectionChanged()));
connect(this, SIGNAL(done(VarTree*, const TQString&)),
parent, SIGNAL(editValueCommitted(VarTree*, const TQString&)));
}
ValueEdit::~ValueEdit()
{
}
void ValueEdit::terminate(bool commit)
{
TRACE(commit?"ValueEdit::terminate(true)":"ValueEdit::terminate(false)");
if (!m_finished)
{
m_finished = true;
hide(); // will call focusOutEvent, that's why we need m_finished
if (commit) {
emit done(m_item, text());
}
}
}
void ValueEdit::keyPressEvent(TQKeyEvent *e)
{
if(e->key() == TQt::Key_Return || e->key() == TQt::Key_Enter)
terminate(true);
else if(e->key() == TQt::Key_Escape)
terminate(false);
else
TQLineEdit::keyPressEvent(e);
}
void ValueEdit::paintEvent(TQPaintEvent* e)
{
TQLineEdit::paintEvent(e);
TQPainter p(this);
p.drawRect(rect());
}
void ValueEdit::focusOutEvent(TQFocusEvent* ev)
{
TRACE("ValueEdit::focusOutEvent");
TQFocusEvent* focusEv = static_cast<TQFocusEvent*>(ev);
if (focusEv->reason() == TQFocusEvent::ActiveWindow)
{
// Switching to a different window should terminate the edit,
// because if the window with this variable display is floating
// then that different window could be the main window, where
// the user had clicked one of the Execute buttons. This in turn
// may pull the item away that we are editing here.
terminate(false);
}
// Don't let a RMB close the editor
else if (focusEv->reason() != TQFocusEvent::Popup)
{
terminate(true);
}
}
void ValueEdit::slotSelectionChanged()
{
TRACE("ValueEdit::slotSelectionChanged");
terminate(false);
}