When an executable is loaded be prepared for some messages from gdb,
[kdbg.git] / kdbg / sourcewnd.cpp
blob9f5900936749ec8196ea392352e27658e133f6db
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 = BarIcon("pcinner");
30 m_pcup = BarIcon("pcup");
31 m_brkena = BarIcon("brkena");
32 m_brkdis = BarIcon("brkdis");
33 m_brktmp = BarIcon("brktmp");
34 m_brkcond = BarIcon("brkcond");
35 m_brkorph = BarIcon("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 const QString& text = m_texts[row];
470 QRect bound =
471 painter.boundingRect(left+2, top, 0,0,
472 AlignLeft | SingleLine | DontClip | ExpandTabs,
473 text, text.length());
474 if (!bound.contains(p))
475 return false; /* p is outside text */
478 * We split the line into words and check each whether it contains
479 * the point p. We must do it the hard way (measuring substrings)
480 * because we cannot rely on that the font is mono-spaced.
482 uint start = 0;
483 while (start < text.length()) {
484 while (start < text.length() && !isident(text[start]))
485 start++;
486 if (start >= text.length())
487 return false;
489 * If p is in the rectangle that ends at 'start', it is in the
490 * last non-word part that we have just skipped.
492 bound =
493 painter.boundingRect(left+2, top, 0,0,
494 AlignLeft | SingleLine | DontClip | ExpandTabs,
495 text, start);
496 if (bound.contains(p))
497 return false;
498 // a word starts now
499 int startWidth = bound.width();
500 uint end = start;
501 while (end < text.length() && isident(text[end]))
502 end++;
503 bound =
504 painter.boundingRect(left+2, top, 0,0,
505 AlignLeft | SingleLine | DontClip | ExpandTabs,
506 text, end);
507 if (bound.contains(p)) {
508 // we found a word!
509 // extract the word
510 word = text.mid(start, end-start);
511 // and the rectangle
512 r = QRect(bound.x()+startWidth,bound.y(),
513 bound.width()-startWidth, bound.height());
514 return true;
516 start = end;
518 return false;
521 void SourceWindow::paletteChange(const QPalette& oldPal)
523 setFont(KGlobalSettings::fixedFont());
524 KTextView::paletteChange(oldPal);
528 * Two file names (possibly full paths) match if the last parts - the file
529 * names - match.
531 bool SourceWindow::fileNameMatches(const QString& other)
533 const QString& me = fileName();
535 // check for null file names first
536 if (me.isNull() || other.isNull()) {
537 return me.isNull() && other.isNull();
541 * Get file names. Note: Either there is a slash, then skip it, or
542 * there is no slash, then -1 + 1 = 0!
544 int sme = me.findRev('/') + 1;
545 int sother = other.findRev('/') + 1;
546 return strcmp(me.data() + sme, other.data() + sother) == 0;
549 void SourceWindow::disassembled(int lineNo, const QList<DisassembledCode>& disass)
551 TRACE("disassembled line " + QString().setNum(lineNo));
552 if (lineNo < 0 || lineNo >= m_sourceCode.size())
553 return;
555 SourceLine& sl = m_sourceCode[lineNo];
557 // copy disassembled code and its addresses
558 sl.disass.setSize(disass.count());
559 sl.disassAddr.setSize(disass.count());
560 sl.canDisass = disass.count() > 0;
561 for (uint i = 0; i < disass.count(); i++) {
562 const DisassembledCode* c =
563 const_cast<QList<DisassembledCode>&>(disass).at(i);
564 sl.disass[i] = c->address.asString() + ' ' + c->code;
565 sl.disassAddr[i] = c->address;
568 int row = lineToRow(lineNo);
569 if (sl.canDisass) {
570 expandRow(row);
571 } else {
572 // clear expansion marker
573 updateCell(row, 1);
577 int SourceWindow::rowToLine(int row, int* sourceRow)
579 int line = row >= 0 ? m_rowToLine[row] : -1;
580 if (sourceRow != 0) {
581 // search back until we hit the first entry with the current line number
582 while (row > 0 && m_rowToLine[row-1] == line)
583 row--;
584 *sourceRow = row;
586 return line;
590 * Rows showing diassembled code have the same line number as the
591 * corresponding source code line number. Therefore, the line numbers in
592 * m_rowToLine are monotonically increasing with blocks of equal line
593 * numbers for a source line and its disassembled code that follows it.
595 * Hence, m_rowToLine always obeys the following condition:
597 * m_rowToLine[i] <= i
600 int SourceWindow::lineToRow(int line)
602 // line is zero-based!
604 assert(line < m_rowToLine.size());
606 // quick test for common case
607 if (line < 0 || m_rowToLine[line] == line)
608 return line;
610 assert(m_rowToLine[line] < line);
613 * Binary search between row == line and end of list. In the loop below
614 * we use the fact that the line numbers m_rowToLine do not contain
615 * holes.
617 int l = line;
618 int h = m_rowToLine.size();
619 while (l < h && m_rowToLine[l] != line)
621 assert(h == m_rowToLine.size() || m_rowToLine[l] < m_rowToLine[h]);
624 * We want to round down the midpoint so that we find the
625 * lowest row that belongs to the line we seek.
627 int mid = (l+h)/2;
628 if (m_rowToLine[mid] <= line)
629 l = mid;
630 else
631 h = mid;
633 // Found! Result is in l:
634 assert(m_rowToLine[l] == line);
637 * We might not have hit the lowest index for the line.
639 while (l > 0 && m_rowToLine[l-1] == line)
640 --l;
642 return l;
645 int SourceWindow::lineToRow(int line, const DbgAddr& address)
647 int row = lineToRow(line);
648 if (isRowExpanded(row)) {
649 row += m_sourceCode[line].findAddressRowOffset(address);
651 return row;
654 bool SourceWindow::isRowExpanded(int row)
656 assert(row >= 0);
657 return row < m_rowToLine.size()-1 &&
658 m_rowToLine[row] == m_rowToLine[row+1];
661 bool SourceWindow::isRowDisassCode(int row)
663 return row > 0 &&
664 m_rowToLine[row] == m_rowToLine[row-1];
667 void SourceWindow::expandRow(int row)
669 TRACE("expanding row " + QString().setNum(row));
670 // get disassembled code
671 int line = rowToLine(row);
672 const ValArray<QString>& disass = m_sourceCode[line].disass;
674 // remove PC (must be set again in slot of signal expanded())
675 m_lineItems[row] &= ~(liPC|liPCup);
677 // insert new lines
678 ++row;
679 m_rowToLine.insertAt(row, line, disass.size());
680 m_lineItems.insertAt(row, 0, disass.size());
681 m_texts.insertAt(row, disass);
683 bool autoU = autoUpdate();
684 setAutoUpdate(false);
686 // update line widths
687 for (int i = 0; i < disass.size(); i++) {
688 updateCellSize(disass[i]);
691 setNumRows(m_texts.size());
693 emit expanded(line); /* must set PC */
695 setAutoUpdate(autoU);
696 if (autoU && isVisible()) {
697 updateTableSize();
698 update();
702 void SourceWindow::collapseRow(int row)
704 TRACE("collapsing row " + QString().setNum(row));
705 int line = rowToLine(row);
707 // find end of this block
708 int end = row+1;
709 while (end < m_rowToLine.size() && m_rowToLine[end] == m_rowToLine[row]) {
710 end++;
712 ++row;
713 m_rowToLine.removeAt(row, end-row);
714 m_lineItems.removeAt(row, end-row);
715 m_texts.removeAt(row, end-row);
717 bool autoU = autoUpdate();
718 setAutoUpdate(false);
720 setNumRows(m_texts.size());
722 emit collapsed(line);
724 setAutoUpdate(autoU);
725 if (autoU && isVisible()) {
726 updateTableSize();
727 update();
731 void SourceWindow::activeLine(int& line, DbgAddr& address)
733 int row, col;
734 cursorPosition(&row, &col);
736 int sourceRow;
737 line = rowToLine(row, &sourceRow);
738 if (row > sourceRow) {
739 int off = row - sourceRow; /* offset from source line */
740 address = m_sourceCode[line].disassAddr[off-1];
745 * Returns the offset from the line displaying the source code to
746 * the line containing the specified address. If the address is not
747 * found, 0 is returned.
749 int SourceWindow::SourceLine::findAddressRowOffset(const DbgAddr& address) const
751 if (address.isEmpty())
752 return 0;
754 for (int i = 0; i < disassAddr.size(); i++) {
755 if (disassAddr[i] == address) {
756 // found exact address
757 return i+1;
759 if (disassAddr[i] > address) {
761 * We have already advanced too far; the address is before this
762 * index, but obviously we haven't found an exact match
763 * earlier. address is somewhere between the displayed
764 * addresses. We return the previous line.
766 return i;
769 // not found
770 return 0;
773 void SourceWindow::actionExpandRow(int row)
775 if (isRowExpanded(row) || isRowDisassCode(row)) {
776 // do nothing
777 return;
780 // disassemble
781 int line = rowToLine(row);
782 const SourceLine& sl = m_sourceCode[line];
783 if (!sl.canDisass)
784 return;
785 if (sl.disass.size() == 0) {
786 emit disassemble(m_fileName, line);
787 } else {
788 expandRow(row);
792 void SourceWindow::actionCollapseRow(int row)
794 if (!isRowExpanded(row) || isRowDisassCode(row)) {
795 // do nothing
796 return;
799 collapseRow(row);
803 #include "sourcewnd.moc"