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>
22 SourceWindow::SourceWindow(const char* fileName
, QWidget
* parent
, const char* name
) :
23 KTextView(parent
, name
),
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
47 if (!f
.open(IO_ReadOnly
)) {
55 // then we copy it into our own m_sourceCode
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
);
63 m_lineItems
.resize(n
, 0);
68 void SourceWindow::reloadFile()
71 if (!f
.open(IO_ReadOnly
)) {
72 // open failed; leave alone
76 // read text into m_sourceCode
77 m_sourceCode
.clear(); /* clear old text */
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
++)
95 void SourceWindow::scrollTo(int lineNo
, const DbgAddr
& address
)
97 if (lineNo
< 0 || lineNo
>= int(m_sourceCode
.size()))
100 int row
= lineToRow(lineNo
, address
);
104 void SourceWindow::scrollToRow(int row
)
106 if (row
>= numRows())
110 if (row
>= topCell() && row
<= lastRowVisible()) {
111 // line is already visible
112 setCursorPosition(row
, 0);
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);
126 int SourceWindow::textCol() const
128 // text is in column 2
132 int SourceWindow::cellWidth(int col
) const
136 } else if (col
== 1) {
139 return KTextView::cellWidth(col
);
143 void SourceWindow::paintCell(QPainter
* p
, int row
, int col
)
145 if (col
== textCol()) {
147 if (isRowDisassCode(row
)) {
150 KTextView::paintCell(p
, row
, col
);
155 uchar item
= m_lineItems
[row
];
156 if (item
== 0) /* shortcut out */
158 int h
= cellHeight(row
);
160 // enabled breakpoint
161 int y
= (h
- m_brkena
.height())/2;
163 p
->drawPixmap(0,y
,m_brkena
);
165 if (item
& liBPdisabled
) {
166 // disabled breakpoint
167 int y
= (h
- m_brkdis
.height())/2;
169 p
->drawPixmap(0,y
,m_brkdis
);
171 if (item
& liBPtemporary
) {
172 // temporary breakpoint marker
173 int y
= (h
- m_brktmp
.height())/2;
175 p
->drawPixmap(0,y
,m_brktmp
);
177 if (item
& liBPconditional
) {
178 // conditional breakpoint marker
179 int y
= (h
- m_brkcond
.height())/2;
181 p
->drawPixmap(0,y
,m_brkcond
);
183 if (item
& liBPorphan
) {
184 // orphaned breakpoint marker
185 int y
= (h
- m_brkcond
.height())/2;
187 p
->drawPixmap(0,y
,m_brkorph
);
190 // program counter in innermost frame
191 int y
= (h
- m_pcinner
.height())/2;
193 p
->drawPixmap(0,y
,m_pcinner
);
196 // program counter somewhere up the stack
197 int y
= (h
- m_pcup
.height())/2;
199 p
->drawPixmap(0,y
,m_pcup
);
204 if (!isRowDisassCode(row
) && m_sourceCode
[rowToLine(row
)].canDisass
) {
205 int h
= cellHeight(row
);
206 int w
= cellWidth(col
);
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
));
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
237 /* doesn't exist anymore, remove it */
238 m_lineItems
[i
] &= ~liBPany
;
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
));
250 if (i
< 0 || i
>= int(m_sourceCode
.size()))
252 // compute new line item flags for breakpoint
253 uchar flags
= bp
->enabled
? liBP
: liBPdisabled
;
255 flags
|= liBPtemporary
;
256 if (!bp
->condition
.isEmpty() || bp
->ignoreCount
!= 0)
257 flags
|= liBPconditional
;
258 if (bp
->isOrphaned())
261 int row
= lineToRow(i
, bp
->address
);
262 if ((m_lineItems
[row
] & liBPany
) != flags
) {
263 m_lineItems
[row
] &= ~liBPany
;
264 m_lineItems
[row
] |= flags
;
271 void SourceWindow::updateLineItem(int row
)
276 void SourceWindow::setPC(bool set
, int lineNo
, const DbgAddr
& address
, int frameNo
)
278 if (lineNo
< 0 || lineNo
>= int(m_sourceCode
.size())) {
282 int row
= lineToRow(lineNo
, address
);
284 uchar flag
= frameNo
== 0 ? liPC
: liPCup
;
286 // set only if not already set
287 if ((m_lineItems
[row
] & flag
) == 0) {
288 m_lineItems
[row
] |= flag
;
292 // clear only if not set
293 if ((m_lineItems
[row
] & flag
) != 0) {
294 m_lineItems
[row
] &= ~flag
;
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())
308 activeLine(line
, dummyAddr
);
311 int curLine
= line
; /* remember where we started */
314 // advance and wrap around
317 line
= m_sourceCode
.size()-1;
318 else if (line
>= int(m_sourceCode
.size()))
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());
336 int col
= findCol(ev
->x());
338 // check if event is in line item column
339 if (col
== textCol()) {
340 KTextView::mousePressEvent(ev
);
345 int row
= findRow(ev
->y());
346 if (row
< 0 || col
< 0)
350 if (isRowExpanded(row
)) {
351 actionCollapseRow(row
);
353 actionExpandRow(row
);
359 int line
= rowToLine(row
, &sourceRow
);
361 // find address if row is disassembled code
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()) {
371 TRACE(QString().sprintf("left-clicked line %d", line
));
372 emit
clickedLeft(m_fileName
, line
, address
,
373 (ev
->state() & ShiftButton
) != 0);
376 TRACE(QString().sprintf("mid-clicked row %d", line
));
377 emit
clickedMid(m_fileName
, line
, address
);
383 void SourceWindow::keyPressEvent(QKeyEvent
* ev
)
387 actionExpandRow(m_curRow
);
390 actionCollapseRow(m_curRow
);
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)
408 // isolate the word at row, col
409 QString line
= text(row
);
410 if (!isident(line
[col
]))
414 while (begin
> 0 && isident(line
[begin
-1]))
418 while (col
< int(line
.length()) && isident(line
[col
]));
421 r
.addCoords(-5,-5,5,5);
422 word
= line
.mid(begin
, col
-begin
);
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
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()))
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
);
480 // clear expansion marker
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
)
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
)
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
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.
536 if (m_rowToLine
[mid
] <= line
)
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
)
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
);
562 bool SourceWindow::isRowExpanded(int row
)
565 return row
< int(m_rowToLine
.size())-1 &&
566 m_rowToLine
[row
] == m_rowToLine
[row
+1];
569 bool SourceWindow::isRowDisassCode(int row
)
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
);
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
604 while (end
< int(m_rowToLine
.size()) && m_rowToLine
[end
] == m_rowToLine
[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
);
611 removeParagraph(end
);
612 update(); // line items
614 emit
collapsed(line
);
617 void SourceWindow::activeLine(int& line
, DbgAddr
& address
)
620 cursorPosition(&row
, &col
);
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())
640 for (size_t i
= 0; i
< disassAddr
.size(); i
++) {
641 if (disassAddr
[i
] == address
) {
642 // found exact address
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.
659 void SourceWindow::actionExpandRow(int row
)
661 if (isRowExpanded(row
) || isRowDisassCode(row
)) {
667 int line
= rowToLine(row
);
668 const SourceLine
& sl
= m_sourceCode
[line
];
671 if (sl
.disass
.size() == 0) {
672 emit
disassemble(m_fileName
, line
);
678 void SourceWindow::actionCollapseRow(int row
)
680 if (!isRowExpanded(row
) || isRowDisassCode(row
)) {
689 #include "sourcewnd.moc"