3 // Copyright by Johannes Sixt
4 // This file is under GPL, the GNU General Public Licence
8 #include <qtextstream.h>
14 #include <kiconloader.h>
15 #include <kglobalsettings.h>
23 SourceWindow::SourceWindow(const char* fileName
, QWidget
* parent
, const char* name
) :
24 KTextView(parent
, name
),
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
48 if (!f
.open(IO_ReadOnly
)) {
52 bool upd
= autoUpdate();
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
];
75 m_lineItems
.resize(m_texts
.size(), 0);
80 void SourceWindow::reloadFile()
83 if (!f
.open(IO_ReadOnly
)) {
84 // open failed; leave alone
88 // read text into m_sourceCode
89 m_sourceCode
.clear(); /* clear old text */
92 std::back_insert_iterator
<std::vector
<SourceLine
> > it(m_sourceCode
);
95 s
.code
= t
.readLine();
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
);
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
++)
128 setAutoUpdate(autoU
);
129 if (autoU
&& isVisible()) {
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())
146 int row
= lineToRow(lineNo
, address
);
150 void SourceWindow::scrollToRow(int row
)
152 if (row
>= numRows())
156 if (row
>= topCell() && row
<= lastRowVisible()) {
157 // line is already visible
158 setCursorPosition(row
, 0);
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);
172 int SourceWindow::textCol() const
174 // text is in column 2
178 int SourceWindow::cellWidth(int col
) const
182 } else if (col
== 1) {
185 return KTextView::cellWidth(col
);
189 void SourceWindow::paintCell(QPainter
* p
, int row
, int col
)
191 if (col
== textCol()) {
193 if (isRowDisassCode(row
)) {
196 KTextView::paintCell(p
, row
, col
);
201 uchar item
= m_lineItems
[row
];
202 if (item
== 0) /* shortcut out */
204 int h
= cellHeight(row
);
206 // enabled breakpoint
207 int y
= (h
- m_brkena
.height())/2;
209 p
->drawPixmap(0,y
,m_brkena
);
211 if (item
& liBPdisabled
) {
212 // disabled breakpoint
213 int y
= (h
- m_brkdis
.height())/2;
215 p
->drawPixmap(0,y
,m_brkdis
);
217 if (item
& liBPtemporary
) {
218 // temporary breakpoint marker
219 int y
= (h
- m_brktmp
.height())/2;
221 p
->drawPixmap(0,y
,m_brktmp
);
223 if (item
& liBPconditional
) {
224 // conditional breakpoint marker
225 int y
= (h
- m_brkcond
.height())/2;
227 p
->drawPixmap(0,y
,m_brkcond
);
229 if (item
& liBPorphan
) {
230 // orphaned breakpoint marker
231 int y
= (h
- m_brkcond
.height())/2;
233 p
->drawPixmap(0,y
,m_brkorph
);
236 // program counter in innermost frame
237 int y
= (h
- m_pcinner
.height())/2;
239 p
->drawPixmap(0,y
,m_pcinner
);
242 // program counter somewhere up the stack
243 int y
= (h
- m_pcup
.height())/2;
245 p
->drawPixmap(0,y
,m_pcup
);
250 if (!isRowDisassCode(row
) && m_sourceCode
[rowToLine(row
)].canDisass
) {
251 int h
= cellHeight(row
);
252 int w
= cellWidth(col
);
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
));
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
283 /* doesn't exist anymore, remove it */
284 m_lineItems
[i
] &= ~liBPany
;
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
));
296 if (i
< 0 || i
>= m_sourceCode
.size())
298 // compute new line item flags for breakpoint
299 uchar flags
= bp
->enabled
? liBP
: liBPdisabled
;
301 flags
|= liBPtemporary
;
302 if (!bp
->condition
.isEmpty() || bp
->ignoreCount
!= 0)
303 flags
|= liBPconditional
;
304 if (bp
->isOrphaned())
307 int row
= lineToRow(i
, bp
->address
);
308 if ((m_lineItems
[row
] & liBPany
) != flags
) {
309 m_lineItems
[row
] &= ~liBPany
;
310 m_lineItems
[row
] |= flags
;
317 void SourceWindow::updateLineItem(int row
)
322 void SourceWindow::setPC(bool set
, int lineNo
, const DbgAddr
& address
, int frameNo
)
324 if (lineNo
< 0 || lineNo
>= int(m_sourceCode
.size())) {
328 int row
= lineToRow(lineNo
, address
);
330 uchar flag
= frameNo
== 0 ? liPC
: liPCup
;
332 // set only if not already set
333 if ((m_lineItems
[row
] & flag
) == 0) {
334 m_lineItems
[row
] |= flag
;
338 // clear only if not set
339 if ((m_lineItems
[row
] & flag
) != 0) {
340 m_lineItems
[row
] &= ~flag
;
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())
354 activeLine(line
, dummyAddr
);
357 int curLine
= line
; /* remember where we started */
360 // advance and wrap around
363 line
= m_sourceCode
.size()-1;
364 else if (line
>= int(m_sourceCode
.size()))
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());
382 int col
= findCol(ev
->x());
384 // check if event is in line item column
385 if (col
== textCol()) {
386 KTextView::mousePressEvent(ev
);
391 int row
= findRow(ev
->y());
392 if (row
< 0 || col
< 0)
396 if (isRowExpanded(row
)) {
397 actionCollapseRow(row
);
399 actionExpandRow(row
);
405 int line
= rowToLine(row
, &sourceRow
);
407 // find address if row is disassembled code
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()) {
417 TRACE(QString().sprintf("left-clicked line %d", line
));
418 emit
clickedLeft(m_fileName
, line
, address
,
419 (ev
->state() & ShiftButton
) != 0);
422 TRACE(QString().sprintf("mid-clicked row %d", line
));
423 emit
clickedMid(m_fileName
, line
, address
);
429 void SourceWindow::keyPressEvent(QKeyEvent
* ev
)
433 actionExpandRow(m_curRow
);
436 actionCollapseRow(m_curRow
);
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)
454 // isolate the word at row, col
455 QString line
= m_texts
[row
];
456 if (!isident(line
[col
]))
460 while (begin
> 0 && isident(line
[begin
-1]))
464 while (col
< line
.length() && isident(line
[col
]));
467 r
.addCoords(-5,-5,5,5);
468 word
= line
.mid(begin
, col
-begin
);
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
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())
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
);
526 // clear expansion marker
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
)
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
)
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
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.
582 if (m_rowToLine
[mid
] <= line
)
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
)
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
);
608 bool SourceWindow::isRowExpanded(int row
)
611 return row
< m_rowToLine
.size()-1 &&
612 m_rowToLine
[row
] == m_rowToLine
[row
+1];
615 bool SourceWindow::isRowDisassCode(int row
)
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
);
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()) {
656 void SourceWindow::collapseRow(int row
)
658 TRACE("collapsing row " + QString().setNum(row
));
659 int line
= rowToLine(row
);
661 // find end of this block
663 while (end
< m_rowToLine
.size() && m_rowToLine
[end
] == m_rowToLine
[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()) {
685 void SourceWindow::activeLine(int& line
, DbgAddr
& address
)
688 cursorPosition(&row
, &col
);
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())
708 for (int i
= 0; i
< disassAddr
.size(); i
++) {
709 if (disassAddr
[i
] == address
) {
710 // found exact address
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.
727 void SourceWindow::actionExpandRow(int row
)
729 if (isRowExpanded(row
) || isRowDisassCode(row
)) {
735 int line
= rowToLine(row
);
736 const SourceLine
& sl
= m_sourceCode
[line
];
739 if (sl
.disass
.size() == 0) {
740 emit
disassemble(m_fileName
, line
);
746 void SourceWindow::actionCollapseRow(int row
)
748 if (!isRowExpanded(row
) || isRowDisassCode(row
)) {
757 #include "sourcewnd.moc"