2 * Copyright Johannes Sixt
3 * This file is licensed under the GNU General Public License Version 2.
4 * See the file COPYING in the toplevel directory of the source directory.
10 #include <QTextStream>
15 #include <QContextMenuEvent>
17 #include <QMouseEvent>
18 #include <kiconloader.h>
19 #include <kglobalsettings.h>
20 #include <kxmlguiwindow.h>
21 #include <kxmlguifactory.h>
26 SourceWindow::SourceWindow(const QString
& fileName
, QWidget
* parent
) :
27 QPlainTextEdit(parent
),
33 m_lineInfoArea(new LineInfoArea(this))
36 m_pcinner
= UserIcon("pcinner");
37 m_pcup
= UserIcon("pcup");
38 m_brkena
= UserIcon("brkena");
39 m_brkdis
= UserIcon("brkdis");
40 m_brktmp
= UserIcon("brktmp");
41 m_brkcond
= UserIcon("brkcond");
42 m_brkorph
= UserIcon("brkorph");
43 setFont(KGlobalSettings::fixedFont());
45 setViewportMargins(lineInfoAreaWidth(), 0, 0 ,0);
46 setWordWrapMode(QTextOption::NoWrap
);
47 connect(this, SIGNAL(updateRequest(const QRect
&, int)),
48 m_lineInfoArea
, SLOT(update()));
49 connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(cursorChanged()));
51 // add a syntax highlighter
52 if (QRegExp("\\.(c(pp|c|\\+\\+)?|CC?|h(\\+\\+|h|pp)?|HH?)$").indexIn(m_fileName
) >= 0)
54 m_highlighter
= new HighlightCpp(this);
58 SourceWindow::~SourceWindow()
63 int SourceWindow::lineInfoAreaWidth() const
65 return 3 + m_widthItems
+ m_widthPlus
+ m_widthLineNo
;
68 bool SourceWindow::loadFile()
71 if (!f
.open(QIODevice::ReadOnly
)) {
76 setPlainText(t
.readAll());
80 m_sourceCode
.resize(n
);
81 m_rowToLine
.resize(n
);
82 for (int i
= 0; i
< n
; i
++) {
85 m_lineItems
.resize(n
, 0);
87 // set a font for line numbers
88 m_lineNoFont
= currentCharFormat().font();
89 m_lineNoFont
.setPixelSize(11);
94 void SourceWindow::reloadFile()
97 if (!f
.open(QIODevice::ReadOnly
)) {
98 // open failed; leave alone
102 m_sourceCode
.clear(); /* clear old text */
105 setPlainText(t
.readAll());
108 m_sourceCode
.resize(blockCount());
109 // expanded lines are collapsed: move existing line items up
110 for (size_t i
= 0; i
< m_lineItems
.size(); i
++) {
111 if (m_rowToLine
[i
] != int(i
)) {
112 m_lineItems
[m_rowToLine
[i
]] |= m_lineItems
[i
];
116 // allocate line items
117 m_lineItems
.resize(m_sourceCode
.size(), 0);
119 m_rowToLine
.resize(m_sourceCode
.size());
120 for (size_t i
= 0; i
< m_sourceCode
.size(); i
++)
123 // Highlighting was applied above when the text was inserted into widget,
124 // but at that time m_rowToLine was not corrected, yet, so that lines
125 // that previously were assembly were painted incorrectly.
127 m_highlighter
->rehighlight();
130 void SourceWindow::scrollTo(int lineNo
, const DbgAddr
& address
)
132 if (lineNo
< 0 || lineNo
>= int(m_sourceCode
.size()))
135 int row
= lineToRow(lineNo
, address
);
139 void SourceWindow::scrollToRow(int row
)
141 QTextCursor
cursor(document()->findBlockByNumber(row
));
142 setTextCursor(cursor
);
143 ensureCursorVisible();
146 void SourceWindow::resizeEvent(QResizeEvent
* e
)
148 QPlainTextEdit::resizeEvent(e
);
150 QRect cr
= contentsRect();
151 cr
.setRight(lineInfoAreaWidth());
152 m_lineInfoArea
->setGeometry(cr
);
155 void SourceWindow::drawLineInfoArea(QPainter
* p
, QPaintEvent
* event
)
157 QTextBlock block
= firstVisibleBlock();
159 p
->setFont(m_lineNoFont
);
161 for (; block
.isValid(); block
= block
.next())
163 if (!block
.isVisible())
166 QRect r
= blockBoundingGeometry(block
).translated(contentOffset()).toRect();
167 if (r
.bottom() < event
->rect().top())
168 continue; // skip blocks that are higher than the region being updated
169 else if (r
.top() > event
->rect().bottom())
170 break; // all the following blocks are lower then the region being updated
172 int row
= block
.blockNumber();
173 uchar item
= m_lineItems
[row
];
175 QPoint pt
= r
.topLeft();
178 p
->translate(0, r
.top());
181 // enabled breakpoint
182 int y
= (h
- m_brkena
.height())/2;
184 p
->drawPixmap(0,y
,m_brkena
);
186 if (item
& liBPdisabled
) {
187 // disabled breakpoint
188 int y
= (h
- m_brkdis
.height())/2;
190 p
->drawPixmap(0,y
,m_brkdis
);
192 if (item
& liBPtemporary
) {
193 // temporary breakpoint marker
194 int y
= (h
- m_brktmp
.height())/2;
196 p
->drawPixmap(0,y
,m_brktmp
);
198 if (item
& liBPconditional
) {
199 // conditional breakpoint marker
200 int y
= (h
- m_brkcond
.height())/2;
202 p
->drawPixmap(0,y
,m_brkcond
);
204 if (item
& liBPorphan
) {
205 // orphaned breakpoint marker
206 int y
= (h
- m_brkcond
.height())/2;
208 p
->drawPixmap(0,y
,m_brkorph
);
211 // program counter in innermost frame
212 int y
= (h
- m_pcinner
.height())/2;
214 p
->drawPixmap(0,y
,m_pcinner
);
217 // program counter somewhere up the stack
218 int y
= (h
- m_pcup
.height())/2;
220 p
->drawPixmap(0,y
,m_pcup
);
222 p
->translate(m_widthItems
, 0);
223 if (!isRowDisassCode(row
) && m_sourceCode
[rowToLine(row
)].canDisass
) {
227 p
->drawLine(x
-2, y
, x
+2, y
);
228 if (!isRowExpanded(row
)) {
229 p
->drawLine(x
, y
-2, x
, y
+2);
232 p
->translate(m_widthPlus
, 0);
233 if (!isRowDisassCode(row
)) {
234 p
->drawText(0, 0, m_widthLineNo
, h
, Qt::AlignRight
|Qt::AlignVCenter
,
235 QString().setNum(rowToLine(row
)+1));
241 void SourceWindow::updateLineItems(const KDebugger
* dbg
)
243 // clear outdated breakpoints
244 for (int i
= m_lineItems
.size()-1; i
>= 0; i
--) {
245 if (m_lineItems
[i
] & liBPany
) {
246 // check if this breakpoint still exists
247 int line
= rowToLine(i
);
248 TRACE(QString().sprintf("checking for bp at %d", line
));
249 KDebugger::BrkptROIterator bp
= dbg
->breakpointsBegin();
250 for (; bp
!= dbg
->breakpointsEnd(); ++bp
)
252 if (bp
->lineNo
== line
&&
253 fileNameMatches(bp
->fileName
) &&
254 lineToRow(line
, bp
->address
) == i
)
256 // yes it exists; mode is changed below
260 if (bp
== dbg
->breakpointsEnd()) {
261 /* doesn't exist anymore, remove it */
262 m_lineItems
[i
] &= ~liBPany
;
267 // add new breakpoints
268 for (KDebugger::BrkptROIterator bp
= dbg
->breakpointsBegin(); bp
!= dbg
->breakpointsEnd(); ++bp
)
270 if (fileNameMatches(bp
->fileName
)) {
271 TRACE(QString("updating %2:%1").arg(bp
->lineNo
).arg(bp
->fileName
));
273 if (i
< 0 || i
>= int(m_sourceCode
.size()))
275 // compute new line item flags for breakpoint
276 uchar flags
= bp
->enabled
? liBP
: liBPdisabled
;
278 flags
|= liBPtemporary
;
279 if (!bp
->condition
.isEmpty() || bp
->ignoreCount
!= 0)
280 flags
|= liBPconditional
;
281 if (bp
->isOrphaned())
284 int row
= lineToRow(i
, bp
->address
);
285 if ((m_lineItems
[row
] & liBPany
) != flags
) {
286 m_lineItems
[row
] &= ~liBPany
;
287 m_lineItems
[row
] |= flags
;
291 m_lineInfoArea
->update();
294 void SourceWindow::setPC(bool set
, int lineNo
, const DbgAddr
& address
, int frameNo
)
296 if (lineNo
< 0 || lineNo
>= int(m_sourceCode
.size())) {
300 int row
= lineToRow(lineNo
, address
);
302 uchar flag
= frameNo
== 0 ? liPC
: liPCup
;
304 // set only if not already set
305 if ((m_lineItems
[row
] & flag
) == 0) {
306 m_lineItems
[row
] |= flag
;
307 m_lineInfoArea
->update();
310 // clear only if not set
311 if ((m_lineItems
[row
] & flag
) != 0) {
312 m_lineItems
[row
] &= ~flag
;
313 m_lineInfoArea
->update();
318 void SourceWindow::find(const QString
& text
, bool caseSensitive
, FindDirection dir
)
320 ASSERT(dir
== 1 || dir
== -1);
321 QTextDocument::FindFlags flags
= 0;
323 flags
|= QTextDocument::FindCaseSensitively
;
325 flags
|= QTextDocument::FindBackward
;
326 if (QPlainTextEdit::find(text
, flags
))
328 // not found; wrap around
329 QTextCursor
cursor(document());
331 cursor
.movePosition(QTextCursor::End
);
332 cursor
= document()->find(text
, cursor
, flags
);
333 if (!cursor
.isNull())
334 setTextCursor(cursor
);
337 void SourceWindow::infoMousePress(QMouseEvent
* ev
)
339 // we handle left and middle button
340 if (ev
->button() != Qt::LeftButton
&& ev
->button() != Qt::MidButton
)
346 int row
= cursorForPosition(QPoint(0, ev
->y())).blockNumber();
350 if (ev
->x() > m_widthItems
)
352 if (isRowExpanded(row
)) {
353 actionCollapseRow(row
);
355 actionExpandRow(row
);
361 int line
= rowToLine(row
, &sourceRow
);
363 // find address if row is disassembled code
365 if (row
> sourceRow
) {
366 // get offset from source code line
367 int off
= row
- sourceRow
;
368 address
= m_sourceCode
[line
].disassAddr
[off
-1];
371 switch (ev
->button()) {
373 TRACE(QString().sprintf("left-clicked line %d", line
));
374 emit
clickedLeft(m_fileName
, line
, address
,
375 (ev
->modifiers() & Qt::ShiftModifier
) != 0);
378 TRACE(QString().sprintf("mid-clicked row %d", line
));
379 emit
clickedMid(m_fileName
, line
, address
);
385 void SourceWindow::keyPressEvent(QKeyEvent
* ev
)
390 actionExpandRow(textCursor().blockNumber());
393 actionCollapseRow(textCursor().blockNumber());
397 moveCursor(QTextCursor::PreviousBlock
);
401 moveCursor(QTextCursor::NextBlock
);
404 moveCursor(QTextCursor::Start
);
407 moveCursor(QTextCursor::End
);
410 case Qt::Key_PageDown
:
411 top1
= firstVisibleBlock().blockNumber();
414 QPlainTextEdit::keyPressEvent(ev
);
418 case Qt::Key_PageDown
:
419 top2
= firstVisibleBlock().blockNumber();
421 QTextCursor cursor
= textCursor();
423 cursor
.movePosition(QTextCursor::NextBlock
,
424 QTextCursor::MoveAnchor
, top2
-top1
);
426 cursor
.movePosition(QTextCursor::PreviousBlock
,
427 QTextCursor::MoveAnchor
, top1
-top2
);
428 setTextCursor(cursor
);
433 bool SourceWindow::wordAtPoint(const QPoint
& p
, QString
& word
, QRect
& r
)
435 QTextCursor cursor
= cursorForPosition(viewport()->mapFrom(this, p
));
439 cursor
.select(QTextCursor::WordUnderCursor
);
440 word
= cursor
.selectedText();
445 // keep only letters and digits
446 QRegExp
w("[\\dA-Za-z_]+");
447 if (w
.indexIn(word
) < 0)
456 void SourceWindow::paletteChange(const QPalette
& oldPal
)
458 setFont(KGlobalSettings::fixedFont());
459 QPlainTextEdit::paletteChange(oldPal
);
463 * Two file names (possibly full paths) match if the last parts - the file
466 bool SourceWindow::fileNameMatches(const QString
& other
)
468 return QFileInfo(other
).fileName() == QFileInfo(m_fileName
).fileName();
471 void SourceWindow::disassembled(int lineNo
, const std::list
<DisassembledCode
>& disass
)
473 TRACE("disassembled line " + QString().setNum(lineNo
));
474 if (lineNo
< 0 || lineNo
>= int(m_sourceCode
.size()))
477 SourceLine
& sl
= m_sourceCode
[lineNo
];
479 // copy disassembled code and its addresses
480 sl
.disass
.resize(disass
.size());
481 sl
.disassAddr
.resize(disass
.size());
482 sl
.canDisass
= !disass
.empty();
484 for (std::list
<DisassembledCode
>::const_iterator c
= disass
.begin(); c
!= disass
.end(); ++c
, ++i
)
486 QString code
= c
->code
;
487 while (code
.endsWith("\n"))
488 code
.truncate(code
.length()-1);
489 sl
.disass
[i
] = c
->address
.asString() + ' ' + code
;
490 sl
.disassAddr
[i
] = c
->address
;
493 int row
= lineToRow(lineNo
);
497 // clear expansion marker
498 m_lineInfoArea
->update();
502 int SourceWindow::rowToLine(int row
, int* sourceRow
)
504 int line
= row
>= 0 ? m_rowToLine
[row
] : -1;
505 if (sourceRow
!= 0) {
506 // search back until we hit the first entry with the current line number
507 while (row
> 0 && m_rowToLine
[row
-1] == line
)
515 * Rows showing diassembled code have the same line number as the
516 * corresponding source code line number. Therefore, the line numbers in
517 * m_rowToLine are monotonically increasing with blocks of equal line
518 * numbers for a source line and its disassembled code that follows it.
520 * Hence, m_rowToLine always obeys the following condition:
522 * m_rowToLine[i] <= i
525 int SourceWindow::lineToRow(int line
)
527 // line is zero-based!
529 assert(line
< int(m_rowToLine
.size()));
531 // quick test for common case
532 if (line
< 0 || m_rowToLine
[line
] == line
)
535 assert(m_rowToLine
[line
] < line
);
538 * Binary search between row == line and end of list. In the loop below
539 * we use the fact that the line numbers m_rowToLine do not contain
543 int h
= m_rowToLine
.size();
544 while (l
< h
&& m_rowToLine
[l
] != line
)
546 assert(h
== int(m_rowToLine
.size()) || m_rowToLine
[l
] < m_rowToLine
[h
]);
549 * We want to round down the midpoint so that we find the
550 * lowest row that belongs to the line we seek.
553 if (m_rowToLine
[mid
] <= line
)
558 // Found! Result is in l:
559 assert(m_rowToLine
[l
] == line
);
562 * We might not have hit the lowest index for the line.
564 while (l
> 0 && m_rowToLine
[l
-1] == line
)
570 int SourceWindow::lineToRow(int line
, const DbgAddr
& address
)
572 int row
= lineToRow(line
);
573 if (isRowExpanded(row
)) {
574 row
+= m_sourceCode
[line
].findAddressRowOffset(address
);
579 bool SourceWindow::isRowExpanded(int row
)
582 return row
< int(m_rowToLine
.size())-1 &&
583 m_rowToLine
[row
] == m_rowToLine
[row
+1];
586 bool SourceWindow::isRowDisassCode(int row
)
588 return row
> 0 && row
< int(m_rowToLine
.size()) &&
589 m_rowToLine
[row
] == m_rowToLine
[row
-1];
592 void SourceWindow::expandRow(int row
)
594 TRACE("expanding row " + QString().setNum(row
));
595 // get disassembled code
596 int line
= rowToLine(row
);
597 const std::vector
<QString
>& disass
= m_sourceCode
[line
].disass
;
599 // remove PC (must be set again in slot of signal expanded())
600 m_lineItems
[row
] &= ~(liPC
|liPCup
);
603 setUpdatesEnabled(false);
605 m_rowToLine
.insert(m_rowToLine
.begin()+row
, disass
.size(), line
);
606 m_lineItems
.insert(m_lineItems
.begin()+row
, disass
.size(), 0);
608 QTextCursor
cursor(document()->findBlockByNumber(row
));
609 for (size_t i
= 0; i
< disass
.size(); i
++) {
610 cursor
.insertText(disass
[i
]);
611 cursor
.insertBlock();
613 setUpdatesEnabled(true);
615 emit
expanded(line
); /* must set PC */
618 void SourceWindow::collapseRow(int row
)
620 TRACE("collapsing row " + QString().setNum(row
));
621 int line
= rowToLine(row
);
623 // find end of this block
625 while (end
< int(m_rowToLine
.size()) && m_rowToLine
[end
] == m_rowToLine
[row
]) {
629 setUpdatesEnabled(false);
630 QTextCursor
cursor(document()->findBlockByNumber(end
-1));
631 while (--end
>= row
) {
632 m_rowToLine
.erase(m_rowToLine
.begin()+end
);
633 m_lineItems
.erase(m_lineItems
.begin()+end
);
634 cursor
.select(QTextCursor::BlockUnderCursor
);
635 cursor
.removeSelectedText();
637 setUpdatesEnabled(true);
639 emit
collapsed(line
);
642 void SourceWindow::activeLine(int& line
, DbgAddr
& address
)
644 int row
= textCursor().blockNumber();
647 line
= rowToLine(row
, &sourceRow
);
648 if (row
> sourceRow
) {
649 int off
= row
- sourceRow
; /* offset from source line */
650 address
= m_sourceCode
[line
].disassAddr
[off
-1];
655 * Returns the offset from the line displaying the source code to
656 * the line containing the specified address. If the address is not
657 * found, 0 is returned.
659 int SourceWindow::SourceLine::findAddressRowOffset(const DbgAddr
& address
) const
661 if (address
.isEmpty())
664 for (size_t i
= 0; i
< disassAddr
.size(); i
++) {
665 if (disassAddr
[i
] == address
) {
666 // found exact address
669 if (disassAddr
[i
] > address
) {
671 * We have already advanced too far; the address is before this
672 * index, but obviously we haven't found an exact match
673 * earlier. address is somewhere between the displayed
674 * addresses. We return the previous line.
683 void SourceWindow::actionExpandRow(int row
)
685 if (row
< 0 || isRowExpanded(row
) || isRowDisassCode(row
))
689 int line
= rowToLine(row
);
690 const SourceLine
& sl
= m_sourceCode
[line
];
693 if (sl
.disass
.size() == 0) {
694 emit
disassemble(m_fileName
, line
);
700 void SourceWindow::actionCollapseRow(int row
)
702 if (row
< 0 || !isRowExpanded(row
) || isRowDisassCode(row
))
708 void SourceWindow::setTabWidth(int numChars
)
712 QFontMetrics
fm(document()->defaultFont());
714 int w
= fm
.width(s
.fill('x', numChars
));
718 void SourceWindow::cursorChanged()
720 QList
<QTextEdit::ExtraSelection
> extraSelections
;
721 QTextEdit::ExtraSelection selection
;
723 selection
.format
.setBackground(QColor("#E7E7E7"));
724 selection
.format
.setProperty(QTextFormat::FullWidthSelection
, true);
725 selection
.cursor
= textCursor();
726 selection
.cursor
.clearSelection();
727 extraSelections
.append(selection
);
728 setExtraSelections(extraSelections
);
732 * Show our own context menu.
734 void SourceWindow::contextMenuEvent(QContextMenuEvent
* e
)
736 // get the context menu from the GUI factory
739 top
= top
->parentWidget();
740 while (!top
->isTopLevel());
741 KXmlGuiWindow
* mw
= static_cast<KXmlGuiWindow
*>(top
);
742 QMenu
* m
= static_cast<QMenu
*>(mw
->factory()->container("popup_files", mw
));
744 m
->exec(e
->globalPos());
747 void LineInfoArea::paintEvent(QPaintEvent
* e
)
750 static_cast<SourceWindow
*>(parent())->drawLineInfoArea(&p
, e
);
753 void LineInfoArea::mousePressEvent(QMouseEvent
* ev
)
755 static_cast<SourceWindow
*>(parent())->infoMousePress(ev
);
758 void LineInfoArea::contextMenuEvent(QContextMenuEvent
* e
)
760 static_cast<SourceWindow
*>(parent())->contextMenuEvent(e
);
763 HighlightCpp::HighlightCpp(SourceWindow
* srcWnd
) :
764 QSyntaxHighlighter(srcWnd
->document()),
776 static const QString ckw
[] =
854 void HighlightCpp::highlightBlock(const QString
& text
)
856 int state
= previousBlockState();
857 state
= highlight(text
, state
);
858 setCurrentBlockState(state
);
861 int HighlightCpp::highlight(const QString
& text
, int state
)
863 // highlight assembly lines
864 if (m_srcWnd
->isRowDisassCode(currentBlock().blockNumber()))
866 setFormat(0, text
.length(), Qt::blue
);
870 if (state
== -2) // initial state
873 // check for preprocessor line
874 if (state
== 0 && text
.trimmed().startsWith("#"))
876 setFormat(0, text
.length(), QColor("darkgreen"));
880 // a font for keywords
881 QTextCharFormat identFont
;
882 identFont
.setFontWeight(QFont::Bold
);
885 while (start
< text
.length())
892 setFormat(start
, end
-start
, QColor("gray"));
895 end
= text
.indexOf("*/", start
);
900 setFormat(start
, end
-start
, QColor("gray"));
903 for (end
= start
+1; end
< int(text
.length()); end
++) {
904 if (text
[end
] == '\\') {
905 if (end
< int(text
.length()))
907 } else if (text
[end
] == text
[start
]) {
913 setFormat(start
, end
-start
, QColor("darkred"));
916 for (end
= start
+1; end
< int(text
.length()); end
++) {
917 if (!text
[end
].isLetterOrNumber() && text
[end
] != '_')
921 if (std::binary_search(ckw
, ckw
+ sizeof(ckw
)/sizeof(ckw
[0]),
922 text
.mid(start
, end
-start
)))
924 setFormat(start
, end
-start
, identFont
);
926 setFormat(start
, end
-start
, m_srcWnd
->palette().color(QPalette::WindowText
));
930 for (end
= start
; end
< int(text
.length()); end
++)
932 if (text
[end
] == '/')
934 if (end
+1 < int(text
.length())) {
935 if (text
[end
+1] == '/') {
936 state
= hlCommentLine
;
938 } else if (text
[end
+1] == '*') {
939 state
= hlCommentBlock
;
944 else if (text
[end
] == '"' || text
[end
] == '\'')
949 else if ((text
[end
] >= 'A' && text
[end
] <= 'Z') ||
950 (text
[end
] >= 'a' && text
[end
] <= 'z') ||
957 setFormat(start
, end
-start
, m_srcWnd
->palette().color(QPalette::WindowText
));
964 #include "sourcewnd.moc"