Prepare for QTextEdit transition: Bring KTextView closer to QTextEdit.
[kdbg.git] / kdbg / sourcewnd.cpp
blob13d072a8b89def3359ee7a7ed8df295dfc18939e
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 QTextStream t(&f);
52 setText(t.read());
53 f.close();
55 // then we copy it into our own m_sourceCode
56 int n = paragraphs();
57 m_sourceCode.resize(n);
58 m_rowToLine.resize(n);
59 for (int i = 0; i < n; i++) {
60 m_sourceCode[i].code = text(i);
61 m_rowToLine[i] = i;
63 m_lineItems.resize(n, 0);
65 return true;
68 void SourceWindow::reloadFile()
70 QFile f(m_fileName);
71 if (!f.open(IO_ReadOnly)) {
72 // open failed; leave alone
73 return;
76 // read text into m_sourceCode
77 m_sourceCode.clear(); /* clear old text */
79 QTextStream t(&f);
80 setText(t.read());
81 f.close();
83 m_sourceCode.resize(paragraphs());
84 for (size_t i = 0; i < m_sourceCode.size(); i++) {
85 m_sourceCode[i].code = text(i);
87 // allocate line items
88 m_lineItems.resize(m_sourceCode.size(), 0);
90 m_rowToLine.resize(m_sourceCode.size());
91 for (size_t i = 0; i < m_sourceCode.size(); i++)
92 m_rowToLine[i] = i;
95 void SourceWindow::scrollTo(int lineNo, const DbgAddr& address)
97 if (lineNo < 0 || lineNo >= int(m_sourceCode.size()))
98 return;
100 int row = lineToRow(lineNo, address);
101 scrollToRow(row);
104 void SourceWindow::scrollToRow(int row)
106 if (row >= numRows())
107 row = numRows();
109 // scroll to lineNo
110 if (row >= topCell() && row <= lastRowVisible()) {
111 // line is already visible
112 setCursorPosition(row, 0);
113 return;
116 // setCursorPosition does only a half-hearted job of making the
117 // cursor line visible, so we do it by hand...
118 setCursorPosition(row, 0);
119 row -= 5;
120 if (row < 0)
121 row = 0;
123 setTopCell(row);
126 int SourceWindow::textCol() const
128 // text is in column 2
129 return 2;
132 int SourceWindow::cellWidth(int col) const
134 if (col == 0) {
135 return 15;
136 } else if (col == 1) {
137 return 12;
138 } else {
139 return KTextView::cellWidth(col);
143 void SourceWindow::paintCell(QPainter* p, int row, int col)
145 if (col == textCol()) {
146 p->save();
147 if (isRowDisassCode(row)) {
148 p->setPen(blue);
150 KTextView::paintCell(p, row, col);
151 p->restore();
152 return;
154 if (col == 0) {
155 uchar item = m_lineItems[row];
156 if (item == 0) /* shortcut out */
157 return;
158 int h = cellHeight(row);
159 if (item & liBP) {
160 // enabled breakpoint
161 int y = (h - m_brkena.height())/2;
162 if (y < 0) y = 0;
163 p->drawPixmap(0,y,m_brkena);
165 if (item & liBPdisabled) {
166 // disabled breakpoint
167 int y = (h - m_brkdis.height())/2;
168 if (y < 0) y = 0;
169 p->drawPixmap(0,y,m_brkdis);
171 if (item & liBPtemporary) {
172 // temporary breakpoint marker
173 int y = (h - m_brktmp.height())/2;
174 if (y < 0) y = 0;
175 p->drawPixmap(0,y,m_brktmp);
177 if (item & liBPconditional) {
178 // conditional breakpoint marker
179 int y = (h - m_brkcond.height())/2;
180 if (y < 0) y = 0;
181 p->drawPixmap(0,y,m_brkcond);
183 if (item & liBPorphan) {
184 // orphaned breakpoint marker
185 int y = (h - m_brkcond.height())/2;
186 if (y < 0) y = 0;
187 p->drawPixmap(0,y,m_brkorph);
189 if (item & liPC) {
190 // program counter in innermost frame
191 int y = (h - m_pcinner.height())/2;
192 if (y < 0) y = 0;
193 p->drawPixmap(0,y,m_pcinner);
195 if (item & liPCup) {
196 // program counter somewhere up the stack
197 int y = (h - m_pcup.height())/2;
198 if (y < 0) y = 0;
199 p->drawPixmap(0,y,m_pcup);
201 return;
203 if (col == 1) {
204 if (!isRowDisassCode(row) && m_sourceCode[rowToLine(row)].canDisass) {
205 int h = cellHeight(row);
206 int w = cellWidth(col);
207 int x = w/2;
208 int y = h/2;
209 p->drawLine(x-2, y, x+2, y);
210 if (!isRowExpanded(row)) {
211 p->drawLine(x, y-2, x, y+2);
217 void SourceWindow::updateLineItems(const KDebugger* dbg)
219 // clear outdated breakpoints
220 for (int i = m_lineItems.size()-1; i >= 0; i--) {
221 if (m_lineItems[i] & liBPany) {
222 // check if this breakpoint still exists
223 int line = rowToLine(i);
224 TRACE(QString().sprintf("checking for bp at %d", line));
225 int j;
226 for (j = dbg->numBreakpoints()-1; j >= 0; j--) {
227 const Breakpoint* bp = dbg->breakpoint(j);
228 if (bp->lineNo == line &&
229 fileNameMatches(bp->fileName) &&
230 lineToRow(line, bp->address) == i)
232 // yes it exists; mode is changed below
233 break;
236 if (j < 0) {
237 /* doesn't exist anymore, remove it */
238 m_lineItems[i] &= ~liBPany;
239 updateLineItem(i);
244 // add new breakpoints
245 for (int j = dbg->numBreakpoints()-1; j >= 0; j--) {
246 const Breakpoint* bp = dbg->breakpoint(j);
247 if (fileNameMatches(bp->fileName)) {
248 TRACE(QString().sprintf("updating %s:%d", bp->fileName.data(), bp->lineNo));
249 int i = bp->lineNo;
250 if (i < 0 || i >= int(m_sourceCode.size()))
251 continue;
252 // compute new line item flags for breakpoint
253 uchar flags = bp->enabled ? liBP : liBPdisabled;
254 if (bp->temporary)
255 flags |= liBPtemporary;
256 if (!bp->condition.isEmpty() || bp->ignoreCount != 0)
257 flags |= liBPconditional;
258 if (bp->isOrphaned())
259 flags |= liBPorphan;
260 // update if changed
261 int row = lineToRow(i, bp->address);
262 if ((m_lineItems[row] & liBPany) != flags) {
263 m_lineItems[row] &= ~liBPany;
264 m_lineItems[row] |= flags;
265 updateLineItem(row);
271 void SourceWindow::updateLineItem(int row)
273 updateCell(row, 0);
276 void SourceWindow::setPC(bool set, int lineNo, const DbgAddr& address, int frameNo)
278 if (lineNo < 0 || lineNo >= int(m_sourceCode.size())) {
279 return;
282 int row = lineToRow(lineNo, address);
284 uchar flag = frameNo == 0 ? liPC : liPCup;
285 if (set) {
286 // set only if not already set
287 if ((m_lineItems[row] & flag) == 0) {
288 m_lineItems[row] |= flag;
289 updateLineItem(row);
291 } else {
292 // clear only if not set
293 if ((m_lineItems[row] & flag) != 0) {
294 m_lineItems[row] &= ~flag;
295 updateLineItem(row);
300 void SourceWindow::find(const QString& text, bool caseSensitive, FindDirection dir)
302 ASSERT(dir == 1 || dir == -1);
303 if (m_sourceCode.size() == 0 || text.isEmpty())
304 return;
306 int line;
307 DbgAddr dummyAddr;
308 activeLine(line, dummyAddr);
309 if (line < 0)
310 line = 0;
311 int curLine = line; /* remember where we started */
312 bool found = false;
313 do {
314 // advance and wrap around
315 line += dir;
316 if (line < 0)
317 line = m_sourceCode.size()-1;
318 else if (line >= int(m_sourceCode.size()))
319 line = 0;
320 // search text
321 found = m_sourceCode[line].code.find(text, 0, caseSensitive) >= 0;
322 } while (!found && line != curLine);
324 scrollTo(line, DbgAddr());
327 void SourceWindow::mousePressEvent(QMouseEvent* ev)
329 // Check if right button was clicked.
330 if (ev->button() == RightButton)
332 emit clickedRight(ev->pos());
333 return;
336 int col = findCol(ev->x());
338 // check if event is in line item column
339 if (col == textCol()) {
340 KTextView::mousePressEvent(ev);
341 return;
344 // get row
345 int row = findRow(ev->y());
346 if (row < 0 || col < 0)
347 return;
349 if (col == 1) {
350 if (isRowExpanded(row)) {
351 actionCollapseRow(row);
352 } else {
353 actionExpandRow(row);
355 return;
358 int sourceRow;
359 int line = rowToLine(row, &sourceRow);
361 // find address if row is disassembled code
362 DbgAddr address;
363 if (row > sourceRow) {
364 // get offset from source code line
365 int off = row - sourceRow;
366 address = m_sourceCode[line].disassAddr[off-1];
369 switch (ev->button()) {
370 case LeftButton:
371 TRACE(QString().sprintf("left-clicked line %d", line));
372 emit clickedLeft(m_fileName, line, address,
373 (ev->state() & ShiftButton) != 0);
374 break;
375 case MidButton:
376 TRACE(QString().sprintf("mid-clicked row %d", line));
377 emit clickedMid(m_fileName, line, address);
378 break;
379 default:;
383 void SourceWindow::keyPressEvent(QKeyEvent* ev)
385 switch (ev->key()) {
386 case Key_Plus:
387 actionExpandRow(m_curRow);
388 return;
389 case Key_Minus:
390 actionCollapseRow(m_curRow);
391 return;
394 KTextView::keyPressEvent(ev);
397 static inline bool isident(QChar c)
399 return c.isLetterOrNumber() || c.latin1() == '_';
402 bool SourceWindow::wordAtPoint(const QPoint& p, QString& word, QRect& r)
404 int row, col = charAt(p, &row);
405 if (row < 0 || col < 0)
406 return false;
408 // isolate the word at row, col
409 QString line = text(row);
410 if (!isident(line[col]))
411 return false;
413 int begin = col;
414 while (begin > 0 && isident(line[begin-1]))
415 --begin;
417 ++col;
418 while (col < int(line.length()) && isident(line[col]));
420 r = QRect(p, p);
421 r.addCoords(-5,-5,5,5);
422 word = line.mid(begin, col-begin);
423 return true;
426 void SourceWindow::paletteChange(const QPalette& oldPal)
428 setFont(KGlobalSettings::fixedFont());
429 KTextView::paletteChange(oldPal);
433 * Two file names (possibly full paths) match if the last parts - the file
434 * names - match.
436 bool SourceWindow::fileNameMatches(const QString& other)
438 const QString& me = fileName();
440 // check for null file names first
441 if (me.isNull() || other.isNull()) {
442 return me.isNull() && other.isNull();
446 * Get file names. Note: Either there is a slash, then skip it, or
447 * there is no slash, then -1 + 1 = 0!
449 int sme = me.findRev('/') + 1;
450 int sother = other.findRev('/') + 1;
451 return strcmp(me.data() + sme, other.data() + sother) == 0;
454 void SourceWindow::disassembled(int lineNo, const QList<DisassembledCode>& disass)
456 TRACE("disassembled line " + QString().setNum(lineNo));
457 if (lineNo < 0 || lineNo >= int(m_sourceCode.size()))
458 return;
460 SourceLine& sl = m_sourceCode[lineNo];
462 // copy disassembled code and its addresses
463 sl.disass.resize(disass.count());
464 sl.disassAddr.resize(disass.count());
465 sl.canDisass = disass.count() > 0;
466 for (uint i = 0; i < disass.count(); i++) {
467 const DisassembledCode* c =
468 const_cast<QList<DisassembledCode>&>(disass).at(i);
469 QString code = c->code;
470 while (code.endsWith("\n"))
471 code.truncate(code.length()-1);
472 sl.disass[i] = c->address.asString() + ' ' + code;
473 sl.disassAddr[i] = c->address;
476 int row = lineToRow(lineNo);
477 if (sl.canDisass) {
478 expandRow(row);
479 } else {
480 // clear expansion marker
481 updateCell(row, 1);
485 int SourceWindow::rowToLine(int row, int* sourceRow)
487 int line = row >= 0 ? m_rowToLine[row] : -1;
488 if (sourceRow != 0) {
489 // search back until we hit the first entry with the current line number
490 while (row > 0 && m_rowToLine[row-1] == line)
491 row--;
492 *sourceRow = row;
494 return line;
498 * Rows showing diassembled code have the same line number as the
499 * corresponding source code line number. Therefore, the line numbers in
500 * m_rowToLine are monotonically increasing with blocks of equal line
501 * numbers for a source line and its disassembled code that follows it.
503 * Hence, m_rowToLine always obeys the following condition:
505 * m_rowToLine[i] <= i
508 int SourceWindow::lineToRow(int line)
510 // line is zero-based!
512 assert(line < int(m_rowToLine.size()));
514 // quick test for common case
515 if (line < 0 || m_rowToLine[line] == line)
516 return line;
518 assert(m_rowToLine[line] < line);
521 * Binary search between row == line and end of list. In the loop below
522 * we use the fact that the line numbers m_rowToLine do not contain
523 * holes.
525 int l = line;
526 int h = m_rowToLine.size();
527 while (l < h && m_rowToLine[l] != line)
529 assert(h == int(m_rowToLine.size()) || m_rowToLine[l] < m_rowToLine[h]);
532 * We want to round down the midpoint so that we find the
533 * lowest row that belongs to the line we seek.
535 int mid = (l+h)/2;
536 if (m_rowToLine[mid] <= line)
537 l = mid;
538 else
539 h = mid;
541 // Found! Result is in l:
542 assert(m_rowToLine[l] == line);
545 * We might not have hit the lowest index for the line.
547 while (l > 0 && m_rowToLine[l-1] == line)
548 --l;
550 return l;
553 int SourceWindow::lineToRow(int line, const DbgAddr& address)
555 int row = lineToRow(line);
556 if (isRowExpanded(row)) {
557 row += m_sourceCode[line].findAddressRowOffset(address);
559 return row;
562 bool SourceWindow::isRowExpanded(int row)
564 assert(row >= 0);
565 return row < int(m_rowToLine.size())-1 &&
566 m_rowToLine[row] == m_rowToLine[row+1];
569 bool SourceWindow::isRowDisassCode(int row)
571 return row > 0 &&
572 m_rowToLine[row] == m_rowToLine[row-1];
575 void SourceWindow::expandRow(int row)
577 TRACE("expanding row " + QString().setNum(row));
578 // get disassembled code
579 int line = rowToLine(row);
580 const std::vector<QString>& disass = m_sourceCode[line].disass;
582 // remove PC (must be set again in slot of signal expanded())
583 m_lineItems[row] &= ~(liPC|liPCup);
585 // insert new lines
586 ++row;
587 m_rowToLine.insert(m_rowToLine.begin()+row, disass.size(), line);
588 m_lineItems.insert(m_lineItems.begin()+row, disass.size(), 0);
589 for (size_t i = 0; i < disass.size(); i++) {
590 insertParagraph(disass[i], row++);
592 update(); // line items
594 emit expanded(line); /* must set PC */
597 void SourceWindow::collapseRow(int row)
599 TRACE("collapsing row " + QString().setNum(row));
600 int line = rowToLine(row);
602 // find end of this block
603 int end = row+1;
604 while (end < int(m_rowToLine.size()) && m_rowToLine[end] == m_rowToLine[row]) {
605 end++;
607 ++row;
608 m_rowToLine.erase(m_rowToLine.begin()+row, m_rowToLine.begin()+end);
609 m_lineItems.erase(m_lineItems.begin()+row, m_lineItems.begin()+end);
610 while (--end >= row)
611 removeParagraph(end);
612 update(); // line items
614 emit collapsed(line);
617 void SourceWindow::activeLine(int& line, DbgAddr& address)
619 int row, col;
620 cursorPosition(&row, &col);
622 int sourceRow;
623 line = rowToLine(row, &sourceRow);
624 if (row > sourceRow) {
625 int off = row - sourceRow; /* offset from source line */
626 address = m_sourceCode[line].disassAddr[off-1];
631 * Returns the offset from the line displaying the source code to
632 * the line containing the specified address. If the address is not
633 * found, 0 is returned.
635 int SourceWindow::SourceLine::findAddressRowOffset(const DbgAddr& address) const
637 if (address.isEmpty())
638 return 0;
640 for (size_t i = 0; i < disassAddr.size(); i++) {
641 if (disassAddr[i] == address) {
642 // found exact address
643 return i+1;
645 if (disassAddr[i] > address) {
647 * We have already advanced too far; the address is before this
648 * index, but obviously we haven't found an exact match
649 * earlier. address is somewhere between the displayed
650 * addresses. We return the previous line.
652 return i;
655 // not found
656 return 0;
659 void SourceWindow::actionExpandRow(int row)
661 if (isRowExpanded(row) || isRowDisassCode(row)) {
662 // do nothing
663 return;
666 // disassemble
667 int line = rowToLine(row);
668 const SourceLine& sl = m_sourceCode[line];
669 if (!sl.canDisass)
670 return;
671 if (sl.disass.size() == 0) {
672 emit disassemble(m_fileName, line);
673 } else {
674 expandRow(row);
678 void SourceWindow::actionCollapseRow(int row)
680 if (!isRowExpanded(row) || isRowDisassCode(row)) {
681 // do nothing
682 return;
685 collapseRow(row);
689 #include "sourcewnd.moc"