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