Make toolbar visible again.
[kdbg.git] / kdbg / sourcewnd.cpp
blobe4180ad470b94176d5990775b16ae3aa4d9b572c
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 #if QT_VERSION >= 200
16 #include <kglobalsettings.h>
17 #else
18 #include <ctype.h>
19 #endif
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23 #include "mydebug.h"
26 SourceWindow::SourceWindow(const char* fileName, QWidget* parent, const char* name) :
27 KTextView(parent, name),
28 m_fileName(fileName)
30 setNumCols(3);
32 // load pixmaps
33 #if QT_VERSION < 200
34 KIconLoader* loader = kapp->getIconLoader();
35 m_pcinner = loader->loadIcon("pcinner.xpm");
36 m_pcup = loader->loadIcon("pcup.xpm");
37 m_brkena = loader->loadIcon("brkena.xpm");
38 m_brkdis = loader->loadIcon("brkdis.xpm");
39 m_brktmp = loader->loadIcon("brktmp.xpm");
40 m_brkcond = loader->loadIcon("brkcond.xpm");
41 setFont(kapp->fixedFont);
42 #else
43 m_pcinner = BarIcon("pcinner");
44 m_pcup = BarIcon("pcup");
45 m_brkena = BarIcon("brkena");
46 m_brkdis = BarIcon("brkdis");
47 m_brktmp = BarIcon("brktmp");
48 m_brkcond = BarIcon("brkcond");
49 setFont(KGlobalSettings::fixedFont());
50 #endif
53 SourceWindow::~SourceWindow()
57 void SourceWindow::loadFile()
59 // first we load the code into KTextView::m_texts
60 QFile f(m_fileName);
61 if (f.open(IO_ReadOnly)) {
62 QTextStream t(&f);
63 QString s;
64 while (!t.eof()) {
65 s = t.readLine();
66 insertLine(s);
68 f.close();
71 // then we copy it into our own m_sourceCode
72 m_sourceCode.setSize(m_texts.size());
73 m_rowToLine.setSize(m_texts.size());
74 m_lineItems.setSize(m_texts.size());
75 for (int i = 0; i < m_texts.size(); i++) {
76 m_sourceCode[i].code = m_texts[i];
77 m_rowToLine[i] = i;
78 m_lineItems[i] = 0;
82 void SourceWindow::reloadFile()
84 QFile f(m_fileName);
85 if (!f.open(IO_ReadOnly)) {
86 // open failed; leave alone
87 return;
90 // read text into m_sourceCode
91 m_sourceCode.setSize(0); /* clear old text */
93 QTextStream t(&f);
94 SourceLine s;
95 while (!t.eof()) {
96 s.code = t.readLine();
97 m_sourceCode.append(s);
99 f.close();
101 bool autoU = autoUpdate();
102 setAutoUpdate(false);
104 int lineNo = QMIN(m_texts.size(), m_sourceCode.size());
105 for (int i = 0; i < lineNo; i++) {
106 replaceLine(i, m_sourceCode[i].code);
108 if (m_sourceCode.size() > m_texts.size()) {
109 // the new file has more lines than the old one
110 // here lineNo is the number of lines of the old file
111 for (int i = lineNo; i < m_sourceCode.size(); i++) {
112 insertLine(m_sourceCode[i].code);
114 // allocate line items
115 m_lineItems.setSize(m_texts.size());
116 for (int i = m_texts.size()-1; i >= lineNo; i--) {
117 m_lineItems[i] = 0;
119 } else {
120 // the new file has fewer lines
121 // here lineNo is the number of lines of the new file
122 // remove the excessive lines
123 m_texts.setSize(lineNo);
124 m_lineItems.setSize(lineNo);
125 // if the cursor is in the deleted lines, move it to the last line
126 if (m_curRow >= lineNo) {
127 m_curRow = -1; /* at this point don't have an active row */
128 activateLine(lineNo-1); /* now we have */
131 f.close();
133 setNumRows(m_texts.size());
135 m_rowToLine.setSize(m_texts.size());
136 for (int i = 0; i < m_texts.size(); i++)
137 m_rowToLine[i] = i;
139 setAutoUpdate(autoU);
140 if (autoU && isVisible()) {
141 repaint();
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)
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 & liPC) {
234 // program counter in innermost frame
235 int y = (h - m_pcinner.height())/2;
236 if (y < 0) y = 0;
237 p->drawPixmap(0,y,m_pcinner);
239 if (item & liPCup) {
240 // program counter somewhere up the stack
241 int y = (h - m_pcup.height())/2;
242 if (y < 0) y = 0;
243 p->drawPixmap(0,y,m_pcup);
245 return;
247 if (col == 1) {
248 if (!isRowDisassCode(row) && m_sourceCode[rowToLine(row)].canDisass) {
249 int h = cellHeight(row);
250 int w = cellWidth(col);
251 int x = w/2;
252 int y = h/2;
253 p->drawLine(x-2, y, x+2, y);
254 if (!isRowExpanded(row)) {
255 p->drawLine(x, y-2, x, y+2);
261 void SourceWindow::updateLineItems(const KDebugger* dbg)
263 // clear outdated breakpoints
264 for (int i = m_lineItems.size()-1; i >= 0; i--) {
265 if (m_lineItems[i] & liBPany) {
266 // check if this breakpoint still exists
267 int line = rowToLine(i);
268 TRACE(QString().sprintf("checking for bp at %d", line));
269 int j;
270 for (j = dbg->numBreakpoints()-1; j >= 0; j--) {
271 const Breakpoint* bp = dbg->breakpoint(j);
272 if (bp->lineNo == line &&
273 fileNameMatches(bp->fileName) &&
274 lineToRow(line, bp->address) == i)
276 // yes it exists; mode is changed below
277 break;
280 if (j < 0) {
281 /* doesn't exist anymore, remove it */
282 m_lineItems[i] &= ~liBPany;
283 updateLineItem(i);
288 // add new breakpoints
289 for (int j = dbg->numBreakpoints()-1; j >= 0; j--) {
290 const Breakpoint* bp = dbg->breakpoint(j);
291 if (fileNameMatches(bp->fileName)) {
292 TRACE(QString().sprintf("updating %s:%d", bp->fileName.data(), bp->lineNo));
293 int i = bp->lineNo;
294 if (i < 0 || i >= m_sourceCode.size())
295 continue;
296 // compute new line item flags for breakpoint
297 uchar flags = bp->enabled ? liBP : liBPdisabled;
298 if (bp->temporary)
299 flags |= liBPtemporary;
300 if (!bp->condition.isEmpty() || bp->ignoreCount != 0)
301 flags |= liBPconditional;
302 // update if changed
303 int row = lineToRow(i, bp->address);
304 if ((m_lineItems[row] & liBPany) != flags) {
305 m_lineItems[row] &= ~liBPany;
306 m_lineItems[row] |= flags;
307 updateLineItem(row);
313 void SourceWindow::updateLineItem(int row)
315 updateCell(row, 0);
318 void SourceWindow::setPC(bool set, int lineNo, const DbgAddr& address, int frameNo)
320 if (lineNo < 0 || lineNo >= int(m_sourceCode.size())) {
321 return;
324 int row = lineToRow(lineNo, address);
326 uchar flag = frameNo == 0 ? liPC : liPCup;
327 if (set) {
328 // set only if not already set
329 if ((m_lineItems[row] & flag) == 0) {
330 m_lineItems[row] |= flag;
331 updateLineItem(row);
333 } else {
334 // clear only if not set
335 if ((m_lineItems[row] & flag) != 0) {
336 m_lineItems[row] &= ~flag;
337 updateLineItem(row);
342 void SourceWindow::find(const QString& text, bool caseSensitive, FindDirection dir)
344 ASSERT(dir == 1 || dir == -1);
345 if (m_sourceCode.size() == 0 || text.isEmpty())
346 return;
348 int line;
349 DbgAddr dummyAddr;
350 activeLine(line, dummyAddr);
351 if (line < 0)
352 line = 0;
353 int curLine = line; /* remember where we started */
354 bool found = false;
355 do {
356 // advance and wrap around
357 line += dir;
358 if (line < 0)
359 line = m_sourceCode.size()-1;
360 else if (line >= int(m_sourceCode.size()))
361 line = 0;
362 // search text
363 found = m_sourceCode[line].code.find(text, 0, caseSensitive) >= 0;
364 } while (!found && line != curLine);
366 scrollTo(line, DbgAddr());
369 void SourceWindow::mousePressEvent(QMouseEvent* ev)
371 // Check if right button was clicked.
372 if (ev->button() == RightButton)
374 emit clickedRight(ev->pos());
375 return;
378 int col = findCol(ev->x());
380 // check if event is in line item column
381 if (col == textCol()) {
382 KTextView::mousePressEvent(ev);
383 return;
386 // get row
387 int row = findRow(ev->y());
388 if (row < 0 || col < 0)
389 return;
391 if (col == 1) {
392 if (isRowExpanded(row)) {
393 actionCollapseRow(row);
394 } else {
395 actionExpandRow(row);
397 return;
400 int sourceRow;
401 int line = rowToLine(row, &sourceRow);
403 // find address if row is disassembled code
404 DbgAddr address;
405 if (row > sourceRow) {
406 // get offset from source code line
407 int off = row - sourceRow;
408 address = m_sourceCode[line].disassAddr[off-1];
411 switch (ev->button()) {
412 case LeftButton:
413 TRACE(QString().sprintf("left-clicked line %d", line));
414 emit clickedLeft(m_fileName, line, address,
415 (ev->state() & ShiftButton) != 0);
416 break;
417 case MidButton:
418 TRACE(QString().sprintf("mid-clicked row %d", line));
419 emit clickedMid(m_fileName, line, address);
420 break;
421 default:;
425 void SourceWindow::keyPressEvent(QKeyEvent* ev)
427 switch (ev->key()) {
428 case Key_Plus:
429 actionExpandRow(m_curRow);
430 return;
431 case Key_Minus:
432 actionCollapseRow(m_curRow);
433 return;
436 KTextView::keyPressEvent(ev);
439 #if QT_VERSION < 200
440 #define ISIDENT(c) (isalnum((c)) || (c) == '_')
441 #else
442 #define ISIDENT(c) ((c).isLetterOrNumber() || (c) == '_')
443 #endif
445 bool SourceWindow::wordAtPoint(const QPoint& p, QString& word, QRect& r)
447 if (findCol(p.x()) != textCol())
448 return false;
449 int row = findRow(p.y());
450 if (row < 0)
451 return false;
453 // find top-left corner of text cell
454 int top, left;
455 if (!colXPos(textCol(), &left))
456 return false;
457 if (!rowYPos(row, &top))
458 return false;
460 // get the bounding rect of the text
461 QPainter painter(this);
462 const QString& text = m_texts[row];
463 QRect bound =
464 painter.boundingRect(left+2, top, 0,0,
465 AlignLeft | SingleLine | DontClip | ExpandTabs,
466 text, text.length());
467 if (!bound.contains(p))
468 return false; /* p is outside text */
471 * We split the line into words and check each whether it contains
472 * the point p. We must do it the hard way (measuring substrings)
473 * because we cannot rely on that the font is mono-spaced.
475 uint start = 0;
476 while (start < text.length()) {
477 while (start < text.length() && !ISIDENT(text[start]))
478 start++;
479 if (start >= text.length())
480 return false;
482 * If p is in the rectangle that ends at 'start', it is in the
483 * last non-word part that we have just skipped.
485 bound =
486 painter.boundingRect(left+2, top, 0,0,
487 AlignLeft | SingleLine | DontClip | ExpandTabs,
488 text, start);
489 if (bound.contains(p))
490 return false;
491 // a word starts now
492 int startWidth = bound.width();
493 uint end = start;
494 while (end < text.length() && ISIDENT(text[end]))
495 end++;
496 bound =
497 painter.boundingRect(left+2, top, 0,0,
498 AlignLeft | SingleLine | DontClip | ExpandTabs,
499 text, end);
500 if (bound.contains(p)) {
501 // we found a word!
502 // extract the word
503 word = text.mid(start, end-start);
504 // and the rectangle
505 r = QRect(bound.x()+startWidth,bound.y(),
506 bound.width()-startWidth, bound.height());
507 return true;
509 start = end;
511 return false;
514 void SourceWindow::paletteChange(const QPalette& oldPal)
516 #if QT_VERSION < 200
517 setFont(kapp->fixedFont);
518 #else
519 setFont(KGlobalSettings::fixedFont());
520 #endif
521 KTextView::paletteChange(oldPal);
525 * Two file names (possibly full paths) match if the last parts - the file
526 * names - match.
528 bool SourceWindow::fileNameMatches(const QString& other)
530 const QString& me = fileName();
532 // check for null file names first
533 if (me.isNull() || other.isNull()) {
534 return me.isNull() && other.isNull();
538 * Get file names. Note: Either there is a slash, then skip it, or
539 * there is no slash, then -1 + 1 = 0!
541 int sme = me.findRev('/') + 1;
542 int sother = other.findRev('/') + 1;
543 return strcmp(me.data() + sme, other.data() + sother) == 0;
546 void SourceWindow::disassembled(int lineNo, const QList<DisassembledCode>& disass)
548 TRACE("disassembled line " + QString().setNum(lineNo));
549 if (lineNo < 0 || lineNo >= m_sourceCode.size())
550 return;
552 SourceLine& sl = m_sourceCode[lineNo];
554 // copy disassembled code and its addresses
555 sl.disass.setSize(disass.count());
556 sl.disassAddr.setSize(disass.count());
557 sl.canDisass = disass.count() > 0;
558 for (uint i = 0; i < disass.count(); i++) {
559 const DisassembledCode* c =
560 const_cast<QList<DisassembledCode>&>(disass).at(i);
561 sl.disass[i] = c->address.asString() + ' ' + c->code;
562 sl.disassAddr[i] = c->address;
565 int row = lineToRow(lineNo);
566 if (sl.canDisass) {
567 expandRow(row);
568 } else {
569 // clear expansion marker
570 updateCell(row, 1);
574 int SourceWindow::rowToLine(int row, int* sourceRow)
576 int line = m_rowToLine[row];
577 if (sourceRow != 0) {
578 // search back until we hit the first entry with the current line number
579 while (row > 0 && m_rowToLine[row-1] == line)
580 row--;
581 *sourceRow = row;
583 return line;
587 * Rows showing diassembled code have the same line number as the
588 * corresponding source code line number. Therefore, the line numbers in
589 * m_rowToLine are monotonically increasing with blocks of equal line
590 * numbers for a source line and its disassembled code that follows it.
592 * Hence, m_rowToLine always obeys the following condition:
594 * m_rowToLine[i] <= i
597 int SourceWindow::lineToRow(int line)
599 // line is zero-based!
601 assert(line < m_rowToLine.size());
603 // quick test for common case
604 if (line < 0 || m_rowToLine[line] == line)
605 return line;
607 assert(m_rowToLine[line] < line);
610 * Binary search between row == line and end of list. In the loop below
611 * we use the fact that the line numbers m_rowToLine do not contain
612 * holes.
614 int l = line;
615 int h = m_rowToLine.size();
616 while (l < h && m_rowToLine[l] != line)
618 assert(h == m_rowToLine.size() || m_rowToLine[l] < m_rowToLine[h]);
621 * We want to round down the midpoint so that we find the
622 * lowest row that belongs to the line we seek.
624 int mid = (l+h)/2;
625 if (m_rowToLine[mid] <= line)
626 l = mid;
627 else
628 h = mid;
630 // Found! Result is in l:
631 assert(m_rowToLine[l] == line);
634 * We might not have hit the lowest index for the line.
636 while (l > 0 && m_rowToLine[l-1] == line)
637 --l;
639 return l;
642 int SourceWindow::lineToRow(int line, const DbgAddr& address)
644 int row = lineToRow(line);
645 if (isRowExpanded(row)) {
646 row += m_sourceCode[line].findAddressRowOffset(address);
648 return row;
651 bool SourceWindow::isRowExpanded(int row)
653 assert(row >= 0);
654 return row < m_rowToLine.size()-1 &&
655 m_rowToLine[row] == m_rowToLine[row+1];
658 bool SourceWindow::isRowDisassCode(int row)
660 return row > 0 &&
661 m_rowToLine[row] == m_rowToLine[row-1];
664 void SourceWindow::expandRow(int row)
666 TRACE("expanding row " + QString().setNum(row));
667 // get disassembled code
668 int line = rowToLine(row);
669 const ValArray<QString>& disass = m_sourceCode[line].disass;
671 // remove PC (must be set again in slot of signal expanded())
672 m_lineItems[row] &= ~(liPC|liPCup);
674 // insert new lines
675 ++row;
676 m_rowToLine.insertAt(row, line, disass.size());
677 m_lineItems.insertAt(row, 0, disass.size());
678 m_texts.insertAt(row, disass);
680 bool autoU = autoUpdate();
681 setAutoUpdate(false);
683 // update line widths
684 for (int i = 0; i < disass.size(); i++) {
685 updateCellSize(disass[i]);
688 setNumRows(m_texts.size());
690 emit expanded(line); /* must set PC */
692 setAutoUpdate(autoU);
693 if (autoU && isVisible())
694 update();
697 void SourceWindow::collapseRow(int row)
699 TRACE("collapsing row " + QString().setNum(row));
700 int line = rowToLine(row);
702 // find end of this block
703 int end = row+1;
704 while (end < m_rowToLine.size() && m_rowToLine[end] == m_rowToLine[row]) {
705 end++;
707 ++row;
708 m_rowToLine.removeAt(row, end-row);
709 m_lineItems.removeAt(row, end-row);
710 m_texts.removeAt(row, end-row);
712 bool autoU = autoUpdate();
713 setAutoUpdate(false);
715 setNumRows(m_texts.size());
717 emit collapsed(line);
719 setAutoUpdate(autoU);
720 if (autoU && isVisible())
721 update();
724 void SourceWindow::activeLine(int& line, DbgAddr& address)
726 int row, col;
727 cursorPosition(&row, &col);
729 int sourceRow;
730 line = rowToLine(row, &sourceRow);
731 if (row > sourceRow) {
732 int off = row - sourceRow; /* offset from source line */
733 address = m_sourceCode[line].disassAddr[off-1];
738 * Returns the offset from the line displaying the source code to
739 * the line containing the specified address. If the address is not
740 * found, 0 is returned.
742 int SourceWindow::SourceLine::findAddressRowOffset(const DbgAddr& address) const
744 if (address.isEmpty())
745 return 0;
747 for (int i = 0; i < disassAddr.size(); i++) {
748 if (disassAddr[i] == address) {
749 // found exact address
750 return i+1;
752 if (disassAddr[i] > address) {
754 * We have already advanced too far; the address is before this
755 * index, but obviously we haven't found an exact match
756 * earlier. address is somewhere between the displayed
757 * addresses. We return the previous line.
759 return i;
762 // not found
763 return 0;
766 void SourceWindow::actionExpandRow(int row)
768 if (isRowExpanded(row) || isRowDisassCode(row)) {
769 // do nothing
770 return;
773 // disassemble
774 int line = rowToLine(row);
775 const SourceLine& sl = m_sourceCode[line];
776 if (!sl.canDisass)
777 return;
778 if (sl.disass.size() == 0) {
779 emit disassemble(m_fileName, line);
780 } else {
781 expandRow(row);
785 void SourceWindow::actionCollapseRow(int row)
787 if (!isRowExpanded(row) || isRowDisassCode(row)) {
788 // do nothing
789 return;
792 collapseRow(row);
796 #include "sourcewnd.moc"