Re-add keyboard cursor navigation in the source code window.
[kdbg.git] / kdbg / sourcewnd.cpp
blobccbc8033ca5d4f453845b9ba92f934a791e1d41c
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 QTextEdit(parent, name),
24 m_fileName(fileName),
25 m_curRow(-1),
26 m_widthItems(16),
27 m_widthPlus(12)
29 // load pixmaps
30 m_pcinner = UserIcon("pcinner");
31 m_pcup = UserIcon("pcup");
32 m_brkena = UserIcon("brkena");
33 m_brkdis = UserIcon("brkdis");
34 m_brktmp = UserIcon("brktmp");
35 m_brkcond = UserIcon("brkcond");
36 m_brkorph = UserIcon("brkorph");
37 setFont(KGlobalSettings::fixedFont());
38 setReadOnly(true);
39 setMargins(m_widthItems+m_widthPlus, 0, 0 ,0);
40 setAutoFormatting(AutoNone);
41 setTextFormat(PlainText);
42 setWordWrap(NoWrap);
43 connect(verticalScrollBar(), SIGNAL(valueChanged(int)),
44 this, SLOT(update()));
45 connect(this, SIGNAL(cursorPositionChanged(int,int)), this, SLOT(cursorChanged(int)));
48 SourceWindow::~SourceWindow()
52 bool SourceWindow::loadFile()
54 // first we load the code into QTextEdit
55 QFile f(m_fileName);
56 if (!f.open(IO_ReadOnly)) {
57 return false;
60 QTextStream t(&f);
61 setText(t.read());
62 f.close();
64 // then we copy it into our own m_sourceCode
65 int n = paragraphs();
66 m_sourceCode.resize(n);
67 m_rowToLine.resize(n);
68 for (int i = 0; i < n; i++) {
69 m_sourceCode[i].code = text(i);
70 m_rowToLine[i] = i;
72 m_lineItems.resize(n, 0);
74 return true;
77 void SourceWindow::reloadFile()
79 QFile f(m_fileName);
80 if (!f.open(IO_ReadOnly)) {
81 // open failed; leave alone
82 return;
85 // read text into m_sourceCode
86 m_sourceCode.clear(); /* clear old text */
88 QTextStream t(&f);
89 setText(t.read());
90 f.close();
92 m_sourceCode.resize(paragraphs());
93 for (size_t i = 0; i < m_sourceCode.size(); i++) {
94 m_sourceCode[i].code = text(i);
96 // allocate line items
97 m_lineItems.resize(m_sourceCode.size(), 0);
99 m_rowToLine.resize(m_sourceCode.size());
100 for (size_t i = 0; i < m_sourceCode.size(); i++)
101 m_rowToLine[i] = i;
104 void SourceWindow::scrollTo(int lineNo, const DbgAddr& address)
106 if (lineNo < 0 || lineNo >= int(m_sourceCode.size()))
107 return;
109 int row = lineToRow(lineNo, address);
110 scrollToRow(row);
113 void SourceWindow::scrollToRow(int row)
115 setCursorPosition(row, 0);
116 ensureCursorVisible();
119 void SourceWindow::drawFrame(QPainter* p)
121 QTextEdit::drawFrame(p);
123 // and paragraph at the top is...
124 int top = paragraphAt(QPoint(0,contentsY()));
125 int bot = paragraphAt(QPoint(0,contentsY()+visibleHeight()-1));
126 if (bot < 0)
127 bot = paragraphs()-1;
129 p->save();
131 // set a clip rectangle
132 int fw = frameWidth();
133 QRect inside = rect();
134 inside.addCoords(fw,fw,-fw,-fw);
135 QRegion clip = p->clipRegion();
136 clip &= QRegion(inside);
137 p->setClipRegion(clip);
139 p->setPen(colorGroup().text());
140 p->eraseRect(inside);
142 for (int row = top; row <= bot; row++)
144 uchar item = m_lineItems[row];
145 p->save();
147 QRect r = paragraphRect(row);
148 QPoint pt = contentsToViewport(r.topLeft());
149 int h = r.height();
150 p->translate(fw, pt.y()+viewport()->y());
152 if (item & liBP) {
153 // enabled breakpoint
154 int y = (h - m_brkena.height())/2;
155 if (y < 0) y = 0;
156 p->drawPixmap(0,y,m_brkena);
158 if (item & liBPdisabled) {
159 // disabled breakpoint
160 int y = (h - m_brkdis.height())/2;
161 if (y < 0) y = 0;
162 p->drawPixmap(0,y,m_brkdis);
164 if (item & liBPtemporary) {
165 // temporary breakpoint marker
166 int y = (h - m_brktmp.height())/2;
167 if (y < 0) y = 0;
168 p->drawPixmap(0,y,m_brktmp);
170 if (item & liBPconditional) {
171 // conditional breakpoint marker
172 int y = (h - m_brkcond.height())/2;
173 if (y < 0) y = 0;
174 p->drawPixmap(0,y,m_brkcond);
176 if (item & liBPorphan) {
177 // orphaned breakpoint marker
178 int y = (h - m_brkcond.height())/2;
179 if (y < 0) y = 0;
180 p->drawPixmap(0,y,m_brkorph);
182 if (item & liPC) {
183 // program counter in innermost frame
184 int y = (h - m_pcinner.height())/2;
185 if (y < 0) y = 0;
186 p->drawPixmap(0,y,m_pcinner);
188 if (item & liPCup) {
189 // program counter somewhere up the stack
190 int y = (h - m_pcup.height())/2;
191 if (y < 0) y = 0;
192 p->drawPixmap(0,y,m_pcup);
194 if (!isRowDisassCode(row) && m_sourceCode[rowToLine(row)].canDisass) {
195 int w = m_widthPlus;
196 p->translate(m_widthItems, 0);
197 int x = w/2;
198 int y = h/2;
199 p->drawLine(x-2, y, x+2, y);
200 if (!isRowExpanded(row)) {
201 p->drawLine(x, y-2, x, y+2);
204 p->restore();
206 p->restore();
209 void SourceWindow::updateLineItems(const KDebugger* dbg)
211 // clear outdated breakpoints
212 for (int i = m_lineItems.size()-1; i >= 0; i--) {
213 if (m_lineItems[i] & liBPany) {
214 // check if this breakpoint still exists
215 int line = rowToLine(i);
216 TRACE(QString().sprintf("checking for bp at %d", line));
217 int j;
218 for (j = dbg->numBreakpoints()-1; j >= 0; j--) {
219 const Breakpoint* bp = dbg->breakpoint(j);
220 if (bp->lineNo == line &&
221 fileNameMatches(bp->fileName) &&
222 lineToRow(line, bp->address) == i)
224 // yes it exists; mode is changed below
225 break;
228 if (j < 0) {
229 /* doesn't exist anymore, remove it */
230 m_lineItems[i] &= ~liBPany;
231 update();
236 // add new breakpoints
237 for (int j = dbg->numBreakpoints()-1; j >= 0; j--) {
238 const Breakpoint* bp = dbg->breakpoint(j);
239 if (fileNameMatches(bp->fileName)) {
240 TRACE(QString().sprintf("updating %s:%d", bp->fileName.data(), bp->lineNo));
241 int i = bp->lineNo;
242 if (i < 0 || i >= int(m_sourceCode.size()))
243 continue;
244 // compute new line item flags for breakpoint
245 uchar flags = bp->enabled ? liBP : liBPdisabled;
246 if (bp->temporary)
247 flags |= liBPtemporary;
248 if (!bp->condition.isEmpty() || bp->ignoreCount != 0)
249 flags |= liBPconditional;
250 if (bp->isOrphaned())
251 flags |= liBPorphan;
252 // update if changed
253 int row = lineToRow(i, bp->address);
254 if ((m_lineItems[row] & liBPany) != flags) {
255 m_lineItems[row] &= ~liBPany;
256 m_lineItems[row] |= flags;
257 update();
263 void SourceWindow::setPC(bool set, int lineNo, const DbgAddr& address, int frameNo)
265 if (lineNo < 0 || lineNo >= int(m_sourceCode.size())) {
266 return;
269 int row = lineToRow(lineNo, address);
271 uchar flag = frameNo == 0 ? liPC : liPCup;
272 if (set) {
273 // set only if not already set
274 if ((m_lineItems[row] & flag) == 0) {
275 m_lineItems[row] |= flag;
276 update();
278 } else {
279 // clear only if not set
280 if ((m_lineItems[row] & flag) != 0) {
281 m_lineItems[row] &= ~flag;
282 update();
287 void SourceWindow::find(const QString& text, bool caseSensitive, FindDirection dir)
289 ASSERT(dir == 1 || dir == -1);
290 if (m_sourceCode.size() == 0 || text.isEmpty())
291 return;
293 int line;
294 DbgAddr dummyAddr;
295 activeLine(line, dummyAddr);
296 if (line < 0)
297 line = 0;
298 int curLine = line; /* remember where we started */
299 bool found = false;
300 do {
301 // advance and wrap around
302 line += dir;
303 if (line < 0)
304 line = m_sourceCode.size()-1;
305 else if (line >= int(m_sourceCode.size()))
306 line = 0;
307 // search text
308 found = m_sourceCode[line].code.find(text, 0, caseSensitive) >= 0;
309 } while (!found && line != curLine);
311 scrollTo(line, DbgAddr());
314 void SourceWindow::mousePressEvent(QMouseEvent* ev)
316 // Check if right button was clicked.
317 if (ev->button() == RightButton)
319 emit clickedRight(ev->pos());
320 return;
323 // get row
324 QPoint p = viewportToContents(QPoint(0, ev->y() - viewport()->y()));
325 int row = paragraphAt(p);
326 if (row < 0)
327 return;
329 if (ev->x() > m_widthItems+frameWidth())
331 if (isRowExpanded(row)) {
332 actionCollapseRow(row);
333 } else {
334 actionExpandRow(row);
336 return;
339 int sourceRow;
340 int line = rowToLine(row, &sourceRow);
342 // find address if row is disassembled code
343 DbgAddr address;
344 if (row > sourceRow) {
345 // get offset from source code line
346 int off = row - sourceRow;
347 address = m_sourceCode[line].disassAddr[off-1];
350 switch (ev->button()) {
351 case LeftButton:
352 TRACE(QString().sprintf("left-clicked line %d", line));
353 emit clickedLeft(m_fileName, line, address,
354 (ev->state() & ShiftButton) != 0);
355 break;
356 case MidButton:
357 TRACE(QString().sprintf("mid-clicked row %d", line));
358 emit clickedMid(m_fileName, line, address);
359 break;
360 default:;
364 void SourceWindow::keyPressEvent(QKeyEvent* ev)
366 int top1, top2;
367 QPoint top;
368 switch (ev->key()) {
369 case Key_Plus:
370 actionExpandRow(m_curRow);
371 return;
372 case Key_Minus:
373 actionCollapseRow(m_curRow);
374 return;
375 case Key_Up:
376 if (m_curRow > 0) {
377 setCursorPosition(m_curRow-1, 0);
379 return;
380 case Key_Down:
381 if (m_curRow < paragraphs()-1) {
382 setCursorPosition(m_curRow+1, 0);
384 return;
385 case Key_Home:
386 setCursorPosition(0, 0);
387 return;
388 case Key_End:
389 setCursorPosition(paragraphs()-1, 0);
390 return;
391 case Key_Next:
392 case Key_Prior:
393 top = viewportToContents(QPoint(0,0));
394 top1 = paragraphAt(top);
397 QTextEdit::keyPressEvent(ev);
399 switch (ev->key()) {
400 case Key_Next:
401 case Key_Prior:
402 top = viewportToContents(QPoint(0,0));
403 top2 = paragraphAt(top);
404 setCursorPosition(m_curRow+(top2-top1), 0);
408 static inline bool isident(QChar c)
410 return c.isLetterOrNumber() || c.latin1() == '_';
413 bool SourceWindow::wordAtPoint(const QPoint& p, QString& word, QRect& r)
415 QPoint pv = viewportToContents(p - viewport()->pos());
416 int row, col = charAt(pv, &row);
417 if (row < 0 || col < 0)
418 return false;
420 // isolate the word at row, col
421 QString line = text(row);
422 if (!isident(line[col]))
423 return false;
425 int begin = col;
426 while (begin > 0 && isident(line[begin-1]))
427 --begin;
429 ++col;
430 while (col < int(line.length()) && isident(line[col]));
432 r = QRect(p, p);
433 r.addCoords(-5,-5,5,5);
434 word = line.mid(begin, col-begin);
435 return true;
438 void SourceWindow::paletteChange(const QPalette& oldPal)
440 setFont(KGlobalSettings::fixedFont());
441 QTextEdit::paletteChange(oldPal);
445 * Two file names (possibly full paths) match if the last parts - the file
446 * names - match.
448 bool SourceWindow::fileNameMatches(const QString& other)
450 const QString& me = fileName();
452 // check for null file names first
453 if (me.isNull() || other.isNull()) {
454 return me.isNull() && other.isNull();
458 * Get file names. Note: Either there is a slash, then skip it, or
459 * there is no slash, then -1 + 1 = 0!
461 int sme = me.findRev('/') + 1;
462 int sother = other.findRev('/') + 1;
463 return strcmp(me.data() + sme, other.data() + sother) == 0;
466 void SourceWindow::disassembled(int lineNo, const QList<DisassembledCode>& disass)
468 TRACE("disassembled line " + QString().setNum(lineNo));
469 if (lineNo < 0 || lineNo >= int(m_sourceCode.size()))
470 return;
472 SourceLine& sl = m_sourceCode[lineNo];
474 // copy disassembled code and its addresses
475 sl.disass.resize(disass.count());
476 sl.disassAddr.resize(disass.count());
477 sl.canDisass = disass.count() > 0;
478 for (uint i = 0; i < disass.count(); i++) {
479 const DisassembledCode* c =
480 const_cast<QList<DisassembledCode>&>(disass).at(i);
481 QString code = c->code;
482 while (code.endsWith("\n"))
483 code.truncate(code.length()-1);
484 sl.disass[i] = c->address.asString() + ' ' + code;
485 sl.disassAddr[i] = c->address;
488 int row = lineToRow(lineNo);
489 if (sl.canDisass) {
490 expandRow(row);
491 } else {
492 // clear expansion marker
493 update();
497 int SourceWindow::rowToLine(int row, int* sourceRow)
499 int line = row >= 0 ? m_rowToLine[row] : -1;
500 if (sourceRow != 0) {
501 // search back until we hit the first entry with the current line number
502 while (row > 0 && m_rowToLine[row-1] == line)
503 row--;
504 *sourceRow = row;
506 return line;
510 * Rows showing diassembled code have the same line number as the
511 * corresponding source code line number. Therefore, the line numbers in
512 * m_rowToLine are monotonically increasing with blocks of equal line
513 * numbers for a source line and its disassembled code that follows it.
515 * Hence, m_rowToLine always obeys the following condition:
517 * m_rowToLine[i] <= i
520 int SourceWindow::lineToRow(int line)
522 // line is zero-based!
524 assert(line < int(m_rowToLine.size()));
526 // quick test for common case
527 if (line < 0 || m_rowToLine[line] == line)
528 return line;
530 assert(m_rowToLine[line] < line);
533 * Binary search between row == line and end of list. In the loop below
534 * we use the fact that the line numbers m_rowToLine do not contain
535 * holes.
537 int l = line;
538 int h = m_rowToLine.size();
539 while (l < h && m_rowToLine[l] != line)
541 assert(h == int(m_rowToLine.size()) || m_rowToLine[l] < m_rowToLine[h]);
544 * We want to round down the midpoint so that we find the
545 * lowest row that belongs to the line we seek.
547 int mid = (l+h)/2;
548 if (m_rowToLine[mid] <= line)
549 l = mid;
550 else
551 h = mid;
553 // Found! Result is in l:
554 assert(m_rowToLine[l] == line);
557 * We might not have hit the lowest index for the line.
559 while (l > 0 && m_rowToLine[l-1] == line)
560 --l;
562 return l;
565 int SourceWindow::lineToRow(int line, const DbgAddr& address)
567 int row = lineToRow(line);
568 if (isRowExpanded(row)) {
569 row += m_sourceCode[line].findAddressRowOffset(address);
571 return row;
574 bool SourceWindow::isRowExpanded(int row)
576 assert(row >= 0);
577 return row < int(m_rowToLine.size())-1 &&
578 m_rowToLine[row] == m_rowToLine[row+1];
581 bool SourceWindow::isRowDisassCode(int row)
583 return row > 0 &&
584 m_rowToLine[row] == m_rowToLine[row-1];
587 void SourceWindow::expandRow(int row)
589 TRACE("expanding row " + QString().setNum(row));
590 // get disassembled code
591 int line = rowToLine(row);
592 const std::vector<QString>& disass = m_sourceCode[line].disass;
594 // remove PC (must be set again in slot of signal expanded())
595 m_lineItems[row] &= ~(liPC|liPCup);
597 // adjust current row
598 if (m_curRow > row) {
599 m_curRow += disass.size();
600 // highlight is moved automatically
603 // insert new lines
604 ++row;
605 m_rowToLine.insert(m_rowToLine.begin()+row, disass.size(), line);
606 m_lineItems.insert(m_lineItems.begin()+row, disass.size(), 0);
607 for (size_t i = 0; i < disass.size(); i++) {
608 insertParagraph(disass[i], row++);
610 update(); // line items
612 emit expanded(line); /* must set PC */
615 void SourceWindow::collapseRow(int row)
617 TRACE("collapsing row " + QString().setNum(row));
618 int line = rowToLine(row);
620 // find end of this block
621 int end = row+1;
622 while (end < int(m_rowToLine.size()) && m_rowToLine[end] == m_rowToLine[row]) {
623 end++;
625 ++row;
626 // adjust current row
627 if (m_curRow >= row) {
628 m_curRow -= end-row;
629 if (m_curRow < row) // was m_curRow in disassembled code?
630 m_curRow = -1;
632 m_rowToLine.erase(m_rowToLine.begin()+row, m_rowToLine.begin()+end);
633 m_lineItems.erase(m_lineItems.begin()+row, m_lineItems.begin()+end);
634 while (--end >= row)
635 removeParagraph(end);
636 update(); // line items
638 emit collapsed(line);
641 void SourceWindow::activeLine(int& line, DbgAddr& address)
643 int row = m_curRow;
645 int sourceRow;
646 line = rowToLine(row, &sourceRow);
647 if (row > sourceRow) {
648 int off = row - sourceRow; /* offset from source line */
649 address = m_sourceCode[line].disassAddr[off-1];
654 * Returns the offset from the line displaying the source code to
655 * the line containing the specified address. If the address is not
656 * found, 0 is returned.
658 int SourceWindow::SourceLine::findAddressRowOffset(const DbgAddr& address) const
660 if (address.isEmpty())
661 return 0;
663 for (size_t i = 0; i < disassAddr.size(); i++) {
664 if (disassAddr[i] == address) {
665 // found exact address
666 return i+1;
668 if (disassAddr[i] > address) {
670 * We have already advanced too far; the address is before this
671 * index, but obviously we haven't found an exact match
672 * earlier. address is somewhere between the displayed
673 * addresses. We return the previous line.
675 return i;
678 // not found
679 return 0;
682 void SourceWindow::actionExpandRow(int row)
684 if (row < 0 || isRowExpanded(row) || isRowDisassCode(row))
685 return;
687 // disassemble
688 int line = rowToLine(row);
689 const SourceLine& sl = m_sourceCode[line];
690 if (!sl.canDisass)
691 return;
692 if (sl.disass.size() == 0) {
693 emit disassemble(m_fileName, line);
694 } else {
695 expandRow(row);
699 void SourceWindow::actionCollapseRow(int row)
701 if (row < 0 || !isRowExpanded(row) || isRowDisassCode(row))
702 return;
704 collapseRow(row);
707 void SourceWindow::setTabWidth(int numChars)
709 if (numChars <= 0)
710 numChars = 8;
711 QFontMetrics fm(currentFont());
712 QString s;
713 int w = fm.width(s.fill('x', numChars));
714 setTabStopWidth(w);
717 void SourceWindow::cursorChanged(int row)
719 if (row == m_curRow)
720 return;
722 if (m_curRow >= 0 && m_curRow < paragraphs())
723 clearParagraphBackground(m_curRow);
724 m_curRow = row;
725 setParagraphBackgroundColor(row, colorGroup().background());
727 emit lineChanged();
731 #include "sourcewnd.moc"