Merge with 2.0.
[kdbg.git] / kdbg / sourcewnd.cpp
blobad8f2d09f560b2609d4ac88bfbef5bd7ca15d2eb
1 // $Id$
3 // Copyright by Johannes Sixt
4 // This file is under GPL, the GNU General Public Licence
6 #include "debugger.h"
7 #include "sourcewnd.h"
8 #include <qtextstream.h>
9 #include <qpainter.h>
10 #include <qbrush.h>
11 #include <qfile.h>
12 #include <qkeycode.h>
13 #include <kapp.h>
14 #include <kiconloader.h>
15 #include <kglobalsettings.h>
16 #ifdef HAVE_CONFIG_H
17 #include "config.h"
18 #endif
19 #include "mydebug.h"
22 SourceWindow::SourceWindow(const char* fileName, QWidget* parent, const char* name) :
23 KTextView(parent, name),
24 m_fileName(fileName)
26 setNumCols(3);
28 // load pixmaps
29 m_pcinner = UserIcon("pcinner");
30 m_pcup = UserIcon("pcup");
31 m_brkena = UserIcon("brkena");
32 m_brkdis = UserIcon("brkdis");
33 m_brktmp = UserIcon("brktmp");
34 m_brkcond = UserIcon("brkcond");
35 m_brkorph = UserIcon("brkorph");
36 setFont(KGlobalSettings::fixedFont());
39 SourceWindow::~SourceWindow()
43 bool SourceWindow::loadFile()
45 // first we load the code into KTextView::m_texts
46 QFile f(m_fileName);
47 if (!f.open(IO_ReadOnly)) {
48 return false;
51 bool upd = autoUpdate();
52 setAutoUpdate(false);
54 QTextStream t(&f);
55 QString s;
56 while (!t.eof()) {
57 s = t.readLine();
58 insertLine(s);
60 f.close();
62 setAutoUpdate(upd);
63 if (upd) {
64 updateTableSize();
67 // then we copy it into our own m_sourceCode
68 m_sourceCode.setSize(m_texts.size());
69 m_rowToLine.setSize(m_texts.size());
70 m_lineItems.setSize(m_texts.size());
71 for (int i = 0; i < m_texts.size(); i++) {
72 m_sourceCode[i].code = m_texts[i];
73 m_rowToLine[i] = i;
74 m_lineItems[i] = 0;
77 return true;
80 void SourceWindow::reloadFile()
82 QFile f(m_fileName);
83 if (!f.open(IO_ReadOnly)) {
84 // open failed; leave alone
85 return;
88 // read text into m_sourceCode
89 m_sourceCode.setSize(0); /* clear old text */
91 QTextStream t(&f);
92 SourceLine s;
93 while (!t.eof()) {
94 s.code = t.readLine();
95 m_sourceCode.append(s);
97 f.close();
99 bool autoU = autoUpdate();
100 setAutoUpdate(false);
102 int lineNo = QMIN(m_texts.size(), m_sourceCode.size());
103 for (int i = 0; i < lineNo; i++) {
104 replaceLine(i, m_sourceCode[i].code);
106 if (m_sourceCode.size() > m_texts.size()) {
107 // the new file has more lines than the old one
108 // here lineNo is the number of lines of the old file
109 for (int i = lineNo; i < m_sourceCode.size(); i++) {
110 insertLine(m_sourceCode[i].code);
112 // allocate line items
113 m_lineItems.setSize(m_texts.size());
114 for (int i = m_texts.size()-1; i >= lineNo; i--) {
115 m_lineItems[i] = 0;
117 } else {
118 // the new file has fewer lines
119 // here lineNo is the number of lines of the new file
120 // remove the excessive lines
121 m_texts.setSize(lineNo);
122 m_lineItems.setSize(lineNo);
124 f.close();
126 setNumRows(m_texts.size());
128 m_rowToLine.setSize(m_texts.size());
129 for (int i = 0; i < m_texts.size(); i++)
130 m_rowToLine[i] = i;
132 setAutoUpdate(autoU);
133 if (autoU && isVisible()) {
134 updateTableSize();
135 repaint();
138 // if the cursor is in the deleted lines, move it to the last line
139 if (m_curRow >= m_texts.size()) {
140 m_curRow = -1; /* at this point don't have an active row */
141 activateLine(m_texts.size()-1); /* now we have */
145 void SourceWindow::scrollTo(int lineNo, const DbgAddr& address)
147 if (lineNo < 0 || lineNo >= m_sourceCode.size())
148 return;
150 int row = lineToRow(lineNo, address);
151 scrollToRow(row);
154 void SourceWindow::scrollToRow(int row)
156 if (row >= numRows())
157 row = numRows();
159 // scroll to lineNo
160 if (row >= topCell() && row <= lastRowVisible()) {
161 // line is already visible
162 setCursorPosition(row, 0);
163 return;
166 // setCursorPosition does only a half-hearted job of making the
167 // cursor line visible, so we do it by hand...
168 setCursorPosition(row, 0);
169 row -= 5;
170 if (row < 0)
171 row = 0;
173 setTopCell(row);
176 int SourceWindow::textCol() const
178 // text is in column 2
179 return 2;
182 int SourceWindow::cellWidth(int col) const
184 if (col == 0) {
185 return 15;
186 } else if (col == 1) {
187 return 12;
188 } else {
189 return KTextView::cellWidth(col);
193 void SourceWindow::paintCell(QPainter* p, int row, int col)
195 if (col == textCol()) {
196 p->save();
197 if (isRowDisassCode(row)) {
198 p->setPen(blue);
200 KTextView::paintCell(p, row, col);
201 p->restore();
202 return;
204 if (col == 0) {
205 uchar item = m_lineItems[row];
206 if (item == 0) /* shortcut out */
207 return;
208 int h = cellHeight(row);
209 if (item & liBP) {
210 // enabled breakpoint
211 int y = (h - m_brkena.height())/2;
212 if (y < 0) y = 0;
213 p->drawPixmap(0,y,m_brkena);
215 if (item & liBPdisabled) {
216 // disabled breakpoint
217 int y = (h - m_brkdis.height())/2;
218 if (y < 0) y = 0;
219 p->drawPixmap(0,y,m_brkdis);
221 if (item & liBPtemporary) {
222 // temporary breakpoint marker
223 int y = (h - m_brktmp.height())/2;
224 if (y < 0) y = 0;
225 p->drawPixmap(0,y,m_brktmp);
227 if (item & liBPconditional) {
228 // conditional breakpoint marker
229 int y = (h - m_brkcond.height())/2;
230 if (y < 0) y = 0;
231 p->drawPixmap(0,y,m_brkcond);
233 if (item & liBPorphan) {
234 // orphaned breakpoint marker
235 int y = (h - m_brkcond.height())/2;
236 if (y < 0) y = 0;
237 p->drawPixmap(0,y,m_brkorph);
239 if (item & liPC) {
240 // program counter in innermost frame
241 int y = (h - m_pcinner.height())/2;
242 if (y < 0) y = 0;
243 p->drawPixmap(0,y,m_pcinner);
245 if (item & liPCup) {
246 // program counter somewhere up the stack
247 int y = (h - m_pcup.height())/2;
248 if (y < 0) y = 0;
249 p->drawPixmap(0,y,m_pcup);
251 return;
253 if (col == 1) {
254 if (!isRowDisassCode(row) && m_sourceCode[rowToLine(row)].canDisass) {
255 int h = cellHeight(row);
256 int w = cellWidth(col);
257 int x = w/2;
258 int y = h/2;
259 p->drawLine(x-2, y, x+2, y);
260 if (!isRowExpanded(row)) {
261 p->drawLine(x, y-2, x, y+2);
267 void SourceWindow::updateLineItems(const KDebugger* dbg)
269 // clear outdated breakpoints
270 for (int i = m_lineItems.size()-1; i >= 0; i--) {
271 if (m_lineItems[i] & liBPany) {
272 // check if this breakpoint still exists
273 int line = rowToLine(i);
274 TRACE(QString().sprintf("checking for bp at %d", line));
275 int j;
276 for (j = dbg->numBreakpoints()-1; j >= 0; j--) {
277 const Breakpoint* bp = dbg->breakpoint(j);
278 if (bp->lineNo == line &&
279 fileNameMatches(bp->fileName) &&
280 lineToRow(line, bp->address) == i)
282 // yes it exists; mode is changed below
283 break;
286 if (j < 0) {
287 /* doesn't exist anymore, remove it */
288 m_lineItems[i] &= ~liBPany;
289 updateLineItem(i);
294 // add new breakpoints
295 for (int j = dbg->numBreakpoints()-1; j >= 0; j--) {
296 const Breakpoint* bp = dbg->breakpoint(j);
297 if (fileNameMatches(bp->fileName)) {
298 TRACE(QString().sprintf("updating %s:%d", bp->fileName.data(), bp->lineNo));
299 int i = bp->lineNo;
300 if (i < 0 || i >= m_sourceCode.size())
301 continue;
302 // compute new line item flags for breakpoint
303 uchar flags = bp->enabled ? liBP : liBPdisabled;
304 if (bp->temporary)
305 flags |= liBPtemporary;
306 if (!bp->condition.isEmpty() || bp->ignoreCount != 0)
307 flags |= liBPconditional;
308 if (bp->isOrphaned())
309 flags |= liBPorphan;
310 // update if changed
311 int row = lineToRow(i, bp->address);
312 if ((m_lineItems[row] & liBPany) != flags) {
313 m_lineItems[row] &= ~liBPany;
314 m_lineItems[row] |= flags;
315 updateLineItem(row);
321 void SourceWindow::updateLineItem(int row)
323 updateCell(row, 0);
326 void SourceWindow::setPC(bool set, int lineNo, const DbgAddr& address, int frameNo)
328 if (lineNo < 0 || lineNo >= int(m_sourceCode.size())) {
329 return;
332 int row = lineToRow(lineNo, address);
334 uchar flag = frameNo == 0 ? liPC : liPCup;
335 if (set) {
336 // set only if not already set
337 if ((m_lineItems[row] & flag) == 0) {
338 m_lineItems[row] |= flag;
339 updateLineItem(row);
341 } else {
342 // clear only if not set
343 if ((m_lineItems[row] & flag) != 0) {
344 m_lineItems[row] &= ~flag;
345 updateLineItem(row);
350 void SourceWindow::find(const QString& text, bool caseSensitive, FindDirection dir)
352 ASSERT(dir == 1 || dir == -1);
353 if (m_sourceCode.size() == 0 || text.isEmpty())
354 return;
356 int line;
357 DbgAddr dummyAddr;
358 activeLine(line, dummyAddr);
359 if (line < 0)
360 line = 0;
361 int curLine = line; /* remember where we started */
362 bool found = false;
363 do {
364 // advance and wrap around
365 line += dir;
366 if (line < 0)
367 line = m_sourceCode.size()-1;
368 else if (line >= int(m_sourceCode.size()))
369 line = 0;
370 // search text
371 found = m_sourceCode[line].code.find(text, 0, caseSensitive) >= 0;
372 } while (!found && line != curLine);
374 scrollTo(line, DbgAddr());
377 void SourceWindow::mousePressEvent(QMouseEvent* ev)
379 // Check if right button was clicked.
380 if (ev->button() == RightButton)
382 emit clickedRight(ev->pos());
383 return;
386 int col = findCol(ev->x());
388 // check if event is in line item column
389 if (col == textCol()) {
390 KTextView::mousePressEvent(ev);
391 return;
394 // get row
395 int row = findRow(ev->y());
396 if (row < 0 || col < 0)
397 return;
399 if (col == 1) {
400 if (isRowExpanded(row)) {
401 actionCollapseRow(row);
402 } else {
403 actionExpandRow(row);
405 return;
408 int sourceRow;
409 int line = rowToLine(row, &sourceRow);
411 // find address if row is disassembled code
412 DbgAddr address;
413 if (row > sourceRow) {
414 // get offset from source code line
415 int off = row - sourceRow;
416 address = m_sourceCode[line].disassAddr[off-1];
419 switch (ev->button()) {
420 case LeftButton:
421 TRACE(QString().sprintf("left-clicked line %d", line));
422 emit clickedLeft(m_fileName, line, address,
423 (ev->state() & ShiftButton) != 0);
424 break;
425 case MidButton:
426 TRACE(QString().sprintf("mid-clicked row %d", line));
427 emit clickedMid(m_fileName, line, address);
428 break;
429 default:;
433 void SourceWindow::keyPressEvent(QKeyEvent* ev)
435 switch (ev->key()) {
436 case Key_Plus:
437 actionExpandRow(m_curRow);
438 return;
439 case Key_Minus:
440 actionCollapseRow(m_curRow);
441 return;
444 KTextView::keyPressEvent(ev);
447 static inline bool isident(QChar c)
449 return c.isLetterOrNumber() || c.latin1() == '_';
452 bool SourceWindow::wordAtPoint(const QPoint& p, QString& word, QRect& r)
454 if (findCol(p.x()) != textCol())
455 return false;
456 int row = findRow(p.y());
457 if (row < 0)
458 return false;
460 // find top-left corner of text cell
461 int top, left;
462 if (!colXPos(textCol(), &left))
463 return false;
464 if (!rowYPos(row, &top))
465 return false;
467 // get the bounding rect of the text
468 QPainter painter(this);
469 setupPainter(&painter);
470 const QString& text = m_texts[row];
471 QRect bound =
472 painter.boundingRect(left+2, top, 0,0,
473 AlignLeft | SingleLine | DontClip | ExpandTabs,
474 text, text.length());
475 if (!bound.contains(p))
476 return false; /* p is outside text */
479 * We split the line into words and check each whether it contains
480 * the point p. We must do it the hard way (measuring substrings)
481 * because we cannot rely on that the font is mono-spaced.
483 uint start = 0;
484 while (start < text.length()) {
485 while (start < text.length() && !isident(text[start]))
486 start++;
487 if (start >= text.length())
488 return false;
490 * If p is in the rectangle that ends at 'start', it is in the
491 * last non-word part that we have just skipped.
493 bound =
494 painter.boundingRect(left+2, top, 0,0,
495 AlignLeft | SingleLine | DontClip | ExpandTabs,
496 text, start);
497 if (bound.contains(p))
498 return false;
499 // a word starts now
500 int startWidth = bound.width();
501 uint end = start;
502 while (end < text.length() && isident(text[end]))
503 end++;
504 bound =
505 painter.boundingRect(left+2, top, 0,0,
506 AlignLeft | SingleLine | DontClip | ExpandTabs,
507 text, end);
508 if (bound.contains(p)) {
509 // we found a word!
510 // extract the word
511 word = text.mid(start, end-start);
512 // and the rectangle
513 r = QRect(bound.x()+startWidth,bound.y(),
514 bound.width()-startWidth, bound.height());
515 return true;
517 start = end;
519 return false;
522 void SourceWindow::paletteChange(const QPalette& oldPal)
524 setFont(KGlobalSettings::fixedFont());
525 KTextView::paletteChange(oldPal);
529 * Two file names (possibly full paths) match if the last parts - the file
530 * names - match.
532 bool SourceWindow::fileNameMatches(const QString& other)
534 const QString& me = fileName();
536 // check for null file names first
537 if (me.isNull() || other.isNull()) {
538 return me.isNull() && other.isNull();
542 * Get file names. Note: Either there is a slash, then skip it, or
543 * there is no slash, then -1 + 1 = 0!
545 int sme = me.findRev('/') + 1;
546 int sother = other.findRev('/') + 1;
547 return strcmp(me.data() + sme, other.data() + sother) == 0;
550 void SourceWindow::disassembled(int lineNo, const QList<DisassembledCode>& disass)
552 TRACE("disassembled line " + QString().setNum(lineNo));
553 if (lineNo < 0 || lineNo >= m_sourceCode.size())
554 return;
556 SourceLine& sl = m_sourceCode[lineNo];
558 // copy disassembled code and its addresses
559 sl.disass.setSize(disass.count());
560 sl.disassAddr.setSize(disass.count());
561 sl.canDisass = disass.count() > 0;
562 for (uint i = 0; i < disass.count(); i++) {
563 const DisassembledCode* c =
564 const_cast<QList<DisassembledCode>&>(disass).at(i);
565 sl.disass[i] = c->address.asString() + ' ' + c->code;
566 sl.disassAddr[i] = c->address;
569 int row = lineToRow(lineNo);
570 if (sl.canDisass) {
571 expandRow(row);
572 } else {
573 // clear expansion marker
574 updateCell(row, 1);
578 int SourceWindow::rowToLine(int row, int* sourceRow)
580 int line = row >= 0 ? m_rowToLine[row] : -1;
581 if (sourceRow != 0) {
582 // search back until we hit the first entry with the current line number
583 while (row > 0 && m_rowToLine[row-1] == line)
584 row--;
585 *sourceRow = row;
587 return line;
591 * Rows showing diassembled code have the same line number as the
592 * corresponding source code line number. Therefore, the line numbers in
593 * m_rowToLine are monotonically increasing with blocks of equal line
594 * numbers for a source line and its disassembled code that follows it.
596 * Hence, m_rowToLine always obeys the following condition:
598 * m_rowToLine[i] <= i
601 int SourceWindow::lineToRow(int line)
603 // line is zero-based!
605 assert(line < m_rowToLine.size());
607 // quick test for common case
608 if (line < 0 || m_rowToLine[line] == line)
609 return line;
611 assert(m_rowToLine[line] < line);
614 * Binary search between row == line and end of list. In the loop below
615 * we use the fact that the line numbers m_rowToLine do not contain
616 * holes.
618 int l = line;
619 int h = m_rowToLine.size();
620 while (l < h && m_rowToLine[l] != line)
622 assert(h == m_rowToLine.size() || m_rowToLine[l] < m_rowToLine[h]);
625 * We want to round down the midpoint so that we find the
626 * lowest row that belongs to the line we seek.
628 int mid = (l+h)/2;
629 if (m_rowToLine[mid] <= line)
630 l = mid;
631 else
632 h = mid;
634 // Found! Result is in l:
635 assert(m_rowToLine[l] == line);
638 * We might not have hit the lowest index for the line.
640 while (l > 0 && m_rowToLine[l-1] == line)
641 --l;
643 return l;
646 int SourceWindow::lineToRow(int line, const DbgAddr& address)
648 int row = lineToRow(line);
649 if (isRowExpanded(row)) {
650 row += m_sourceCode[line].findAddressRowOffset(address);
652 return row;
655 bool SourceWindow::isRowExpanded(int row)
657 assert(row >= 0);
658 return row < m_rowToLine.size()-1 &&
659 m_rowToLine[row] == m_rowToLine[row+1];
662 bool SourceWindow::isRowDisassCode(int row)
664 return row > 0 &&
665 m_rowToLine[row] == m_rowToLine[row-1];
668 void SourceWindow::expandRow(int row)
670 TRACE("expanding row " + QString().setNum(row));
671 // get disassembled code
672 int line = rowToLine(row);
673 const ValArray<QString>& disass = m_sourceCode[line].disass;
675 // remove PC (must be set again in slot of signal expanded())
676 m_lineItems[row] &= ~(liPC|liPCup);
678 // insert new lines
679 ++row;
680 m_rowToLine.insertAt(row, line, disass.size());
681 m_lineItems.insertAt(row, 0, disass.size());
682 m_texts.insertAt(row, disass);
684 bool autoU = autoUpdate();
685 setAutoUpdate(false);
687 // update line widths
688 for (int i = 0; i < disass.size(); i++) {
689 updateCellSize(disass[i]);
692 setNumRows(m_texts.size());
694 emit expanded(line); /* must set PC */
696 setAutoUpdate(autoU);
697 if (autoU && isVisible()) {
698 updateTableSize();
699 update();
703 void SourceWindow::collapseRow(int row)
705 TRACE("collapsing row " + QString().setNum(row));
706 int line = rowToLine(row);
708 // find end of this block
709 int end = row+1;
710 while (end < m_rowToLine.size() && m_rowToLine[end] == m_rowToLine[row]) {
711 end++;
713 ++row;
714 m_rowToLine.removeAt(row, end-row);
715 m_lineItems.removeAt(row, end-row);
716 m_texts.removeAt(row, end-row);
718 bool autoU = autoUpdate();
719 setAutoUpdate(false);
721 setNumRows(m_texts.size());
723 emit collapsed(line);
725 setAutoUpdate(autoU);
726 if (autoU && isVisible()) {
727 updateTableSize();
728 update();
732 void SourceWindow::activeLine(int& line, DbgAddr& address)
734 int row, col;
735 cursorPosition(&row, &col);
737 int sourceRow;
738 line = rowToLine(row, &sourceRow);
739 if (row > sourceRow) {
740 int off = row - sourceRow; /* offset from source line */
741 address = m_sourceCode[line].disassAddr[off-1];
746 * Returns the offset from the line displaying the source code to
747 * the line containing the specified address. If the address is not
748 * found, 0 is returned.
750 int SourceWindow::SourceLine::findAddressRowOffset(const DbgAddr& address) const
752 if (address.isEmpty())
753 return 0;
755 for (int i = 0; i < disassAddr.size(); i++) {
756 if (disassAddr[i] == address) {
757 // found exact address
758 return i+1;
760 if (disassAddr[i] > address) {
762 * We have already advanced too far; the address is before this
763 * index, but obviously we haven't found an exact match
764 * earlier. address is somewhere between the displayed
765 * addresses. We return the previous line.
767 return i;
770 // not found
771 return 0;
774 void SourceWindow::actionExpandRow(int row)
776 if (isRowExpanded(row) || isRowDisassCode(row)) {
777 // do nothing
778 return;
781 // disassemble
782 int line = rowToLine(row);
783 const SourceLine& sl = m_sourceCode[line];
784 if (!sl.canDisass)
785 return;
786 if (sl.disass.size() == 0) {
787 emit disassemble(m_fileName, line);
788 } else {
789 expandRow(row);
793 void SourceWindow::actionCollapseRow(int row)
795 if (!isRowExpanded(row) || isRowDisassCode(row)) {
796 // do nothing
797 return;
800 collapseRow(row);
804 #include "sourcewnd.moc"