// ************************************************************************** // begin : Sun Aug 8 1999 // copyright : (C) 1999 by John Birch // email : jbb@tdevelop.org // // Adapted for ruby debugging // -------------------------- // begin : Mon Nov 1 2004 // copyright : (C) 2004 by Richard Dale // email : Richard_Dale@tipitina.demon.co.uk // ************************************************************************** // ************************************************************************** // * * // * 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 "rdbparser.h" #include "rdbcommand.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // ************************************************************************** // ************************************************************************** // ************************************************************************** namespace RDBDebugger { VariableWidget::VariableWidget(TQWidget *parent, const char *name) : TQWidget(parent, name) { varTree_ = new VariableTree(this); TQLabel *label = new TQLabel(i18n("E&xpression to watch:"), this); TQHBox *watchEntry = new TQHBox( this ); watchVarEditor_ = new KHistoryCombo( watchEntry, "var-to-watch editor"); label->setBuddy(watchVarEditor_); TQPushButton *addButton = new TQPushButton(i18n("&Add"), watchEntry ); addButton->adjustSize(); addButton->setFixedWidth(addButton->width()); TQBoxLayout * vbox = new TQVBoxLayout(); vbox->addWidget( label ); vbox->addWidget( watchEntry ); TQVBoxLayout *topLayout = new TQVBoxLayout(this, 2); topLayout->addWidget(varTree_, 10); topLayout->addLayout( vbox ); connect( addButton, TQT_SIGNAL(clicked()), TQT_SLOT(slotAddWatchExpression()) ); connect( watchVarEditor_, TQT_SIGNAL(returnPressed()), TQT_SLOT(slotAddWatchExpression()) ); } // ************************************************************************** void VariableWidget::setEnabled(bool bEnabled) { TQWidget::setEnabled(bEnabled); if (bEnabled && parentWidget() != 0) { varTree_->setColumnWidth(0, parentWidget()->width()/2); } } // ************************************************************************** void VariableWidget::slotAddWatchExpression() { TQString watchVar(watchVarEditor_->currentText()); if (!watchVar.isEmpty()) { slotAddWatchExpression(watchVar); } } // ************************************************************************** void VariableWidget::slotAddWatchExpression(const TQString &ident) { if (!ident.isEmpty()) { watchVarEditor_->addToHistory(ident); varTree_->slotAddWatchExpression(ident); watchVarEditor_->clearEdit(); } } // ************************************************************************** void VariableWidget::focusInEvent(TQFocusEvent */*e*/) { varTree_->setFocus(); } void VariableWidget::restorePartialProjectSession(const TQDomElement* el) { varTree_->watchRoot()->restorePartialProjectSession(el); } void VariableWidget::savePartialProjectSession(TQDomElement* el) { varTree_->watchRoot()->savePartialProjectSession(el); } // ************************************************************************** // ************************************************************************** // ************************************************************************** VariableTree::VariableTree(VariableWidget *parent, const char *name) : KListView(parent, name), TQToolTip( viewport() ), activationId_(0), currentThread_(-1), selectedFrame_(0), watchRoot_(0), globalRoot_(0) { setRootIsDecorated(true); setAllColumnsShowFocus(true); setColumnWidthMode(0, Manual); setSorting(VAR_NAME_COLUMN); TQListView::setSelectionMode(TQListView::Single); addColumn(i18n("Variable"), 100 ); addColumn(i18n("Value"), 100 ); connect( this, TQT_SIGNAL(contextMenu(KListView*, TQListViewItem*, const TQPoint&)), TQT_SLOT(slotContextMenu(KListView*, TQListViewItem*)) ); connect( this, TQT_SIGNAL(pressed(TQListViewItem*)), this, TQT_SLOT(slotPressed(TQListViewItem*)) ); watchRoot_ = new WatchRoot(this); } // ************************************************************************** VariableTree::~VariableTree() { } // ************************************************************************** void VariableTree::clear() { TQListViewItem *sibling = firstChild(); while (sibling != 0) { TQListViewItem * current = sibling; sibling = sibling->nextSibling(); if (current->rtti() != RTTI_WATCH_ROOT) { delete current; } } globalRoot_ = 0; selectedFrame_ = 0; return; } // ************************************************************************** void VariableTree::slotContextMenu(KListView *, TQListViewItem *item) { if (item == 0) return; setSelected(item, true); // Need to select this item. if (item->parent() != 0) { KPopupMenu popup(this); popup.insertTitle(item->text(VAR_NAME_COLUMN)); int idRemoveWatch = -2; if (item->rtti() == RTTI_WATCH_VAR_ITEM) { idRemoveWatch = popup.insertItem( i18n("Remove Watch Expression") ); } int idCopyToClipboard = popup.insertItem( i18n("Copy to Clipboard") ); int res = popup.exec(TQCursor::pos()); if (res == idRemoveWatch) { emit removeWatchExpression(((WatchVarItem*)item)->displayId()); delete item; } else if (res == idCopyToClipboard) { TQClipboard *qb = TDEApplication::clipboard(); TQString text = "{ \"" + item->text( VAR_NAME_COLUMN ) + "\", " + "\"" + item->text( VALUE_COLUMN ) + "\" }"; qb->setText( text, TQClipboard::Clipboard ); } } } /***************************************************************************/ void VariableTree::setSelected(TQListViewItem * item, bool selected) { // Save the last selected VarFrameRoot for slotPressed() to restore if (item->rtti() == RTTI_VAR_FRAME_ROOT && selected) { selectedFrame_ = (VarFrameRoot *) item; } TQListView::setSelected(item, selected); } /***************************************************************************/ // Makes sure that only VarFrameRoot items can be selected void VariableTree::slotPressed(TQListViewItem * item) { if (item == 0) { return; } while (item->rtti() == RTTI_VAR_ITEM) { item = item->parent(); } if ( item->rtti() == RTTI_GLOBAL_ROOT || item->rtti() == RTTI_WATCH_ROOT || item->rtti() == RTTI_WATCH_VAR_ITEM ) { if (selectedFrame_ != 0) { setSelected(selectedFrame_, true); } return; } if (item->rtti() == RTTI_VAR_FRAME_ROOT) { VarFrameRoot * frame = (VarFrameRoot*) item; emit selectFrame(frame->frameNo(), frame->threadNo()); } return; } // ************************************************************************** void VariableTree::prune() { TQListViewItem *child = firstChild(); while (child != 0) { TQListViewItem *nextChild = child->nextSibling(); // Only prune var frames, not the watch or global root if (child->rtti() == RTTI_VAR_FRAME_ROOT) { if (((VarFrameRoot*) child)->isActive()) { if (child->isOpen()) { ((VarFrameRoot*) child)->prune(); } } else { delete child; } } child = nextChild; } } // ************************************************************************** // The debugger has moved onto the next program pause, so invalidate // everything in the Variable Tree void VariableTree::nextActivationId() { activationId_++; globalRoot()->setActivationId(); watchRoot()->setActivationId(); // ..but that's only the Watch and Global roots } // ************************************************************************** // VarFrameRoot frames in the Variable Tree from the previous program pause, // are set active here. Notified by the Frame Stack widget when it parses the // backtrace from the 'where' command after a pause. // // After that, any frames which aren't marked as active must have gone // out of scope and will end up pruned. void VariableTree::slotFrameActive(int frameNo, int threadNo, const TQString& frameName) { VarFrameRoot * frame = findFrame(frameNo, threadNo); if (frameNo == 1) { // If the current frame 1 doesn't exist, create it if (frame == 0) { frame = new VarFrameRoot(this, frameNo, threadNo); } frame->setFrameName(frameName); } if (frame != 0 && frame->text(VAR_NAME_COLUMN) == frameName) { frame->setActivationId(); } } // ************************************************************************** bool VariableTree::schedule() { TQListViewItem * child = firstChild(); VarFrameRoot * frame = 0; while (child != 0) { if (child->rtti() == RTTI_VAR_FRAME_ROOT) { frame = (VarFrameRoot *) child; Q_ASSERT( !frame->isWaitingForData() ); if (frame->needsVariables()) { if (TQApplication::overrideCursor() == 0) { TQApplication::setOverrideCursor(TQCursor(TQt::WaitCursor)); } // Tell the controller to fetch the variable values emit selectFrame(frame->frameNo(), frame->threadNo()); return true; } } child = child->nextSibling(); } frame = findFrame(1, currentThread_); Q_ASSERT( frame != 0 ); Q_ASSERT( !frame->needsVariables() ); // All over, nothing left to fetch. // Return to frame 1, and prune the inactive items // from the variable tree.. TQApplication::restoreOverrideCursor(); emit selectFrame(1, currentThread_); prune(); return false; } // ************************************************************************** void VariableTree::slotAddWatchExpression(const TQString &watchVar) { new WatchVarItem(watchRoot(), watchVar, UNKNOWN_TYPE); emit addWatchExpression(watchVar, true); } // ************************************************************************** void VariableTree::setFetchGlobals(bool fetch) { emit fetchGlobals(fetch); } // ************************************************************************** VarFrameRoot *VariableTree::findFrame(int frameNo, int threadNo) const { // frames only exist on the top level so we only need to // check the siblings TQListViewItem *sibling = firstChild(); while (sibling != 0) { if ( sibling->rtti() == RTTI_VAR_FRAME_ROOT && ((VarFrameRoot*) sibling)->frameNo() == frameNo && ((VarFrameRoot*) sibling)->threadNo() == threadNo ) { return (VarFrameRoot*) sibling; } sibling = sibling->nextSibling(); } return 0; } // ************************************************************************** WatchRoot *VariableTree::watchRoot() { return watchRoot_; } // ************************************************************************** GlobalRoot *VariableTree::globalRoot() { if (globalRoot_ == 0) { globalRoot_ = new GlobalRoot(this); } return globalRoot_; } // ************************************************************************** // Watch variables can be added before the start of a debugging session, // so tell the controller about any already in the tree at start. void VariableTree::resetWatchVars() { for (TQListViewItem *child = watchRoot()->firstChild(); child != 0; child = child->nextSibling()) { ((WatchVarItem*) child)->setDisplayId(-1); emit addWatchExpression(child->text(VAR_NAME_COLUMN), false); } } // ************************************************************************** void VariableTree::maybeTip(const TQPoint &p) { VarItem * item = dynamic_cast( itemAt(p) ); if (item != 0) { TQRect r = itemRect(item); if (r.isValid()) { tip(r, item->tipText()); } } } // ************************************************************************** // ************************************************************************** // ************************************************************************** LazyFetchItem::LazyFetchItem(VariableTree *parent) : KListViewItem(parent), activationId_(0), waitingForData_(false) { setActivationId(); } // ************************************************************************** LazyFetchItem::LazyFetchItem(LazyFetchItem *parent) : KListViewItem(parent), activationId_(0), waitingForData_(false) { setActivationId(); } // ************************************************************************** LazyFetchItem::~LazyFetchItem() { } // ************************************************************************** void LazyFetchItem::paintCell(TQPainter *p, const TQColorGroup &cg, int column, int width, int align) { if (p == 0) { return; } // make toplevel item (watch and frame items) names bold if (column == VAR_NAME_COLUMN && parent() == 0) { TQFont f = p->font(); f.setBold(true); p->setFont(f); } TQListViewItem::paintCell( p, cg, column, width, align ); } // ************************************************************************** VarItem *LazyFetchItem::findItem(const TQString &name) const { TQListViewItem *child = firstChild(); // Check the siblings on this branch while (child != 0) { if (child->text(VAR_NAME_COLUMN) == name) { return (VarItem*) child; } child = child->nextSibling(); } return 0; } // ************************************************************************** void LazyFetchItem::prune() { TQListViewItem *child = firstChild(); while (child != 0) { LazyFetchItem *item = (LazyFetchItem*) child; child = child->nextSibling(); // Never prune a branch if we are waiting on data to arrive. if (!waitingForData_) { if (item->isActive()) { item->prune(); } else { delete item; } } } } // ************************************************************************** // ************************************************************************** // ************************************************************************** VarItem::VarItem(LazyFetchItem *parent, const TQString &varName, DataType dataType) : LazyFetchItem (parent), cache_(TQCString()), dataType_(dataType), highlight_(false) { setText(VAR_NAME_COLUMN, varName); setSelectable(false); // Order the VarItems so that globals are first, then // constants, class variables, instance variables and // finally local variables // Matches either an array element or a string slice, // Order on the array index or the first number in the // range specifying the slice. TQRegExp arrayelement_re("\\[(\\d+)(\\.\\.\\d+)?\\]"); key_ = varName; if (arrayelement_re.search(varName) != -1) { key_.sprintf("%.6d", arrayelement_re.cap(1).toInt()); } else if (key_.startsWith("$")) { key_.prepend("1001"); // Global variable } else if (TQRegExp("^[A-Z]").search(varName) != -1) { key_.prepend("1002"); // Constant } else if (key_.startsWith("@@")) { key_.prepend("1003"); // Class variable } else if (key_.startsWith("@")) { key_.prepend("1004"); // Instance variable } else { key_.prepend("1005"); // Local variable or parameter } // kdDebug(9012) << " ### VarItem::VarItem *CONSTR* " << varName << endl; } // ************************************************************************** VarItem::~VarItem() { } TQString VarItem::key(int /*column*/, bool /*ascending*/) const { return key_; } // ************************************************************************** // Returns the path of a ruby item. If it is an instance variable, assume // that there is an attr_accessor method for it. // For example, @foobar within instance obj is accessed as obj.foobar. // But don't strip off the @ for an instance variable with no path, // and leave a plain '@foobar' as it is. TQString VarItem::fullName() const { TQString itemName = text(VAR_NAME_COLUMN); TQString vPath(""); const VarItem *item = this; if (item->parent()->rtti() != RTTI_VAR_ITEM) { return itemName; } // This stops at the root item (FrameRoot or GlobalRoot) while (item->rtti() == RTTI_VAR_ITEM) { TQString itemName = item->text(VAR_NAME_COLUMN); if (vPath.startsWith("[")) { // If it's a Hash or an Array, then just insert the value. As // in adding '[0]' to foo.bar to give foo.bar[0] vPath.prepend(itemName); } else { if (vPath.isEmpty()) { vPath = itemName; } else { vPath.prepend(itemName + "."); } } item = (VarItem*) item->parent(); } // Change 'self.@foobar' to '@foobar' vPath.replace(TQRegExp("^self\\.@"), "@"); // Use instance_variable_get() to access any '@var's in the middle of a path TQRegExp re_instance_var("\\.(@[^\\[.]+)"); int pos = re_instance_var.search(vPath); while (pos != -1) { vPath.replace( pos, re_instance_var.matchedLength(), TQString(".instance_variable_get(:") + re_instance_var.cap(1) + ")" ); pos = re_instance_var.search(vPath, pos); } return vPath; } // ************************************************************************** void VarItem::setText(int column, const TQString &data) { setActivationId(); if (column == VALUE_COLUMN) { highlight_ = (!text(VALUE_COLUMN).isEmpty() && text(VALUE_COLUMN) != data); } TQListViewItem::setText(column, data); repaint(); } // ************************************************************************** void VarItem::expandValue(char *buf) { LazyFetchItem::stopWaitingForData(); RDBParser::parseExpandedVariable(this, buf); } // ************************************************************************** void VarItem::setOpen(bool open) { TQListViewItem::setOpen(open); Q_ASSERT( dataType_ == REFERENCE_TYPE || dataType_ == ARRAY_TYPE || dataType_ == HASH_TYPE || dataType_ == STRING_TYPE || dataType_ == STRUCT_TYPE ); update(); return; } // ************************************************************************** void VarItem::update() { if (isOpen()) { startWaitingForData(); // emit ((VariableTree*)listView())->expandItem(this, fullName().latin1()); ((VariableTree*)listView())->expandItem(this, fullName().latin1()); } return; } // ************************************************************************** DataType VarItem::dataType() const { return dataType_; } // ************************************************************************** void VarItem::setDataType(DataType dataType) { dataType_ = dataType; } // ************************************************************************** // Overridden to highlight the changed items void VarItem::paintCell(TQPainter *p, const TQColorGroup &cg, int column, int width, int align) { if (p == 0) { return; } if (column == VALUE_COLUMN) { // Show color values as colors, and make the text color the same // as the base color if (dataType_ == COLOR_TYPE) { TQRegExp color_re("\\s(#.*)>"); if (color_re.search(text(column)) != -1) { TQColorGroup color_cg( cg.foreground(), cg.background(), cg.light(), cg.dark(), cg.mid(), TQColor(color_re.cap(1)), TQColor(color_re.cap(1)) ); TQListViewItem::paintCell(p, color_cg, column, width, align); return; } } // Highlight recently changed items in red if (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); return; } } TQListViewItem::paintCell(p, cg, column, width, align); return; } // ************************************************************************** TQString VarItem::tipText() const { const unsigned int MAX_TOOLTIP_SIZE = 70; TQString tip = text(VALUE_COLUMN); if (tip.length() < MAX_TOOLTIP_SIZE) { return tip; } else { return tip.mid(0, MAX_TOOLTIP_SIZE - 1) + " [...]"; } } // ************************************************************************** // ************************************************************************** // ************************************************************************** VarFrameRoot::VarFrameRoot(VariableTree *parent, int frameNo, int threadNo) : LazyFetchItem(parent), needsVariables_(true), frameNo_(frameNo), threadNo_(threadNo), cache_("") { setExpandable(true); } // ************************************************************************** VarFrameRoot::~VarFrameRoot() { } // ************************************************************************** void VarFrameRoot::addLocals(char *variables) { cache_.append(variables); } // ************************************************************************** void VarFrameRoot::setLocals() { RDBParser::parseVariables(this, cache_.data()); cache_ = ""; needsVariables_ = false; stopWaitingForData(); prune(); return; } // ************************************************************************** // Override setOpen so that we can decide what to do when we do change // state. void VarFrameRoot::setOpen(bool open) { bool localsViewChanged = (isOpen() != open); TQListViewItem::setOpen(open); if (localsViewChanged) { ((VariableTree*)listView())->selectFrame(frameNo_, threadNo_); } return; } void VarFrameRoot::setFrameName(const TQString &frameName) { setText(VAR_NAME_COLUMN, frameName); setText(VALUE_COLUMN, ""); return; } void VarFrameRoot::setActivationId() { LazyFetchItem::setActivationId(); stopWaitingForData(); needsVariables_ = true; cache_ = ""; } bool VarFrameRoot::needsVariables() const { return ( text(VAR_NAME_COLUMN).contains("try_initialize") == 0 && isOpen() && !isWaitingForData() && needsVariables_ ); } // ************************************************************************** // ************************************************************************** // ************************************************************************** // ************************************************************************** GlobalRoot::GlobalRoot(VariableTree *parent) : LazyFetchItem(parent) { setText(0, i18n("Global")); setExpandable(true); setOpen(false); setSelectable(false); } // ************************************************************************** GlobalRoot::~GlobalRoot() { } // ************************************************************************** void GlobalRoot::setGlobals(char * globals) { setActivationId(); RDBParser::parseVariables(this, globals); return; } // ************************************************************************** void GlobalRoot::setOpen(bool open) { bool globalsViewChanged = (isOpen() != open); TQListViewItem::setOpen(open); if (globalsViewChanged) { ((VariableTree*)listView())->setFetchGlobals(isOpen()); } return; } // ************************************************************************** // ************************************************************************** // ************************************************************************** // ************************************************************************** WatchVarItem::WatchVarItem( LazyFetchItem *parent, const TQString &varName, DataType dataType, int displayId ) : VarItem(parent, varName, dataType), displayId_(displayId) { } // ************************************************************************** WatchVarItem::~WatchVarItem() { } // ************************************************************************** void WatchVarItem::setDisplayId(int id) { displayId_ = id; } // ************************************************************************** int WatchVarItem::displayId() { return displayId_; } // ************************************************************************** // ************************************************************************** // ************************************************************************** // ************************************************************************** WatchRoot::WatchRoot(VariableTree *parent) : LazyFetchItem(parent) { setText(VAR_NAME_COLUMN, i18n("Watch")); setOpen(true); setSelectable(false); } // ************************************************************************** WatchRoot::~WatchRoot() { } // ************************************************************************** // Sets the initial value of a new Watch item, along with the // display id void WatchRoot::setWatchExpression(char * buf, char * expression) { TQString expr(expression); TQRegExp display_re("^(\\d+):\\s([^\n]+)\n"); for ( TQListViewItem *child = firstChild(); child != 0; child = child->nextSibling() ) { WatchVarItem *varItem = (WatchVarItem*) child; if ( varItem->text(VAR_NAME_COLUMN) == expr && varItem->displayId() == -1 && display_re.search(buf) >= 0 ) { varItem->setDisplayId(display_re.cap(1).toInt()); // Skip over the 'thing = ' part of expr to get the value varItem->setText( VALUE_COLUMN, display_re.cap(2).mid(varItem->text(VAR_NAME_COLUMN).length() + strlen(" = ")) ); return; } } } // After a program pause, this updates the new value of a Watch item // expr is the thing = value part of "1: a = 1", id is the display number void WatchRoot::updateWatchExpression(int id, const TQString& expr) { for ( TQListViewItem *child = firstChild(); child != 0; child = child->nextSibling() ) { WatchVarItem *varItem = (WatchVarItem*) child; if (varItem->displayId() == id) { Q_ASSERT( expr.startsWith(varItem->text(VAR_NAME_COLUMN)) ); // Skip over the 'thing = ' part of expr to get the value varItem->setText( VALUE_COLUMN, expr.mid(varItem->text(VAR_NAME_COLUMN).length() + strlen(" = ")) ); return; } } } void WatchRoot::savePartialProjectSession(TQDomElement* el) { TQDomDocument domDoc = el->ownerDocument(); if (domDoc.isNull()) { return; } TQDomElement watchEl = domDoc.createElement("watchExpressions"); for ( TQListViewItem *child = firstChild(); child != 0; child = child->nextSibling() ) { TQDomElement subEl = domDoc.createElement("el"); subEl.appendChild(domDoc.createTextNode(child->text(VAR_NAME_COLUMN))); watchEl.appendChild(subEl); } if (!watchEl.isNull()) { el->appendChild(watchEl); } return; } void WatchRoot::restorePartialProjectSession(const TQDomElement* el) { TQDomDocument domDoc = el->ownerDocument(); if (domDoc.isNull()) { return; } TQDomElement watchEl = el->namedItem("watchExpressions").toElement(); TQDomElement subEl = watchEl.firstChild().toElement(); while (!subEl.isNull()) { new WatchVarItem(this, subEl.firstChild().toText().data(), UNKNOWN_TYPE); subEl = subEl.nextSibling().toElement(); } return; } // ************************************************************************** // ************************************************************************** // ************************************************************************** } #include "variablewidget.moc"