// ************************************************************************** // 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /** 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( "Variable tree

" "The variable tree allows you to see the values of local " "variables and arbitrary expressions." "

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." "

To change the value of a variable or an expression, " "click on the value.")); TQWhatsThis::add(watchVarEditor_, i18n("Expression entry" "

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(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(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(item); var_item->setFormat(static_cast(res)); } else if (res == idRemember) { if (VarItem *item = dynamic_cast(currentItem())) { ((VariableWidget*)parent())-> slotEvaluateExpression(item->gdbExpression()); } } else if (res == idWatch) { if (VarItem *item = dynamic_cast(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(currentItem())) emit toggleWatchpoint(item->gdbExpression()); } else if (res == idReevaluate) { if (VarItem* item = dynamic_cast(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(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 (child)) && child != recentExpressions_) { delete child; } child = nextChild; } currentFrameItem = 0; if (recentExpressions_) { for(TQListViewItem* child = recentExpressions_->firstChild(); child; child = child->nextSibling()) { static_cast(child)->unhookFromGdb(); } } if (WatchRoot* w = findWatch()) { for(TQListViewItem* child = w->firstChild(); child; child = child->nextSibling()) { static_cast(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 (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 (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( 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& 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( static_cast(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 , ... 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& 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, "Can't get frame id" "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 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(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(current)->recreateLocallyMaybe(); } for(TQListViewItem* child = findWatch()->firstChild(); child; child = child->nextSibling()) { VarItem* var = static_cast(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 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::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& 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("Debugger error
") + 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(item); Q_ASSERT(v); if (v) { v->setValue(text); } } } void VariableTree::keyPressEvent(TQKeyEvent* e) { if (VarItem* item = dynamic_cast(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(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::tqclipboard(); 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(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& 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& 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(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(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(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(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 tqunicode // 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(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.tqunicode[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(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 ""; } // ************************************************************************** // 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::tqpalette().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(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(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(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"