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 QTextEdit(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());
39 setMargins(m_widthItems
+m_widthPlus
, 0, 0 ,0);
40 setAutoFormatting(AutoNone
);
41 setTextFormat(PlainText
);
43 connect(verticalScrollBar(), SIGNAL(valueChanged(int)),
44 this, SLOT(update()));
45 connect(this, SIGNAL(cursorPositionChanged(int,int)), this, SLOT(cursorChanged(int)));
48 SourceWindow::~SourceWindow()
52 bool SourceWindow::loadFile()
54 // first we load the code into QTextEdit
56 if (!f
.open(IO_ReadOnly
)) {
64 // then we copy it into our own m_sourceCode
66 m_sourceCode
.resize(n
);
67 m_rowToLine
.resize(n
);
68 for (int i
= 0; i
< n
; i
++) {
69 m_sourceCode
[i
].code
= text(i
);
72 m_lineItems
.resize(n
, 0);
77 void SourceWindow::reloadFile()
80 if (!f
.open(IO_ReadOnly
)) {
81 // open failed; leave alone
85 // read text into m_sourceCode
86 m_sourceCode
.clear(); /* clear old text */
92 m_sourceCode
.resize(paragraphs());
93 for (size_t i
= 0; i
< m_sourceCode
.size(); i
++) {
94 m_sourceCode
[i
].code
= text(i
);
96 // allocate line items
97 m_lineItems
.resize(m_sourceCode
.size(), 0);
99 m_rowToLine
.resize(m_sourceCode
.size());
100 for (size_t i
= 0; i
< m_sourceCode
.size(); i
++)
104 void SourceWindow::scrollTo(int lineNo
, const DbgAddr
& address
)
106 if (lineNo
< 0 || lineNo
>= int(m_sourceCode
.size()))
109 int row
= lineToRow(lineNo
, address
);
113 void SourceWindow::scrollToRow(int row
)
115 setCursorPosition(row
, 0);
116 ensureCursorVisible();
119 void SourceWindow::drawFrame(QPainter
* p
)
121 QTextEdit::drawFrame(p
);
123 // and paragraph at the top is...
124 int top
= paragraphAt(QPoint(0,contentsY()));
125 int bot
= paragraphAt(QPoint(0,contentsY()+visibleHeight()-1));
127 bot
= paragraphs()-1;
131 // set a clip rectangle
132 int fw
= frameWidth();
133 QRect inside
= rect();
134 inside
.addCoords(fw
,fw
,-fw
,-fw
);
135 QRegion clip
= p
->clipRegion();
136 clip
&= QRegion(inside
);
137 p
->setClipRegion(clip
);
139 p
->setPen(colorGroup().text());
140 p
->eraseRect(inside
);
142 for (int row
= top
; row
<= bot
; row
++)
144 uchar item
= m_lineItems
[row
];
147 QRect r
= paragraphRect(row
);
148 QPoint pt
= contentsToViewport(r
.topLeft());
150 p
->translate(fw
, pt
.y()+viewport()->y());
153 // enabled breakpoint
154 int y
= (h
- m_brkena
.height())/2;
156 p
->drawPixmap(0,y
,m_brkena
);
158 if (item
& liBPdisabled
) {
159 // disabled breakpoint
160 int y
= (h
- m_brkdis
.height())/2;
162 p
->drawPixmap(0,y
,m_brkdis
);
164 if (item
& liBPtemporary
) {
165 // temporary breakpoint marker
166 int y
= (h
- m_brktmp
.height())/2;
168 p
->drawPixmap(0,y
,m_brktmp
);
170 if (item
& liBPconditional
) {
171 // conditional breakpoint marker
172 int y
= (h
- m_brkcond
.height())/2;
174 p
->drawPixmap(0,y
,m_brkcond
);
176 if (item
& liBPorphan
) {
177 // orphaned breakpoint marker
178 int y
= (h
- m_brkcond
.height())/2;
180 p
->drawPixmap(0,y
,m_brkorph
);
183 // program counter in innermost frame
184 int y
= (h
- m_pcinner
.height())/2;
186 p
->drawPixmap(0,y
,m_pcinner
);
189 // program counter somewhere up the stack
190 int y
= (h
- m_pcup
.height())/2;
192 p
->drawPixmap(0,y
,m_pcup
);
194 if (!isRowDisassCode(row
) && m_sourceCode
[rowToLine(row
)].canDisass
) {
196 p
->translate(m_widthItems
, 0);
199 p
->drawLine(x
-2, y
, x
+2, y
);
200 if (!isRowExpanded(row
)) {
201 p
->drawLine(x
, y
-2, x
, y
+2);
209 void SourceWindow::updateLineItems(const KDebugger
* dbg
)
211 // clear outdated breakpoints
212 for (int i
= m_lineItems
.size()-1; i
>= 0; i
--) {
213 if (m_lineItems
[i
] & liBPany
) {
214 // check if this breakpoint still exists
215 int line
= rowToLine(i
);
216 TRACE(QString().sprintf("checking for bp at %d", line
));
218 for (j
= dbg
->numBreakpoints()-1; j
>= 0; j
--) {
219 const Breakpoint
* bp
= dbg
->breakpoint(j
);
220 if (bp
->lineNo
== line
&&
221 fileNameMatches(bp
->fileName
) &&
222 lineToRow(line
, bp
->address
) == i
)
224 // yes it exists; mode is changed below
229 /* doesn't exist anymore, remove it */
230 m_lineItems
[i
] &= ~liBPany
;
236 // add new breakpoints
237 for (int j
= dbg
->numBreakpoints()-1; j
>= 0; j
--) {
238 const Breakpoint
* bp
= dbg
->breakpoint(j
);
239 if (fileNameMatches(bp
->fileName
)) {
240 TRACE(QString().sprintf("updating %s:%d", bp
->fileName
.data(), bp
->lineNo
));
242 if (i
< 0 || i
>= int(m_sourceCode
.size()))
244 // compute new line item flags for breakpoint
245 uchar flags
= bp
->enabled
? liBP
: liBPdisabled
;
247 flags
|= liBPtemporary
;
248 if (!bp
->condition
.isEmpty() || bp
->ignoreCount
!= 0)
249 flags
|= liBPconditional
;
250 if (bp
->isOrphaned())
253 int row
= lineToRow(i
, bp
->address
);
254 if ((m_lineItems
[row
] & liBPany
) != flags
) {
255 m_lineItems
[row
] &= ~liBPany
;
256 m_lineItems
[row
] |= flags
;
263 void SourceWindow::setPC(bool set
, int lineNo
, const DbgAddr
& address
, int frameNo
)
265 if (lineNo
< 0 || lineNo
>= int(m_sourceCode
.size())) {
269 int row
= lineToRow(lineNo
, address
);
271 uchar flag
= frameNo
== 0 ? liPC
: liPCup
;
273 // set only if not already set
274 if ((m_lineItems
[row
] & flag
) == 0) {
275 m_lineItems
[row
] |= flag
;
279 // clear only if not set
280 if ((m_lineItems
[row
] & flag
) != 0) {
281 m_lineItems
[row
] &= ~flag
;
287 void SourceWindow::find(const QString
& text
, bool caseSensitive
, FindDirection dir
)
289 ASSERT(dir
== 1 || dir
== -1);
290 if (m_sourceCode
.size() == 0 || text
.isEmpty())
295 activeLine(line
, dummyAddr
);
298 int curLine
= line
; /* remember where we started */
301 // advance and wrap around
304 line
= m_sourceCode
.size()-1;
305 else if (line
>= int(m_sourceCode
.size()))
308 found
= m_sourceCode
[line
].code
.find(text
, 0, caseSensitive
) >= 0;
309 } while (!found
&& line
!= curLine
);
311 scrollTo(line
, DbgAddr());
314 void SourceWindow::mousePressEvent(QMouseEvent
* ev
)
316 // Check if right button was clicked.
317 if (ev
->button() == RightButton
)
319 emit
clickedRight(ev
->pos());
324 QPoint p
= viewportToContents(QPoint(0, ev
->y() - viewport()->y()));
325 int row
= paragraphAt(p
);
329 if (ev
->x() > m_widthItems
+frameWidth())
331 if (isRowExpanded(row
)) {
332 actionCollapseRow(row
);
334 actionExpandRow(row
);
340 int line
= rowToLine(row
, &sourceRow
);
342 // find address if row is disassembled code
344 if (row
> sourceRow
) {
345 // get offset from source code line
346 int off
= row
- sourceRow
;
347 address
= m_sourceCode
[line
].disassAddr
[off
-1];
350 switch (ev
->button()) {
352 TRACE(QString().sprintf("left-clicked line %d", line
));
353 emit
clickedLeft(m_fileName
, line
, address
,
354 (ev
->state() & ShiftButton
) != 0);
357 TRACE(QString().sprintf("mid-clicked row %d", line
));
358 emit
clickedMid(m_fileName
, line
, address
);
364 void SourceWindow::keyPressEvent(QKeyEvent
* ev
)
370 actionExpandRow(m_curRow
);
373 actionCollapseRow(m_curRow
);
377 setCursorPosition(m_curRow
-1, 0);
381 if (m_curRow
< paragraphs()-1) {
382 setCursorPosition(m_curRow
+1, 0);
386 setCursorPosition(0, 0);
389 setCursorPosition(paragraphs()-1, 0);
393 top
= viewportToContents(QPoint(0,0));
394 top1
= paragraphAt(top
);
397 QTextEdit::keyPressEvent(ev
);
402 top
= viewportToContents(QPoint(0,0));
403 top2
= paragraphAt(top
);
404 setCursorPosition(m_curRow
+(top2
-top1
), 0);
408 static inline bool isident(QChar c
)
410 return c
.isLetterOrNumber() || c
.latin1() == '_';
413 bool SourceWindow::wordAtPoint(const QPoint
& p
, QString
& word
, QRect
& r
)
415 QPoint pv
= viewportToContents(p
- viewport()->pos());
416 int row
, col
= charAt(pv
, &row
);
417 if (row
< 0 || col
< 0)
420 // isolate the word at row, col
421 QString line
= text(row
);
422 if (!isident(line
[col
]))
426 while (begin
> 0 && isident(line
[begin
-1]))
430 while (col
< int(line
.length()) && isident(line
[col
]));
433 r
.addCoords(-5,-5,5,5);
434 word
= line
.mid(begin
, col
-begin
);
438 void SourceWindow::paletteChange(const QPalette
& oldPal
)
440 setFont(KGlobalSettings::fixedFont());
441 QTextEdit::paletteChange(oldPal
);
445 * Two file names (possibly full paths) match if the last parts - the file
448 bool SourceWindow::fileNameMatches(const QString
& other
)
450 const QString
& me
= fileName();
452 // check for null file names first
453 if (me
.isNull() || other
.isNull()) {
454 return me
.isNull() && other
.isNull();
458 * Get file names. Note: Either there is a slash, then skip it, or
459 * there is no slash, then -1 + 1 = 0!
461 int sme
= me
.findRev('/') + 1;
462 int sother
= other
.findRev('/') + 1;
463 return strcmp(me
.data() + sme
, other
.data() + sother
) == 0;
466 void SourceWindow::disassembled(int lineNo
, const QList
<DisassembledCode
>& disass
)
468 TRACE("disassembled line " + QString().setNum(lineNo
));
469 if (lineNo
< 0 || lineNo
>= int(m_sourceCode
.size()))
472 SourceLine
& sl
= m_sourceCode
[lineNo
];
474 // copy disassembled code and its addresses
475 sl
.disass
.resize(disass
.count());
476 sl
.disassAddr
.resize(disass
.count());
477 sl
.canDisass
= disass
.count() > 0;
478 for (uint i
= 0; i
< disass
.count(); i
++) {
479 const DisassembledCode
* c
=
480 const_cast<QList
<DisassembledCode
>&>(disass
).at(i
);
481 QString code
= c
->code
;
482 while (code
.endsWith("\n"))
483 code
.truncate(code
.length()-1);
484 sl
.disass
[i
] = c
->address
.asString() + ' ' + code
;
485 sl
.disassAddr
[i
] = c
->address
;
488 int row
= lineToRow(lineNo
);
492 // clear expansion marker
497 int SourceWindow::rowToLine(int row
, int* sourceRow
)
499 int line
= row
>= 0 ? m_rowToLine
[row
] : -1;
500 if (sourceRow
!= 0) {
501 // search back until we hit the first entry with the current line number
502 while (row
> 0 && m_rowToLine
[row
-1] == line
)
510 * Rows showing diassembled code have the same line number as the
511 * corresponding source code line number. Therefore, the line numbers in
512 * m_rowToLine are monotonically increasing with blocks of equal line
513 * numbers for a source line and its disassembled code that follows it.
515 * Hence, m_rowToLine always obeys the following condition:
517 * m_rowToLine[i] <= i
520 int SourceWindow::lineToRow(int line
)
522 // line is zero-based!
524 assert(line
< int(m_rowToLine
.size()));
526 // quick test for common case
527 if (line
< 0 || m_rowToLine
[line
] == line
)
530 assert(m_rowToLine
[line
] < line
);
533 * Binary search between row == line and end of list. In the loop below
534 * we use the fact that the line numbers m_rowToLine do not contain
538 int h
= m_rowToLine
.size();
539 while (l
< h
&& m_rowToLine
[l
] != line
)
541 assert(h
== int(m_rowToLine
.size()) || m_rowToLine
[l
] < m_rowToLine
[h
]);
544 * We want to round down the midpoint so that we find the
545 * lowest row that belongs to the line we seek.
548 if (m_rowToLine
[mid
] <= line
)
553 // Found! Result is in l:
554 assert(m_rowToLine
[l
] == line
);
557 * We might not have hit the lowest index for the line.
559 while (l
> 0 && m_rowToLine
[l
-1] == line
)
565 int SourceWindow::lineToRow(int line
, const DbgAddr
& address
)
567 int row
= lineToRow(line
);
568 if (isRowExpanded(row
)) {
569 row
+= m_sourceCode
[line
].findAddressRowOffset(address
);
574 bool SourceWindow::isRowExpanded(int row
)
577 return row
< int(m_rowToLine
.size())-1 &&
578 m_rowToLine
[row
] == m_rowToLine
[row
+1];
581 bool SourceWindow::isRowDisassCode(int row
)
584 m_rowToLine
[row
] == m_rowToLine
[row
-1];
587 void SourceWindow::expandRow(int row
)
589 TRACE("expanding row " + QString().setNum(row
));
590 // get disassembled code
591 int line
= rowToLine(row
);
592 const std::vector
<QString
>& disass
= m_sourceCode
[line
].disass
;
594 // remove PC (must be set again in slot of signal expanded())
595 m_lineItems
[row
] &= ~(liPC
|liPCup
);
597 // adjust current row
598 if (m_curRow
> row
) {
599 m_curRow
+= disass
.size();
600 // highlight is moved automatically
605 m_rowToLine
.insert(m_rowToLine
.begin()+row
, disass
.size(), line
);
606 m_lineItems
.insert(m_lineItems
.begin()+row
, disass
.size(), 0);
607 for (size_t i
= 0; i
< disass
.size(); i
++) {
608 insertParagraph(disass
[i
], row
++);
610 update(); // line items
612 emit
expanded(line
); /* must set PC */
615 void SourceWindow::collapseRow(int row
)
617 TRACE("collapsing row " + QString().setNum(row
));
618 int line
= rowToLine(row
);
620 // find end of this block
622 while (end
< int(m_rowToLine
.size()) && m_rowToLine
[end
] == m_rowToLine
[row
]) {
626 // adjust current row
627 if (m_curRow
>= row
) {
629 if (m_curRow
< row
) // was m_curRow in disassembled code?
632 m_rowToLine
.erase(m_rowToLine
.begin()+row
, m_rowToLine
.begin()+end
);
633 m_lineItems
.erase(m_lineItems
.begin()+row
, m_lineItems
.begin()+end
);
635 removeParagraph(end
);
636 update(); // line items
638 emit
collapsed(line
);
641 void SourceWindow::activeLine(int& line
, DbgAddr
& address
)
646 line
= rowToLine(row
, &sourceRow
);
647 if (row
> sourceRow
) {
648 int off
= row
- sourceRow
; /* offset from source line */
649 address
= m_sourceCode
[line
].disassAddr
[off
-1];
654 * Returns the offset from the line displaying the source code to
655 * the line containing the specified address. If the address is not
656 * found, 0 is returned.
658 int SourceWindow::SourceLine::findAddressRowOffset(const DbgAddr
& address
) const
660 if (address
.isEmpty())
663 for (size_t i
= 0; i
< disassAddr
.size(); i
++) {
664 if (disassAddr
[i
] == address
) {
665 // found exact address
668 if (disassAddr
[i
] > address
) {
670 * We have already advanced too far; the address is before this
671 * index, but obviously we haven't found an exact match
672 * earlier. address is somewhere between the displayed
673 * addresses. We return the previous line.
682 void SourceWindow::actionExpandRow(int row
)
684 if (row
< 0 || isRowExpanded(row
) || isRowDisassCode(row
))
688 int line
= rowToLine(row
);
689 const SourceLine
& sl
= m_sourceCode
[line
];
692 if (sl
.disass
.size() == 0) {
693 emit
disassemble(m_fileName
, line
);
699 void SourceWindow::actionCollapseRow(int row
)
701 if (row
< 0 || !isRowExpanded(row
) || isRowDisassCode(row
))
707 void SourceWindow::setTabWidth(int numChars
)
711 QFontMetrics
fm(currentFont());
713 int w
= fm
.width(s
.fill('x', numChars
));
717 void SourceWindow::cursorChanged(int row
)
722 if (m_curRow
>= 0 && m_curRow
< paragraphs())
723 clearParagraphBackground(m_curRow
);
725 setParagraphBackgroundColor(row
, colorGroup().background());
731 #include "sourcewnd.moc"