1
// **************************************************************************
2
// begin : Sun Aug 8 1999
3
// copyright : (C) 1999 by John Birch
4
// email : jbb@kdevelop.org
6
// Adapted for ruby debugging
7
// --------------------------
8
// begin : Mon Nov 1 2004
9
// copyright : (C) 2004 by Richard Dale
10
// email : Richard_Dale@tipitina.demon.co.uk
11
// **************************************************************************
13
// **************************************************************************
15
// * This program is free software; you can redistribute it and/or modify *
16
// * it under the terms of the GNU General Public License as published by *
17
// * the Free Software Foundation; either version 2 of the License, or *
18
// * (at your option) any later version. *
20
// **************************************************************************
22
#include "variablewidget.h"
23
#include "rdbparser.h"
24
#include "rdbcommand.h"
27
#include <kpopupmenu.h>
28
#include <klineedit.h>
29
#include <kdeversion.h>
36
#include <qpushbutton.h>
42
#include <qclipboard.h>
43
#include <kapplication.h>
45
// **************************************************************************
46
// **************************************************************************
47
// **************************************************************************
52
VariableWidget::VariableWidget(QWidget *parent, const char *name)
53
: QWidget(parent, name)
55
varTree_ = new VariableTree(this);
56
QLabel *label = new QLabel(i18n("E&xpression to watch:"), this);
58
QHBox *watchEntry = new QHBox( this );
59
watchVarEditor_ = new KHistoryCombo( watchEntry, "var-to-watch editor");
60
label->setBuddy(watchVarEditor_);
62
QPushButton *addButton = new QPushButton(i18n("&Add"), watchEntry );
63
addButton->adjustSize();
64
addButton->setFixedWidth(addButton->width());
66
QBoxLayout * vbox = new QVBoxLayout();
67
vbox->addWidget( label );
68
vbox->addWidget( watchEntry );
70
QVBoxLayout *topLayout = new QVBoxLayout(this, 2);
71
topLayout->addWidget(varTree_, 10);
72
topLayout->addLayout( vbox );
74
connect( addButton, SIGNAL(clicked()), SLOT(slotAddWatchExpression()) );
75
connect( watchVarEditor_, SIGNAL(returnPressed()), SLOT(slotAddWatchExpression()) );
79
// **************************************************************************
81
void VariableWidget::setEnabled(bool bEnabled)
83
QWidget::setEnabled(bEnabled);
84
if (bEnabled && parentWidget() != 0) {
85
varTree_->setColumnWidth(0, parentWidget()->width()/2);
88
// **************************************************************************
90
void VariableWidget::slotAddWatchExpression()
92
QString watchVar(watchVarEditor_->currentText());
93
if (!watchVar.isEmpty()) {
94
slotAddWatchExpression(watchVar);
98
// **************************************************************************
100
void VariableWidget::slotAddWatchExpression(const QString &ident)
102
if (!ident.isEmpty()) {
103
watchVarEditor_->addToHistory(ident);
104
varTree_->slotAddWatchExpression(ident);
105
watchVarEditor_->clearEdit();
109
// **************************************************************************
111
void VariableWidget::focusInEvent(QFocusEvent */*e*/)
113
varTree_->setFocus();
116
void VariableWidget::restorePartialProjectSession(const QDomElement* el)
118
varTree_->watchRoot()->restorePartialProjectSession(el);
121
void VariableWidget::savePartialProjectSession(QDomElement* el)
123
varTree_->watchRoot()->savePartialProjectSession(el);
126
// **************************************************************************
127
// **************************************************************************
128
// **************************************************************************
130
VariableTree::VariableTree(VariableWidget *parent, const char *name)
131
: KListView(parent, name),
132
QToolTip( viewport() ),
139
setRootIsDecorated(true);
140
setAllColumnsShowFocus(true);
141
setColumnWidthMode(0, Manual);
142
setSorting(VAR_NAME_COLUMN);
143
QListView::setSelectionMode(QListView::Single);
145
addColumn(i18n("Variable"), 100 );
146
addColumn(i18n("Value"), 100 );
148
connect( this, SIGNAL(contextMenu(KListView*, QListViewItem*, const QPoint&)),
149
SLOT(slotContextMenu(KListView*, QListViewItem*)) );
151
connect( this, SIGNAL(pressed(QListViewItem*)),
152
this, SLOT(slotPressed(QListViewItem*)) );
154
watchRoot_ = new WatchRoot(this);
157
// **************************************************************************
159
VariableTree::~VariableTree()
163
// **************************************************************************
165
void VariableTree::clear()
167
QListViewItem *sibling = firstChild();
168
while (sibling != 0) {
169
QListViewItem * current = sibling;
170
sibling = sibling->nextSibling();
171
if (current->rtti() != RTTI_WATCH_ROOT) {
181
// **************************************************************************
183
void VariableTree::slotContextMenu(KListView *, QListViewItem *item)
188
setSelected(item, true); // Need to select this item.
190
if (item->parent() != 0) {
191
KPopupMenu popup(this);
192
popup.insertTitle(item->text(VAR_NAME_COLUMN));
193
int idRemoveWatch = -2;
194
if (item->rtti() == RTTI_WATCH_VAR_ITEM) {
195
idRemoveWatch = popup.insertItem( i18n("Remove Watch Expression") );
198
int idCopyToClipboard = popup.insertItem( i18n("Copy to Clipboard") );
199
int res = popup.exec(QCursor::pos());
201
if (res == idRemoveWatch) {
202
emit removeWatchExpression(((WatchVarItem*)item)->displayId());
204
} else if (res == idCopyToClipboard) {
205
QClipboard *qb = KApplication::clipboard();
206
QString text = "{ \"" + item->text( VAR_NAME_COLUMN ) + "\", " +
207
"\"" + item->text( VALUE_COLUMN ) + "\" }";
209
#if KDE_VERSION > 305
210
qb->setText( text, QClipboard::Clipboard );
218
/***************************************************************************/
220
void VariableTree::setSelected(QListViewItem * item, bool selected)
222
// Save the last selected VarFrameRoot for slotPressed() to restore
223
if (item->rtti() == RTTI_VAR_FRAME_ROOT && selected) {
224
selectedFrame_ = (VarFrameRoot *) item;
227
QListView::setSelected(item, selected);
230
/***************************************************************************/
232
// Makes sure that only VarFrameRoot items can be selected
233
void VariableTree::slotPressed(QListViewItem * item)
239
while (item->rtti() == RTTI_VAR_ITEM) {
240
item = item->parent();
243
if ( item->rtti() == RTTI_GLOBAL_ROOT
244
|| item->rtti() == RTTI_WATCH_ROOT
245
|| item->rtti() == RTTI_WATCH_VAR_ITEM )
247
if (selectedFrame_ != 0) {
248
setSelected(selectedFrame_, true);
253
if (item->rtti() == RTTI_VAR_FRAME_ROOT) {
254
VarFrameRoot * frame = (VarFrameRoot*) item;
255
emit selectFrame(frame->frameNo(), frame->threadNo());
261
// **************************************************************************
263
void VariableTree::prune()
265
QListViewItem *child = firstChild();
268
QListViewItem *nextChild = child->nextSibling();
270
// Only prune var frames, not the watch or global root
271
if (child->rtti() == RTTI_VAR_FRAME_ROOT) {
272
if (((VarFrameRoot*) child)->isActive()) {
273
if (child->isOpen()) {
274
((VarFrameRoot*) child)->prune();
285
// **************************************************************************
287
// The debugger has moved onto the next program pause, so invalidate
288
// everything in the Variable Tree
289
void VariableTree::nextActivationId()
292
globalRoot()->setActivationId();
293
watchRoot()->setActivationId();
294
// ..but that's only the Watch and Global roots
297
// **************************************************************************
299
// VarFrameRoot frames in the Variable Tree from the previous program pause,
300
// are set active here. Notified by the Frame Stack widget when it parses the
301
// backtrace from the 'where' command after a pause.
303
// After that, any frames which aren't marked as active must have gone
304
// out of scope and will end up pruned.
305
void VariableTree::slotFrameActive(int frameNo, int threadNo, const QString& frameName)
307
VarFrameRoot * frame = findFrame(frameNo, threadNo);
309
// If the current frame 1 doesn't exist, create it
311
frame = new VarFrameRoot(this, frameNo, threadNo);
314
frame->setFrameName(frameName);
317
if (frame != 0 && frame->text(VAR_NAME_COLUMN) == frameName) {
318
frame->setActivationId();
322
// **************************************************************************
324
bool VariableTree::schedule()
326
QListViewItem * child = firstChild();
327
VarFrameRoot * frame = 0;
330
if (child->rtti() == RTTI_VAR_FRAME_ROOT) {
331
frame = (VarFrameRoot *) child;
332
Q_ASSERT( !frame->isWaitingForData() );
334
if (frame->needsVariables()) {
335
if (QApplication::overrideCursor() == 0) {
336
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
339
// Tell the controller to fetch the variable values
340
emit selectFrame(frame->frameNo(), frame->threadNo());
345
child = child->nextSibling();
348
frame = findFrame(1, currentThread_);
349
Q_ASSERT( frame != 0 );
350
Q_ASSERT( !frame->needsVariables() );
352
// All over, nothing left to fetch.
353
// Return to frame 1, and prune the inactive items
354
// from the variable tree..
355
QApplication::restoreOverrideCursor();
356
emit selectFrame(1, currentThread_);
362
// **************************************************************************
364
void VariableTree::slotAddWatchExpression(const QString &watchVar)
366
new WatchVarItem(watchRoot(), watchVar, UNKNOWN_TYPE);
367
emit addWatchExpression(watchVar, true);
371
// **************************************************************************
373
void VariableTree::setFetchGlobals(bool fetch)
375
emit fetchGlobals(fetch);
378
// **************************************************************************
380
VarFrameRoot *VariableTree::findFrame(int frameNo, int threadNo) const
382
// frames only exist on the top level so we only need to
383
// check the siblings
384
QListViewItem *sibling = firstChild();
385
while (sibling != 0) {
386
if ( sibling->rtti() == RTTI_VAR_FRAME_ROOT
387
&& ((VarFrameRoot*) sibling)->frameNo() == frameNo
388
&& ((VarFrameRoot*) sibling)->threadNo() == threadNo )
390
return (VarFrameRoot*) sibling;
393
sibling = sibling->nextSibling();
399
// **************************************************************************
401
WatchRoot *VariableTree::watchRoot()
406
// **************************************************************************
408
GlobalRoot *VariableTree::globalRoot()
410
if (globalRoot_ == 0) {
411
globalRoot_ = new GlobalRoot(this);
417
// **************************************************************************
419
// Watch variables can be added before the start of a debugging session,
420
// so tell the controller about any already in the tree at start.
421
void VariableTree::resetWatchVars()
423
for (QListViewItem *child = watchRoot()->firstChild(); child != 0; child = child->nextSibling()) {
424
((WatchVarItem*) child)->setDisplayId(-1);
425
emit addWatchExpression(child->text(VAR_NAME_COLUMN), false);
429
// **************************************************************************
431
void VariableTree::maybeTip(const QPoint &p)
433
VarItem * item = dynamic_cast<VarItem*>( itemAt(p) );
435
QRect r = itemRect(item);
437
tip(r, item->tipText());
443
// **************************************************************************
444
// **************************************************************************
445
// **************************************************************************
447
LazyFetchItem::LazyFetchItem(VariableTree *parent)
448
: KListViewItem(parent),
450
waitingForData_(false)
455
// **************************************************************************
457
LazyFetchItem::LazyFetchItem(LazyFetchItem *parent)
458
: KListViewItem(parent),
460
waitingForData_(false)
465
// **************************************************************************
467
LazyFetchItem::~LazyFetchItem()
471
// **************************************************************************
473
void LazyFetchItem::paintCell(QPainter *p, const QColorGroup &cg,
474
int column, int width, int align)
480
// make toplevel item (watch and frame items) names bold
481
if (column == VAR_NAME_COLUMN && parent() == 0) {
487
QListViewItem::paintCell( p, cg, column, width, align );
490
// **************************************************************************
492
VarItem *LazyFetchItem::findItem(const QString &name) const
494
QListViewItem *child = firstChild();
496
// Check the siblings on this branch
498
if (child->text(VAR_NAME_COLUMN) == name) {
499
return (VarItem*) child;
502
child = child->nextSibling();
508
// **************************************************************************
510
void LazyFetchItem::prune()
512
QListViewItem *child = firstChild();
515
LazyFetchItem *item = (LazyFetchItem*) child;
516
child = child->nextSibling();
517
// Never prune a branch if we are waiting on data to arrive.
518
if (!waitingForData_) {
519
if (item->isActive()) {
529
// **************************************************************************
530
// **************************************************************************
531
// **************************************************************************
533
VarItem::VarItem(LazyFetchItem *parent, const QString &varName, DataType dataType)
534
: LazyFetchItem (parent),
539
setText(VAR_NAME_COLUMN, varName);
540
setSelectable(false);
542
// Order the VarItems so that globals are first, then
543
// constants, class variables, instance variables and
544
// finally local variables
546
// Matches either an array element or a string slice,
547
// Order on the array index or the first number in the
548
// range specifying the slice.
549
QRegExp arrayelement_re("\\[(\\d+)(\\.\\.\\d+)?\\]");
552
if (arrayelement_re.search(varName) != -1) {
553
key_.sprintf("%.6d", arrayelement_re.cap(1).toInt());
554
} else if (key_.startsWith("$")) {
555
key_.prepend("1001"); // Global variable
556
} else if (QRegExp("^[A-Z]").search(varName) != -1) {
557
key_.prepend("1002"); // Constant
558
} else if (key_.startsWith("@@")) {
559
key_.prepend("1003"); // Class variable
560
} else if (key_.startsWith("@")) {
561
key_.prepend("1004"); // Instance variable
563
key_.prepend("1005"); // Local variable or parameter
566
// kdDebug(9012) << " ### VarItem::VarItem *CONSTR* " << varName << endl;
569
// **************************************************************************
575
QString VarItem::key(int /*column*/, bool /*ascending*/) const
580
// **************************************************************************
582
// Returns the path of a ruby item. If it is an instance variable, assume
583
// that there is an attr_accessor method for it.
584
// For example, @foobar within instance obj is accessed as obj.foobar.
585
// But don't strip off the @ for an instance variable with no path,
586
// and leave a plain '@foobar' as it is.
587
QString VarItem::fullName() const
589
QString itemName = text(VAR_NAME_COLUMN);
591
const VarItem *item = this;
593
if (item->parent()->rtti() != RTTI_VAR_ITEM) {
597
// This stops at the root item (FrameRoot or GlobalRoot)
598
while (item->rtti() == RTTI_VAR_ITEM) {
599
QString itemName = item->text(VAR_NAME_COLUMN);
601
if (vPath.startsWith("[")) {
602
// If it's a Hash or an Array, then just insert the value. As
603
// in adding '[0]' to foo.bar to give foo.bar[0]
604
vPath.prepend(itemName);
606
if (vPath.isEmpty()) {
609
vPath.prepend(itemName + ".");
612
item = (VarItem*) item->parent();
615
// Change 'self.@foobar' to '@foobar'
616
vPath.replace(QRegExp("^self\\.@"), "@");
618
// Use instance_variable_get() to access any '@var's in the middle of a path
619
QRegExp re_instance_var("\\.(@[^.]+)");
620
int pos = re_instance_var.search(vPath);
623
re_instance_var.matchedLength(),
624
QString(".instance_variable_get(:") + re_instance_var.cap(1) + ")" );
625
pos = re_instance_var.search(vPath, pos);
631
// **************************************************************************
633
void VarItem::setText(int column, const QString &data)
637
if (column == VALUE_COLUMN) {
638
highlight_ = (!text(VALUE_COLUMN).isEmpty() && text(VALUE_COLUMN) != data);
641
QListViewItem::setText(column, data);
645
// **************************************************************************
647
void VarItem::expandValue(char *buf)
649
LazyFetchItem::stopWaitingForData();
650
RDBParser::parseExpandedVariable(this, buf);
653
// **************************************************************************
655
void VarItem::setOpen(bool open)
657
QListViewItem::setOpen(open);
659
Q_ASSERT( dataType_ == REFERENCE_TYPE
660
|| dataType_ == ARRAY_TYPE
661
|| dataType_ == HASH_TYPE
662
|| dataType_ == STRING_TYPE
663
|| dataType_ == STRUCT_TYPE );
669
// **************************************************************************
671
void VarItem::update()
674
startWaitingForData();
675
// emit ((VariableTree*)listView())->expandItem(this, fullName().latin1());
676
((VariableTree*)listView())->expandItem(this, fullName().latin1());
682
// **************************************************************************
684
DataType VarItem::dataType() const
689
// **************************************************************************
691
void VarItem::setDataType(DataType dataType)
693
dataType_ = dataType;
696
// **************************************************************************
698
// Overridden to highlight the changed items
699
void VarItem::paintCell(QPainter *p, const QColorGroup &cg,
700
int column, int width, int align)
706
if (column == VALUE_COLUMN) {
707
// Show color values as colors, and make the text color the same
709
if (dataType_ == COLOR_TYPE) {
710
QRegExp color_re("\\s(#.*)>");
712
if (color_re.search(text(column)) != -1) {
713
QColorGroup color_cg( cg.foreground(), cg.background(),
714
cg.light(), cg.dark(), cg.mid(),
715
QColor(color_re.cap(1)), QColor(color_re.cap(1)) );
716
QListViewItem::paintCell(p, color_cg, column, width, align);
721
// Highlight recently changed items in red
723
QColorGroup hl_cg( cg.foreground(), cg.background(),
724
cg.light(), cg.dark(), cg.mid(),
726
QListViewItem::paintCell(p, hl_cg, column, width, align);
731
QListViewItem::paintCell(p, cg, column, width, align);
735
// **************************************************************************
737
QString VarItem::tipText() const
739
const unsigned int MAX_TOOLTIP_SIZE = 70;
740
QString tip = text(VALUE_COLUMN);
742
if (tip.length() < MAX_TOOLTIP_SIZE) {
745
return tip.mid(0, MAX_TOOLTIP_SIZE - 1) + " [...]";
749
// **************************************************************************
750
// **************************************************************************
751
// **************************************************************************
753
VarFrameRoot::VarFrameRoot(VariableTree *parent, int frameNo, int threadNo)
754
: LazyFetchItem(parent),
755
needsVariables_(true),
763
// **************************************************************************
765
VarFrameRoot::~VarFrameRoot()
769
// **************************************************************************
771
void VarFrameRoot::addLocals(char *variables)
773
cache_.append(variables);
776
// **************************************************************************
778
void VarFrameRoot::setLocals()
780
RDBParser::parseVariables(this, cache_.data());
782
needsVariables_ = false;
783
stopWaitingForData();
789
// **************************************************************************
791
// Override setOpen so that we can decide what to do when we do change
793
void VarFrameRoot::setOpen(bool open)
795
bool localsViewChanged = (isOpen() != open);
796
QListViewItem::setOpen(open);
798
if (localsViewChanged) {
799
((VariableTree*)listView())->selectFrame(frameNo_, threadNo_);
805
void VarFrameRoot::setFrameName(const QString &frameName)
807
setText(VAR_NAME_COLUMN, frameName);
808
setText(VALUE_COLUMN, "");
813
void VarFrameRoot::setActivationId()
815
LazyFetchItem::setActivationId();
816
stopWaitingForData();
817
needsVariables_ = true;
821
bool VarFrameRoot::needsVariables() const
823
return ( text(VAR_NAME_COLUMN).contains("try_initialize") == 0
825
&& !isWaitingForData()
826
&& needsVariables_ );
829
// **************************************************************************
830
// **************************************************************************
831
// **************************************************************************
832
// **************************************************************************
835
GlobalRoot::GlobalRoot(VariableTree *parent)
836
: LazyFetchItem(parent)
838
setText(0, i18n("Global"));
841
setSelectable(false);
844
// **************************************************************************
846
GlobalRoot::~GlobalRoot()
850
// **************************************************************************
852
void GlobalRoot::setGlobals(char * globals)
855
RDBParser::parseVariables(this, globals);
860
// **************************************************************************
862
void GlobalRoot::setOpen(bool open)
864
bool globalsViewChanged = (isOpen() != open);
865
QListViewItem::setOpen(open);
867
if (globalsViewChanged) {
868
((VariableTree*)listView())->setFetchGlobals(isOpen());
874
// **************************************************************************
875
// **************************************************************************
876
// **************************************************************************
877
// **************************************************************************
879
WatchVarItem::WatchVarItem( LazyFetchItem *parent, const QString &varName, DataType dataType, int displayId )
880
: VarItem(parent, varName, dataType),
881
displayId_(displayId)
885
// **************************************************************************
887
WatchVarItem::~WatchVarItem()
891
// **************************************************************************
893
void WatchVarItem::setDisplayId(int id)
898
// **************************************************************************
900
int WatchVarItem::displayId()
905
// **************************************************************************
906
// **************************************************************************
907
// **************************************************************************
908
// **************************************************************************
910
WatchRoot::WatchRoot(VariableTree *parent)
911
: LazyFetchItem(parent)
913
setText(VAR_NAME_COLUMN, i18n("Watch"));
915
setSelectable(false);
918
// **************************************************************************
920
WatchRoot::~WatchRoot()
924
// **************************************************************************
926
// Sets the initial value of a new Watch item, along with the
928
void WatchRoot::setWatchExpression(char * buf, char * expression)
930
QString expr(expression);
931
QRegExp display_re("^(\\d+):\\s([^\n]+)\n");
933
for ( QListViewItem *child = firstChild();
935
child = child->nextSibling() )
937
WatchVarItem *varItem = (WatchVarItem*) child;
938
if ( varItem->text(VAR_NAME_COLUMN) == expr
939
&& varItem->displayId() == -1
940
&& display_re.search(buf) >= 0 )
942
varItem->setDisplayId(display_re.cap(1).toInt());
943
// Skip over the 'thing = ' part of expr to get the value
944
varItem->setText( VALUE_COLUMN,
945
display_re.cap(2).mid(varItem->text(VAR_NAME_COLUMN).length() + strlen(" = ")) );
951
// After a program pause, this updates the new value of a Watch item
952
// expr is the thing = value part of "1: a = 1", id is the display number
953
void WatchRoot::updateWatchExpression(int id, const QString& expr)
955
for ( QListViewItem *child = firstChild();
957
child = child->nextSibling() )
959
WatchVarItem *varItem = (WatchVarItem*) child;
960
if (varItem->displayId() == id) {
961
Q_ASSERT( expr.startsWith(varItem->text(VAR_NAME_COLUMN)) );
962
// Skip over the 'thing = ' part of expr to get the value
963
varItem->setText( VALUE_COLUMN,
964
expr.mid(varItem->text(VAR_NAME_COLUMN).length() + strlen(" = ")) );
970
void WatchRoot::savePartialProjectSession(QDomElement* el)
972
QDomDocument domDoc = el->ownerDocument();
973
if (domDoc.isNull()) {
977
QDomElement watchEl = domDoc.createElement("watchExpressions");
979
for ( QListViewItem *child = firstChild();
981
child = child->nextSibling() )
983
QDomElement subEl = domDoc.createElement("el");
984
subEl.appendChild(domDoc.createTextNode(child->text(VAR_NAME_COLUMN)));
985
watchEl.appendChild(subEl);
988
if (!watchEl.isNull()) {
989
el->appendChild(watchEl);
995
void WatchRoot::restorePartialProjectSession(const QDomElement* el)
997
QDomDocument domDoc = el->ownerDocument();
998
if (domDoc.isNull()) {
1002
QDomElement watchEl = el->namedItem("watchExpressions").toElement();
1003
QDomElement subEl = watchEl.firstChild().toElement();
1005
while (!subEl.isNull()) {
1006
new WatchVarItem(this, subEl.firstChild().toText().data(), UNKNOWN_TYPE);
1008
subEl = subEl.nextSibling().toElement();
1014
// **************************************************************************
1015
// **************************************************************************
1016
// **************************************************************************
1021
#include "variablewidget.moc"