3 // Copyright by Johannes Sixt
4 // This file is under GPL, the GNU General Public Licence
8 #include <qtextstream.h>
14 #include <kiconloader.h>
16 #include <kglobalsettings.h>
26 SourceWindow::SourceWindow(const char* fileName
, QWidget
* parent
, const char* name
) :
27 KTextView(parent
, name
),
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
);
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());
53 SourceWindow::~SourceWindow()
57 void SourceWindow::loadFile()
59 // first we load the code into KTextView::m_texts
61 if (f
.open(IO_ReadOnly
)) {
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
];
82 void SourceWindow::reloadFile()
85 if (!f
.open(IO_ReadOnly
)) {
86 // open failed; leave alone
90 // read text into m_sourceCode
91 m_sourceCode
.setSize(0); /* clear old text */
96 s
.code
= t
.readLine();
97 m_sourceCode
.append(s
);
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
--) {
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 */
133 setNumRows(m_texts
.size());
135 m_rowToLine
.setSize(m_texts
.size());
136 for (int i
= 0; i
< m_texts
.size(); i
++)
139 setAutoUpdate(autoU
);
140 if (autoU
&& isVisible()) {
145 void SourceWindow::scrollTo(int lineNo
, const DbgAddr
& address
)
147 if (lineNo
< 0 || lineNo
>= m_sourceCode
.size())
150 int row
= lineToRow(lineNo
, address
);
154 void SourceWindow::scrollToRow(int row
)
156 if (row
>= numRows())
160 if (row
>= topCell() && row
<= lastRowVisible()) {
161 // line is already visible
162 setCursorPosition(row
, 0);
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);
176 int SourceWindow::textCol() const
178 // text is in column 2
182 int SourceWindow::cellWidth(int col
)
186 } else if (col
== 1) {
189 return KTextView::cellWidth(col
);
193 void SourceWindow::paintCell(QPainter
* p
, int row
, int col
)
195 if (col
== textCol()) {
197 if (isRowDisassCode(row
)) {
200 KTextView::paintCell(p
, row
, col
);
205 uchar item
= m_lineItems
[row
];
206 if (item
== 0) /* shortcut out */
208 int h
= cellHeight(row
);
210 // enabled breakpoint
211 int y
= (h
- m_brkena
.height())/2;
213 p
->drawPixmap(0,y
,m_brkena
);
215 if (item
& liBPdisabled
) {
216 // disabled breakpoint
217 int y
= (h
- m_brkdis
.height())/2;
219 p
->drawPixmap(0,y
,m_brkdis
);
221 if (item
& liBPtemporary
) {
222 // temporary breakpoint marker
223 int y
= (h
- m_brktmp
.height())/2;
225 p
->drawPixmap(0,y
,m_brktmp
);
227 if (item
& liBPconditional
) {
228 // conditional breakpoint marker
229 int y
= (h
- m_brkcond
.height())/2;
231 p
->drawPixmap(0,y
,m_brkcond
);
234 // program counter in innermost frame
235 int y
= (h
- m_pcinner
.height())/2;
237 p
->drawPixmap(0,y
,m_pcinner
);
240 // program counter somewhere up the stack
241 int y
= (h
- m_pcup
.height())/2;
243 p
->drawPixmap(0,y
,m_pcup
);
248 if (!isRowDisassCode(row
) && m_sourceCode
[rowToLine(row
)].canDisass
) {
249 int h
= cellHeight(row
);
250 int w
= cellWidth(col
);
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
));
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
281 /* doesn't exist anymore, remove it */
282 m_lineItems
[i
] &= ~liBPany
;
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
));
294 if (i
< 0 || i
>= m_sourceCode
.size())
296 // compute new line item flags for breakpoint
297 uchar flags
= bp
->enabled
? liBP
: liBPdisabled
;
299 flags
|= liBPtemporary
;
300 if (!bp
->condition
.isEmpty() || bp
->ignoreCount
!= 0)
301 flags
|= liBPconditional
;
303 int row
= lineToRow(i
, bp
->address
);
304 if ((m_lineItems
[row
] & liBPany
) != flags
) {
305 m_lineItems
[row
] &= ~liBPany
;
306 m_lineItems
[row
] |= flags
;
313 void SourceWindow::updateLineItem(int row
)
318 void SourceWindow::setPC(bool set
, int lineNo
, const DbgAddr
& address
, int frameNo
)
320 if (lineNo
< 0 || lineNo
>= int(m_sourceCode
.size())) {
324 int row
= lineToRow(lineNo
, address
);
326 uchar flag
= frameNo
== 0 ? liPC
: liPCup
;
328 // set only if not already set
329 if ((m_lineItems
[row
] & flag
) == 0) {
330 m_lineItems
[row
] |= flag
;
334 // clear only if not set
335 if ((m_lineItems
[row
] & flag
) != 0) {
336 m_lineItems
[row
] &= ~flag
;
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())
350 activeLine(line
, dummyAddr
);
353 int curLine
= line
; /* remember where we started */
356 // advance and wrap around
359 line
= m_sourceCode
.size()-1;
360 else if (line
>= int(m_sourceCode
.size()))
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());
378 int col
= findCol(ev
->x());
380 // check if event is in line item column
381 if (col
== textCol()) {
382 KTextView::mousePressEvent(ev
);
387 int row
= findRow(ev
->y());
388 if (row
< 0 || col
< 0)
392 if (isRowExpanded(row
)) {
393 actionCollapseRow(row
);
395 actionExpandRow(row
);
401 int line
= rowToLine(row
, &sourceRow
);
403 // find address if row is disassembled code
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()) {
413 TRACE(QString().sprintf("left-clicked line %d", line
));
414 emit
clickedLeft(m_fileName
, line
, address
,
415 (ev
->state() & ShiftButton
) != 0);
418 TRACE(QString().sprintf("mid-clicked row %d", line
));
419 emit
clickedMid(m_fileName
, line
, address
);
425 void SourceWindow::keyPressEvent(QKeyEvent
* ev
)
429 actionExpandRow(m_curRow
);
432 actionCollapseRow(m_curRow
);
436 KTextView::keyPressEvent(ev
);
440 #define ISIDENT(c) (isalnum((c)) || (c) == '_')
442 #define ISIDENT(c) ((c).isLetterOrNumber() || (c) == '_')
445 bool SourceWindow::wordAtPoint(const QPoint
& p
, QString
& word
, QRect
& r
)
447 if (findCol(p
.x()) != textCol())
449 int row
= findRow(p
.y());
453 // find top-left corner of text cell
455 if (!colXPos(textCol(), &left
))
457 if (!rowYPos(row
, &top
))
460 // get the bounding rect of the text
461 QPainter
painter(this);
462 const QString
& text
= m_texts
[row
];
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.
476 while (start
< text
.length()) {
477 while (start
< text
.length() && !ISIDENT(text
[start
]))
479 if (start
>= text
.length())
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.
486 painter
.boundingRect(left
+2, top
, 0,0,
487 AlignLeft
| SingleLine
| DontClip
| ExpandTabs
,
489 if (bound
.contains(p
))
492 int startWidth
= bound
.width();
494 while (end
< text
.length() && ISIDENT(text
[end
]))
497 painter
.boundingRect(left
+2, top
, 0,0,
498 AlignLeft
| SingleLine
| DontClip
| ExpandTabs
,
500 if (bound
.contains(p
)) {
503 word
= text
.mid(start
, end
-start
);
505 r
= QRect(bound
.x()+startWidth
,bound
.y(),
506 bound
.width()-startWidth
, bound
.height());
514 void SourceWindow::paletteChange(const QPalette
& oldPal
)
517 setFont(kapp
->fixedFont
);
519 setFont(KGlobalSettings::fixedFont());
521 KTextView::paletteChange(oldPal
);
525 * Two file names (possibly full paths) match if the last parts - the file
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())
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
);
569 // clear expansion marker
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
)
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
)
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
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.
625 if (m_rowToLine
[mid
] <= line
)
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
)
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
);
651 bool SourceWindow::isRowExpanded(int row
)
654 return row
< m_rowToLine
.size()-1 &&
655 m_rowToLine
[row
] == m_rowToLine
[row
+1];
658 bool SourceWindow::isRowDisassCode(int row
)
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
);
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())
697 void SourceWindow::collapseRow(int row
)
699 TRACE("collapsing row " + QString().setNum(row
));
700 int line
= rowToLine(row
);
702 // find end of this block
704 while (end
< m_rowToLine
.size() && m_rowToLine
[end
] == m_rowToLine
[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())
724 void SourceWindow::activeLine(int& line
, DbgAddr
& address
)
727 cursorPosition(&row
, &col
);
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())
747 for (int i
= 0; i
< disassAddr
.size(); i
++) {
748 if (disassAddr
[i
] == address
) {
749 // found exact address
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.
766 void SourceWindow::actionExpandRow(int row
)
768 if (isRowExpanded(row
) || isRowDisassCode(row
)) {
774 int line
= rowToLine(row
);
775 const SourceLine
& sl
= m_sourceCode
[line
];
778 if (sl
.disass
.size() == 0) {
779 emit
disassemble(m_fileName
, line
);
785 void SourceWindow::actionCollapseRow(int row
)
787 if (!isRowExpanded(row
) || isRowDisassCode(row
)) {
796 #include "sourcewnd.moc"