Prepare for QTextEdit transition: Add KTextEdit::charAt() for wordAtPoint().
[kdbg.git] / kdbg / sourcewnd.cpp
blobe1196aab34a2dfcf07c868534e3a42b371895da9
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 #include <iterator>
17 #ifdef HAVE_CONFIG_H
18 #include "config.h"
19 #endif
20 #include "mydebug.h"
23 SourceWindow::SourceWindow(const char* fileName, QWidget* parent, const char* name) :
24 KTextView(parent, name),
25 m_fileName(fileName)
27 setNumCols(3);
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());
40 SourceWindow::~SourceWindow()
44 bool SourceWindow::loadFile()
46 // first we load the code into KTextView::m_texts
47 QFile f(m_fileName);
48 if (!f.open(IO_ReadOnly)) {
49 return false;
52 bool upd = autoUpdate();
53 setAutoUpdate(false);
55 QTextStream t(&f);
56 QString s;
57 while (!t.eof()) {
58 s = t.readLine();
59 insertLine(s);
61 f.close();
63 setAutoUpdate(upd);
64 if (upd) {
65 updateTableSize();
68 // then we copy it into our own m_sourceCode
69 m_sourceCode.resize(m_texts.size());
70 m_rowToLine.resize(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;
75 m_lineItems.resize(m_texts.size(), 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.clear(); /* clear old text */
91 QTextStream t(&f);
92 std::back_insert_iterator<std::vector<SourceLine> > it(m_sourceCode);
93 SourceLine s;
94 while (!t.eof()) {
95 s.code = t.readLine();
96 *it = s;
98 f.close();
100 bool autoU = autoUpdate();
101 setAutoUpdate(false);
103 int lineNo = QMIN(m_texts.size(), m_sourceCode.size());
104 for (int i = 0; i < lineNo; i++) {
105 replaceLine(i, m_sourceCode[i].code);
107 if (m_sourceCode.size() > m_texts.size()) {
108 // the new file has more lines than the old one
109 // here lineNo is the number of lines of the old file
110 for (int i = lineNo; i < m_sourceCode.size(); i++) {
111 insertLine(m_sourceCode[i].code);
113 } else {
114 // the new file has fewer lines
115 // here lineNo is the number of lines of the new file
116 // remove the excessive lines
117 m_texts.resize(lineNo);
119 // allocate line items
120 m_lineItems.resize(lineNo, 0);
122 setNumRows(m_texts.size());
124 m_rowToLine.resize(m_texts.size());
125 for (int i = 0; i < m_texts.size(); i++)
126 m_rowToLine[i] = i;
128 setAutoUpdate(autoU);
129 if (autoU && isVisible()) {
130 updateTableSize();
131 repaint();
134 // if the cursor is in the deleted lines, move it to the last line
135 if (m_curRow >= m_texts.size()) {
136 m_curRow = -1; /* at this point don't have an active row */
137 activateLine(m_texts.size()-1); /* now we have */
141 void SourceWindow::scrollTo(int lineNo, const DbgAddr& address)
143 if (lineNo < 0 || lineNo >= m_sourceCode.size())
144 return;
146 int row = lineToRow(lineNo, address);
147 scrollToRow(row);
150 void SourceWindow::scrollToRow(int row)
152 if (row >= numRows())
153 row = numRows();
155 // scroll to lineNo
156 if (row >= topCell() && row <= lastRowVisible()) {
157 // line is already visible
158 setCursorPosition(row, 0);
159 return;
162 // setCursorPosition does only a half-hearted job of making the
163 // cursor line visible, so we do it by hand...
164 setCursorPosition(row, 0);
165 row -= 5;
166 if (row < 0)
167 row = 0;
169 setTopCell(row);
172 int SourceWindow::textCol() const
174 // text is in column 2
175 return 2;
178 int SourceWindow::cellWidth(int col) const
180 if (col == 0) {
181 return 15;
182 } else if (col == 1) {
183 return 12;
184 } else {
185 return KTextView::cellWidth(col);
189 void SourceWindow::paintCell(QPainter* p, int row, int col)
191 if (col == textCol()) {
192 p->save();
193 if (isRowDisassCode(row)) {
194 p->setPen(blue);
196 KTextView::paintCell(p, row, col);
197 p->restore();
198 return;
200 if (col == 0) {
201 uchar item = m_lineItems[row];
202 if (item == 0) /* shortcut out */
203 return;
204 int h = cellHeight(row);
205 if (item & liBP) {
206 // enabled breakpoint
207 int y = (h - m_brkena.height())/2;
208 if (y < 0) y = 0;
209 p->drawPixmap(0,y,m_brkena);
211 if (item & liBPdisabled) {
212 // disabled breakpoint
213 int y = (h - m_brkdis.height())/2;
214 if (y < 0) y = 0;
215 p->drawPixmap(0,y,m_brkdis);
217 if (item & liBPtemporary) {
218 // temporary breakpoint marker
219 int y = (h - m_brktmp.height())/2;
220 if (y < 0) y = 0;
221 p->drawPixmap(0,y,m_brktmp);
223 if (item & liBPconditional) {
224 // conditional breakpoint marker
225 int y = (h - m_brkcond.height())/2;
226 if (y < 0) y = 0;
227 p->drawPixmap(0,y,m_brkcond);
229 if (item & liBPorphan) {
230 // orphaned breakpoint marker
231 int y = (h - m_brkcond.height())/2;
232 if (y < 0) y = 0;
233 p->drawPixmap(0,y,m_brkorph);
235 if (item & liPC) {
236 // program counter in innermost frame
237 int y = (h - m_pcinner.height())/2;
238 if (y < 0) y = 0;
239 p->drawPixmap(0,y,m_pcinner);
241 if (item & liPCup) {
242 // program counter somewhere up the stack
243 int y = (h - m_pcup.height())/2;
244 if (y < 0) y = 0;
245 p->drawPixmap(0,y,m_pcup);
247 return;
249 if (col == 1) {
250 if (!isRowDisassCode(row) && m_sourceCode[rowToLine(row)].canDisass) {
251 int h = cellHeight(row);
252 int w = cellWidth(col);
253 int x = w/2;
254 int y = h/2;
255 p->drawLine(x-2, y, x+2, y);
256 if (!isRowExpanded(row)) {
257 p->drawLine(x, y-2, x, y+2);
263 void SourceWindow::updateLineItems(const KDebugger* dbg)
265 // clear outdated breakpoints
266 for (int i = m_lineItems.size()-1; i >= 0; i--) {
267 if (m_lineItems[i] & liBPany) {
268 // check if this breakpoint still exists
269 int line = rowToLine(i);
270 TRACE(QString().sprintf("checking for bp at %d", line));
271 int j;
272 for (j = dbg->numBreakpoints()-1; j >= 0; j--) {
273 const Breakpoint* bp = dbg->breakpoint(j);
274 if (bp->lineNo == line &&
275 fileNameMatches(bp->fileName) &&
276 lineToRow(line, bp->address) == i)
278 // yes it exists; mode is changed below
279 break;
282 if (j < 0) {
283 /* doesn't exist anymore, remove it */
284 m_lineItems[i] &= ~liBPany;
285 updateLineItem(i);
290 // add new breakpoints
291 for (int j = dbg->numBreakpoints()-1; j >= 0; j--) {
292 const Breakpoint* bp = dbg->breakpoint(j);
293 if (fileNameMatches(bp->fileName)) {
294 TRACE(QString().sprintf("updating %s:%d", bp->fileName.data(), bp->lineNo));
295 int i = bp->lineNo;
296 if (i < 0 || i >= m_sourceCode.size())
297 continue;
298 // compute new line item flags for breakpoint
299 uchar flags = bp->enabled ? liBP : liBPdisabled;
300 if (bp->temporary)
301 flags |= liBPtemporary;
302 if (!bp->condition.isEmpty() || bp->ignoreCount != 0)
303 flags |= liBPconditional;
304 if (bp->isOrphaned())
305 flags |= liBPorphan;
306 // update if changed
307 int row = lineToRow(i, bp->address);
308 if ((m_lineItems[row] & liBPany) != flags) {
309 m_lineItems[row] &= ~liBPany;
310 m_lineItems[row] |= flags;
311 updateLineItem(row);
317 void SourceWindow::updateLineItem(int row)
319 updateCell(row, 0);
322 void SourceWindow::setPC(bool set, int lineNo, const DbgAddr& address, int frameNo)
324 if (lineNo < 0 || lineNo >= int(m_sourceCode.size())) {
325 return;
328 int row = lineToRow(lineNo, address);
330 uchar flag = frameNo == 0 ? liPC : liPCup;
331 if (set) {
332 // set only if not already set
333 if ((m_lineItems[row] & flag) == 0) {
334 m_lineItems[row] |= flag;
335 updateLineItem(row);
337 } else {
338 // clear only if not set
339 if ((m_lineItems[row] & flag) != 0) {
340 m_lineItems[row] &= ~flag;
341 updateLineItem(row);
346 void SourceWindow::find(const QString& text, bool caseSensitive, FindDirection dir)
348 ASSERT(dir == 1 || dir == -1);
349 if (m_sourceCode.size() == 0 || text.isEmpty())
350 return;
352 int line;
353 DbgAddr dummyAddr;
354 activeLine(line, dummyAddr);
355 if (line < 0)
356 line = 0;
357 int curLine = line; /* remember where we started */
358 bool found = false;
359 do {
360 // advance and wrap around
361 line += dir;
362 if (line < 0)
363 line = m_sourceCode.size()-1;
364 else if (line >= int(m_sourceCode.size()))
365 line = 0;
366 // search text
367 found = m_sourceCode[line].code.find(text, 0, caseSensitive) >= 0;
368 } while (!found && line != curLine);
370 scrollTo(line, DbgAddr());
373 void SourceWindow::mousePressEvent(QMouseEvent* ev)
375 // Check if right button was clicked.
376 if (ev->button() == RightButton)
378 emit clickedRight(ev->pos());
379 return;
382 int col = findCol(ev->x());
384 // check if event is in line item column
385 if (col == textCol()) {
386 KTextView::mousePressEvent(ev);
387 return;
390 // get row
391 int row = findRow(ev->y());
392 if (row < 0 || col < 0)
393 return;
395 if (col == 1) {
396 if (isRowExpanded(row)) {
397 actionCollapseRow(row);
398 } else {
399 actionExpandRow(row);
401 return;
404 int sourceRow;
405 int line = rowToLine(row, &sourceRow);
407 // find address if row is disassembled code
408 DbgAddr address;
409 if (row > sourceRow) {
410 // get offset from source code line
411 int off = row - sourceRow;
412 address = m_sourceCode[line].disassAddr[off-1];
415 switch (ev->button()) {
416 case LeftButton:
417 TRACE(QString().sprintf("left-clicked line %d", line));
418 emit clickedLeft(m_fileName, line, address,
419 (ev->state() & ShiftButton) != 0);
420 break;
421 case MidButton:
422 TRACE(QString().sprintf("mid-clicked row %d", line));
423 emit clickedMid(m_fileName, line, address);
424 break;
425 default:;
429 void SourceWindow::keyPressEvent(QKeyEvent* ev)
431 switch (ev->key()) {
432 case Key_Plus:
433 actionExpandRow(m_curRow);
434 return;
435 case Key_Minus:
436 actionCollapseRow(m_curRow);
437 return;
440 KTextView::keyPressEvent(ev);
443 static inline bool isident(QChar c)
445 return c.isLetterOrNumber() || c.latin1() == '_';
448 bool SourceWindow::wordAtPoint(const QPoint& p, QString& word, QRect& r)
450 int row, col = charAt(p, &row);
451 if (row < 0 || col < 0)
452 return false;
454 // isolate the word at row, col
455 QString line = m_texts[row];
456 if (!isident(line[col]))
457 return false;
459 int begin = col;
460 while (begin > 0 && isident(line[begin-1]))
461 --begin;
463 ++col;
464 while (col < line.length() && isident(line[col]));
466 r = QRect(p, p);
467 r.addCoords(-5,-5,5,5);
468 word = line.mid(begin, col-begin);
469 return true;
472 void SourceWindow::paletteChange(const QPalette& oldPal)
474 setFont(KGlobalSettings::fixedFont());
475 KTextView::paletteChange(oldPal);
479 * Two file names (possibly full paths) match if the last parts - the file
480 * names - match.
482 bool SourceWindow::fileNameMatches(const QString& other)
484 const QString& me = fileName();
486 // check for null file names first
487 if (me.isNull() || other.isNull()) {
488 return me.isNull() && other.isNull();
492 * Get file names. Note: Either there is a slash, then skip it, or
493 * there is no slash, then -1 + 1 = 0!
495 int sme = me.findRev('/') + 1;
496 int sother = other.findRev('/') + 1;
497 return strcmp(me.data() + sme, other.data() + sother) == 0;
500 void SourceWindow::disassembled(int lineNo, const QList<DisassembledCode>& disass)
502 TRACE("disassembled line " + QString().setNum(lineNo));
503 if (lineNo < 0 || lineNo >= m_sourceCode.size())
504 return;
506 SourceLine& sl = m_sourceCode[lineNo];
508 // copy disassembled code and its addresses
509 sl.disass.resize(disass.count());
510 sl.disassAddr.resize(disass.count());
511 sl.canDisass = disass.count() > 0;
512 for (uint i = 0; i < disass.count(); i++) {
513 const DisassembledCode* c =
514 const_cast<QList<DisassembledCode>&>(disass).at(i);
515 QString code = c->code;
516 while (code.endsWith("\n"))
517 code.truncate(code.length()-1);
518 sl.disass[i] = c->address.asString() + ' ' + code;
519 sl.disassAddr[i] = c->address;
522 int row = lineToRow(lineNo);
523 if (sl.canDisass) {
524 expandRow(row);
525 } else {
526 // clear expansion marker
527 updateCell(row, 1);
531 int SourceWindow::rowToLine(int row, int* sourceRow)
533 int line = row >= 0 ? m_rowToLine[row] : -1;
534 if (sourceRow != 0) {
535 // search back until we hit the first entry with the current line number
536 while (row > 0 && m_rowToLine[row-1] == line)
537 row--;
538 *sourceRow = row;
540 return line;
544 * Rows showing diassembled code have the same line number as the
545 * corresponding source code line number. Therefore, the line numbers in
546 * m_rowToLine are monotonically increasing with blocks of equal line
547 * numbers for a source line and its disassembled code that follows it.
549 * Hence, m_rowToLine always obeys the following condition:
551 * m_rowToLine[i] <= i
554 int SourceWindow::lineToRow(int line)
556 // line is zero-based!
558 assert(line < m_rowToLine.size());
560 // quick test for common case
561 if (line < 0 || m_rowToLine[line] == line)
562 return line;
564 assert(m_rowToLine[line] < line);
567 * Binary search between row == line and end of list. In the loop below
568 * we use the fact that the line numbers m_rowToLine do not contain
569 * holes.
571 int l = line;
572 int h = m_rowToLine.size();
573 while (l < h && m_rowToLine[l] != line)
575 assert(h == m_rowToLine.size() || m_rowToLine[l] < m_rowToLine[h]);
578 * We want to round down the midpoint so that we find the
579 * lowest row that belongs to the line we seek.
581 int mid = (l+h)/2;
582 if (m_rowToLine[mid] <= line)
583 l = mid;
584 else
585 h = mid;
587 // Found! Result is in l:
588 assert(m_rowToLine[l] == line);
591 * We might not have hit the lowest index for the line.
593 while (l > 0 && m_rowToLine[l-1] == line)
594 --l;
596 return l;
599 int SourceWindow::lineToRow(int line, const DbgAddr& address)
601 int row = lineToRow(line);
602 if (isRowExpanded(row)) {
603 row += m_sourceCode[line].findAddressRowOffset(address);
605 return row;
608 bool SourceWindow::isRowExpanded(int row)
610 assert(row >= 0);
611 return row < m_rowToLine.size()-1 &&
612 m_rowToLine[row] == m_rowToLine[row+1];
615 bool SourceWindow::isRowDisassCode(int row)
617 return row > 0 &&
618 m_rowToLine[row] == m_rowToLine[row-1];
621 void SourceWindow::expandRow(int row)
623 TRACE("expanding row " + QString().setNum(row));
624 // get disassembled code
625 int line = rowToLine(row);
626 const std::vector<QString>& disass = m_sourceCode[line].disass;
628 // remove PC (must be set again in slot of signal expanded())
629 m_lineItems[row] &= ~(liPC|liPCup);
631 // insert new lines
632 ++row;
633 m_rowToLine.insert(m_rowToLine.begin()+row, disass.size(), line);
634 m_lineItems.insert(m_lineItems.begin()+row, disass.size(), 0);
635 m_texts.insert(m_texts.begin()+row, disass.begin(), disass.end());
637 bool autoU = autoUpdate();
638 setAutoUpdate(false);
640 // update line widths
641 for (int i = 0; i < disass.size(); i++) {
642 updateCellSize(disass[i]);
645 setNumRows(m_texts.size());
647 emit expanded(line); /* must set PC */
649 setAutoUpdate(autoU);
650 if (autoU && isVisible()) {
651 updateTableSize();
652 update();
656 void SourceWindow::collapseRow(int row)
658 TRACE("collapsing row " + QString().setNum(row));
659 int line = rowToLine(row);
661 // find end of this block
662 int end = row+1;
663 while (end < m_rowToLine.size() && m_rowToLine[end] == m_rowToLine[row]) {
664 end++;
666 ++row;
667 m_rowToLine.erase(m_rowToLine.begin()+row, m_rowToLine.begin()+end);
668 m_lineItems.erase(m_lineItems.begin()+row, m_lineItems.begin()+end);
669 m_texts.erase(m_texts.begin()+row, m_texts.begin()+end);
671 bool autoU = autoUpdate();
672 setAutoUpdate(false);
674 setNumRows(m_texts.size());
676 emit collapsed(line);
678 setAutoUpdate(autoU);
679 if (autoU && isVisible()) {
680 updateTableSize();
681 update();
685 void SourceWindow::activeLine(int& line, DbgAddr& address)
687 int row, col;
688 cursorPosition(&row, &col);
690 int sourceRow;
691 line = rowToLine(row, &sourceRow);
692 if (row > sourceRow) {
693 int off = row - sourceRow; /* offset from source line */
694 address = m_sourceCode[line].disassAddr[off-1];
699 * Returns the offset from the line displaying the source code to
700 * the line containing the specified address. If the address is not
701 * found, 0 is returned.
703 int SourceWindow::SourceLine::findAddressRowOffset(const DbgAddr& address) const
705 if (address.isEmpty())
706 return 0;
708 for (int i = 0; i < disassAddr.size(); i++) {
709 if (disassAddr[i] == address) {
710 // found exact address
711 return i+1;
713 if (disassAddr[i] > address) {
715 * We have already advanced too far; the address is before this
716 * index, but obviously we haven't found an exact match
717 * earlier. address is somewhere between the displayed
718 * addresses. We return the previous line.
720 return i;
723 // not found
724 return 0;
727 void SourceWindow::actionExpandRow(int row)
729 if (isRowExpanded(row) || isRowDisassCode(row)) {
730 // do nothing
731 return;
734 // disassemble
735 int line = rowToLine(row);
736 const SourceLine& sl = m_sourceCode[line];
737 if (!sl.canDisass)
738 return;
739 if (sl.disass.size() == 0) {
740 emit disassemble(m_fileName, line);
741 } else {
742 expandRow(row);
746 void SourceWindow::actionCollapseRow(int row)
748 if (!isRowExpanded(row) || isRowDisassCode(row)) {
749 // do nothing
750 return;
753 collapseRow(row);
757 #include "sourcewnd.moc"