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.
9 #include <q3textstream.h>
13 #include <qfileinfo.h>
14 #include <qnamespace.h>
15 #include <q3popupmenu.h>
16 #include <QContextMenuEvent>
18 #include <QMouseEvent>
20 #include <kapplication.h>
21 #include <kiconloader.h>
22 #include <kglobalsettings.h>
23 #include <kmainwindow.h>
28 SourceWindow::SourceWindow(const QString
& fileName
, QWidget
* parent
) :
37 m_pcinner
= UserIcon("pcinner");
38 m_pcup
= UserIcon("pcup");
39 m_brkena
= UserIcon("brkena");
40 m_brkdis
= UserIcon("brkdis");
41 m_brktmp
= UserIcon("brktmp");
42 m_brkcond
= UserIcon("brkcond");
43 m_brkorph
= UserIcon("brkorph");
44 setFont(KGlobalSettings::fixedFont());
46 setMargins(m_widthItems
+m_widthPlus
+m_widthLineNo
, 0, 0 ,0);
47 setAutoFormatting(AutoNone
);
48 setTextFormat(Qt::PlainText
);
50 connect(verticalScrollBar(), SIGNAL(valueChanged(int)),
51 this, SLOT(update()));
52 connect(this, SIGNAL(cursorPositionChanged(int,int)), this, SLOT(cursorChanged(int)));
53 viewport()->installEventFilter(this);
55 // add a syntax highlighter
56 if (QRegExp("\\.(c(pp|c|\\+\\+)?|CC?|h(\\+\\+|h)?|HH?)$").search(m_fileName
) >= 0)
58 new HighlightCpp(this);
62 SourceWindow::~SourceWindow()
64 delete syntaxHighlighter();
67 bool SourceWindow::loadFile()
69 // first we load the code into QTextEdit
71 if (!f
.open(QIODevice::ReadOnly
)) {
79 // then we copy it into our own m_sourceCode
81 m_sourceCode
.resize(n
);
82 m_rowToLine
.resize(n
);
83 for (int i
= 0; i
< n
; i
++) {
84 m_sourceCode
[i
].code
= text(i
);
87 m_lineItems
.resize(n
, 0);
89 // set a font for line numbers
90 m_lineNoFont
= currentFont();
91 m_lineNoFont
.setPixelSize(11);
96 void SourceWindow::reloadFile()
99 if (!f
.open(QIODevice::ReadOnly
)) {
100 // open failed; leave alone
104 // read text into m_sourceCode
105 m_sourceCode
.clear(); /* clear old text */
111 m_sourceCode
.resize(paragraphs());
112 for (size_t i
= 0; i
< m_sourceCode
.size(); i
++) {
113 m_sourceCode
[i
].code
= text(i
);
115 // expanded lines are collapsed: move existing line items up
116 for (size_t i
= 0; i
< m_lineItems
.size(); i
++) {
117 if (m_rowToLine
[i
] != i
) {
118 m_lineItems
[m_rowToLine
[i
]] |= m_lineItems
[i
];
122 // allocate line items
123 m_lineItems
.resize(m_sourceCode
.size(), 0);
125 m_rowToLine
.resize(m_sourceCode
.size());
126 for (size_t i
= 0; i
< m_sourceCode
.size(); i
++)
129 // Highlighting was applied above when the text was inserted into widget,
130 // but at that time m_rowToLine was not corrected, yet, so that lines
131 // that previously were assembly were painted incorrectly.
132 if (syntaxHighlighter())
133 syntaxHighlighter()->rehighlight();
134 update(); // line numbers
137 void SourceWindow::scrollTo(int lineNo
, const DbgAddr
& address
)
139 if (lineNo
< 0 || lineNo
>= int(m_sourceCode
.size()))
142 int row
= lineToRow(lineNo
, address
);
146 void SourceWindow::scrollToRow(int row
)
148 setCursorPosition(row
, 0);
149 ensureCursorVisible();
152 void SourceWindow::drawFrame(QPainter
* p
)
154 Q3TextEdit::drawFrame(p
);
156 // and paragraph at the top is...
157 int top
= paragraphAt(QPoint(0,contentsY()));
158 int bot
= paragraphAt(QPoint(0,contentsY()+visibleHeight()-1));
160 bot
= paragraphs()-1;
164 // set a clip rectangle
165 int fw
= frameWidth();
166 QRect inside
= rect();
167 inside
.addCoords(fw
,fw
,-fw
,-fw
);
168 QRegion clip
= p
->clipRegion();
169 clip
&= QRegion(inside
);
170 p
->setClipRegion(clip
);
172 p
->setFont(m_lineNoFont
);
173 p
->setPen(colorGroup().text());
174 p
->eraseRect(inside
);
176 for (int row
= top
; row
<= bot
; row
++)
178 uchar item
= m_lineItems
[row
];
181 QRect r
= paragraphRect(row
);
182 QPoint pt
= contentsToViewport(r
.topLeft());
184 p
->translate(fw
, pt
.y()+viewport()->y());
187 // enabled breakpoint
188 int y
= (h
- m_brkena
.height())/2;
190 p
->drawPixmap(0,y
,m_brkena
);
192 if (item
& liBPdisabled
) {
193 // disabled breakpoint
194 int y
= (h
- m_brkdis
.height())/2;
196 p
->drawPixmap(0,y
,m_brkdis
);
198 if (item
& liBPtemporary
) {
199 // temporary breakpoint marker
200 int y
= (h
- m_brktmp
.height())/2;
202 p
->drawPixmap(0,y
,m_brktmp
);
204 if (item
& liBPconditional
) {
205 // conditional breakpoint marker
206 int y
= (h
- m_brkcond
.height())/2;
208 p
->drawPixmap(0,y
,m_brkcond
);
210 if (item
& liBPorphan
) {
211 // orphaned breakpoint marker
212 int y
= (h
- m_brkcond
.height())/2;
214 p
->drawPixmap(0,y
,m_brkorph
);
217 // program counter in innermost frame
218 int y
= (h
- m_pcinner
.height())/2;
220 p
->drawPixmap(0,y
,m_pcinner
);
223 // program counter somewhere up the stack
224 int y
= (h
- m_pcup
.height())/2;
226 p
->drawPixmap(0,y
,m_pcup
);
228 p
->translate(m_widthItems
, 0);
229 if (!isRowDisassCode(row
) && m_sourceCode
[rowToLine(row
)].canDisass
) {
233 p
->drawLine(x
-2, y
, x
+2, y
);
234 if (!isRowExpanded(row
)) {
235 p
->drawLine(x
, y
-2, x
, y
+2);
238 p
->translate(m_widthPlus
, 0);
239 if (!isRowDisassCode(row
)) {
240 p
->drawText(0, 0, m_widthLineNo
, h
, Qt::AlignRight
|Qt::AlignVCenter
,
241 QString().setNum(rowToLine(row
)+1));
248 void SourceWindow::updateLineItems(const KDebugger
* dbg
)
250 // clear outdated breakpoints
251 for (int i
= m_lineItems
.size()-1; i
>= 0; i
--) {
252 if (m_lineItems
[i
] & liBPany
) {
253 // check if this breakpoint still exists
254 int line
= rowToLine(i
);
255 TRACE(QString().sprintf("checking for bp at %d", line
));
256 KDebugger::BrkptROIterator bp
= dbg
->breakpointsBegin();
257 for (; bp
!= dbg
->breakpointsEnd(); ++bp
)
259 if (bp
->lineNo
== line
&&
260 fileNameMatches(bp
->fileName
) &&
261 lineToRow(line
, bp
->address
) == i
)
263 // yes it exists; mode is changed below
267 if (bp
== dbg
->breakpointsEnd()) {
268 /* doesn't exist anymore, remove it */
269 m_lineItems
[i
] &= ~liBPany
;
275 // add new breakpoints
276 for (KDebugger::BrkptROIterator bp
= dbg
->breakpointsBegin(); bp
!= dbg
->breakpointsEnd(); ++bp
)
278 if (fileNameMatches(bp
->fileName
)) {
279 TRACE(QString().sprintf("updating %s:%d", bp
->fileName
.data(), bp
->lineNo
));
281 if (i
< 0 || i
>= int(m_sourceCode
.size()))
283 // compute new line item flags for breakpoint
284 uchar flags
= bp
->enabled
? liBP
: liBPdisabled
;
286 flags
|= liBPtemporary
;
287 if (!bp
->condition
.isEmpty() || bp
->ignoreCount
!= 0)
288 flags
|= liBPconditional
;
289 if (bp
->isOrphaned())
292 int row
= lineToRow(i
, bp
->address
);
293 if ((m_lineItems
[row
] & liBPany
) != flags
) {
294 m_lineItems
[row
] &= ~liBPany
;
295 m_lineItems
[row
] |= flags
;
302 void SourceWindow::setPC(bool set
, int lineNo
, const DbgAddr
& address
, int frameNo
)
304 if (lineNo
< 0 || lineNo
>= int(m_sourceCode
.size())) {
308 int row
= lineToRow(lineNo
, address
);
310 uchar flag
= frameNo
== 0 ? liPC
: liPCup
;
312 // set only if not already set
313 if ((m_lineItems
[row
] & flag
) == 0) {
314 m_lineItems
[row
] |= flag
;
318 // clear only if not set
319 if ((m_lineItems
[row
] & flag
) != 0) {
320 m_lineItems
[row
] &= ~flag
;
326 void SourceWindow::find(const QString
& text
, bool caseSensitive
, FindDirection dir
)
328 ASSERT(dir
== 1 || dir
== -1);
329 if (Q3TextEdit::find(text
, caseSensitive
, false, dir
> 0))
331 // not found; wrap around
332 int para
= dir
> 0 ? 0 : paragraphs(), index
= 0;
333 Q3TextEdit::find(text
, caseSensitive
, false, dir
> 0, ¶
, &index
);
336 void SourceWindow::mousePressEvent(QMouseEvent
* ev
)
338 // we handle left and middle button
339 if (ev
->button() != Qt::LeftButton
&& ev
->button() != Qt::MidButton
)
341 Q3TextEdit::mousePressEvent(ev
);
346 QPoint p
= viewportToContents(QPoint(0, ev
->y() - viewport()->y()));
347 int row
= paragraphAt(p
);
351 if (ev
->x() > m_widthItems
+frameWidth())
353 if (isRowExpanded(row
)) {
354 actionCollapseRow(row
);
356 actionExpandRow(row
);
362 int line
= rowToLine(row
, &sourceRow
);
364 // find address if row is disassembled code
366 if (row
> sourceRow
) {
367 // get offset from source code line
368 int off
= row
- sourceRow
;
369 address
= m_sourceCode
[line
].disassAddr
[off
-1];
372 switch (ev
->button()) {
374 TRACE(QString().sprintf("left-clicked line %d", line
));
375 emit
clickedLeft(m_fileName
, line
, address
,
376 (ev
->state() & Qt::ShiftButton
) != 0);
379 TRACE(QString().sprintf("mid-clicked row %d", line
));
380 emit
clickedMid(m_fileName
, line
, address
);
386 void SourceWindow::keyPressEvent(QKeyEvent
* ev
)
392 actionExpandRow(m_curRow
);
395 actionCollapseRow(m_curRow
);
399 setCursorPosition(m_curRow
-1, 0);
403 if (m_curRow
< paragraphs()-1) {
404 setCursorPosition(m_curRow
+1, 0);
408 setCursorPosition(0, 0);
411 setCursorPosition(paragraphs()-1, 0);
415 top
= viewportToContents(QPoint(0,0));
416 top1
= paragraphAt(top
);
419 Q3TextEdit::keyPressEvent(ev
);
424 top
= viewportToContents(QPoint(0,0));
425 top2
= paragraphAt(top
);
426 setCursorPosition(m_curRow
+(top2
-top1
), 0);
430 static inline bool isident(QChar c
)
432 return c
.isLetterOrNumber() || c
.latin1() == '_';
435 bool SourceWindow::wordAtPoint(const QPoint
& p
, QString
& word
, QRect
& r
)
437 QPoint pv
= viewportToContents(p
- viewport()->pos());
438 int row
, col
= charAt(pv
, &row
);
439 if (row
< 0 || col
< 0)
442 // isolate the word at row, col
443 QString line
= text(row
);
444 if (!isident(line
[col
]))
448 while (begin
> 0 && isident(line
[begin
-1]))
452 while (col
< int(line
.length()) && isident(line
[col
]));
455 r
.addCoords(-5,-5,5,5);
456 word
= line
.mid(begin
, col
-begin
);
460 void SourceWindow::paletteChange(const QPalette
& oldPal
)
462 setFont(KGlobalSettings::fixedFont());
463 Q3TextEdit::paletteChange(oldPal
);
467 * Two file names (possibly full paths) match if the last parts - the file
470 bool SourceWindow::fileNameMatches(const QString
& other
)
472 return QFileInfo(other
).fileName() == QFileInfo(m_fileName
).fileName();
475 void SourceWindow::disassembled(int lineNo
, const std::list
<DisassembledCode
>& disass
)
477 TRACE("disassembled line " + QString().setNum(lineNo
));
478 if (lineNo
< 0 || lineNo
>= int(m_sourceCode
.size()))
481 SourceLine
& sl
= m_sourceCode
[lineNo
];
483 // copy disassembled code and its addresses
484 sl
.disass
.resize(disass
.size());
485 sl
.disassAddr
.resize(disass
.size());
486 sl
.canDisass
= !disass
.empty();
488 for (std::list
<DisassembledCode
>::const_iterator c
= disass
.begin(); c
!= disass
.end(); ++c
, ++i
)
490 QString code
= c
->code
;
491 while (code
.endsWith("\n"))
492 code
.truncate(code
.length()-1);
493 sl
.disass
[i
] = c
->address
.asString() + ' ' + code
;
494 sl
.disassAddr
[i
] = c
->address
;
497 int row
= lineToRow(lineNo
);
501 // clear expansion marker
506 int SourceWindow::rowToLine(int row
, int* sourceRow
)
508 int line
= row
>= 0 ? m_rowToLine
[row
] : -1;
509 if (sourceRow
!= 0) {
510 // search back until we hit the first entry with the current line number
511 while (row
> 0 && m_rowToLine
[row
-1] == line
)
519 * Rows showing diassembled code have the same line number as the
520 * corresponding source code line number. Therefore, the line numbers in
521 * m_rowToLine are monotonically increasing with blocks of equal line
522 * numbers for a source line and its disassembled code that follows it.
524 * Hence, m_rowToLine always obeys the following condition:
526 * m_rowToLine[i] <= i
529 int SourceWindow::lineToRow(int line
)
531 // line is zero-based!
533 assert(line
< int(m_rowToLine
.size()));
535 // quick test for common case
536 if (line
< 0 || m_rowToLine
[line
] == line
)
539 assert(m_rowToLine
[line
] < line
);
542 * Binary search between row == line and end of list. In the loop below
543 * we use the fact that the line numbers m_rowToLine do not contain
547 int h
= m_rowToLine
.size();
548 while (l
< h
&& m_rowToLine
[l
] != line
)
550 assert(h
== int(m_rowToLine
.size()) || m_rowToLine
[l
] < m_rowToLine
[h
]);
553 * We want to round down the midpoint so that we find the
554 * lowest row that belongs to the line we seek.
557 if (m_rowToLine
[mid
] <= line
)
562 // Found! Result is in l:
563 assert(m_rowToLine
[l
] == line
);
566 * We might not have hit the lowest index for the line.
568 while (l
> 0 && m_rowToLine
[l
-1] == line
)
574 int SourceWindow::lineToRow(int line
, const DbgAddr
& address
)
576 int row
= lineToRow(line
);
577 if (isRowExpanded(row
)) {
578 row
+= m_sourceCode
[line
].findAddressRowOffset(address
);
583 bool SourceWindow::isRowExpanded(int row
)
586 return row
< int(m_rowToLine
.size())-1 &&
587 m_rowToLine
[row
] == m_rowToLine
[row
+1];
590 bool SourceWindow::isRowDisassCode(int row
)
592 return row
> 0 && row
< int(m_rowToLine
.size()) &&
593 m_rowToLine
[row
] == m_rowToLine
[row
-1];
596 void SourceWindow::expandRow(int row
)
598 TRACE("expanding row " + QString().setNum(row
));
599 // get disassembled code
600 int line
= rowToLine(row
);
601 const std::vector
<QString
>& disass
= m_sourceCode
[line
].disass
;
603 // remove PC (must be set again in slot of signal expanded())
604 m_lineItems
[row
] &= ~(liPC
|liPCup
);
606 // adjust current row
607 if (m_curRow
> row
) {
608 m_curRow
+= disass
.size();
609 // highlight is moved automatically
613 setUpdatesEnabled(false);
615 for (size_t i
= 0; i
< disass
.size(); i
++) {
616 m_rowToLine
.insert(m_rowToLine
.begin()+row
, line
);
617 m_lineItems
.insert(m_lineItems
.begin()+row
, 0);
618 insertParagraph(disass
[i
], row
++);
620 setUpdatesEnabled(true);
621 viewport()->update();
622 update(); // line items
624 emit
expanded(line
); /* must set PC */
627 void SourceWindow::collapseRow(int row
)
629 TRACE("collapsing row " + QString().setNum(row
));
630 int line
= rowToLine(row
);
632 // find end of this block
634 while (end
< int(m_rowToLine
.size()) && m_rowToLine
[end
] == m_rowToLine
[row
]) {
638 // adjust current row
639 if (m_curRow
>= row
) {
641 if (m_curRow
< row
) // was m_curRow in disassembled code?
644 setUpdatesEnabled(false);
645 while (--end
>= row
) {
646 m_rowToLine
.erase(m_rowToLine
.begin()+end
);
647 m_lineItems
.erase(m_lineItems
.begin()+end
);
648 removeParagraph(end
);
650 setUpdatesEnabled(true);
651 viewport()->update();
652 update(); // line items
654 emit
collapsed(line
);
657 void SourceWindow::activeLine(int& line
, DbgAddr
& address
)
662 line
= rowToLine(row
, &sourceRow
);
663 if (row
> sourceRow
) {
664 int off
= row
- sourceRow
; /* offset from source line */
665 address
= m_sourceCode
[line
].disassAddr
[off
-1];
670 * Returns the offset from the line displaying the source code to
671 * the line containing the specified address. If the address is not
672 * found, 0 is returned.
674 int SourceWindow::SourceLine::findAddressRowOffset(const DbgAddr
& address
) const
676 if (address
.isEmpty())
679 for (size_t i
= 0; i
< disassAddr
.size(); i
++) {
680 if (disassAddr
[i
] == address
) {
681 // found exact address
684 if (disassAddr
[i
] > address
) {
686 * We have already advanced too far; the address is before this
687 * index, but obviously we haven't found an exact match
688 * earlier. address is somewhere between the displayed
689 * addresses. We return the previous line.
698 void SourceWindow::actionExpandRow(int row
)
700 if (row
< 0 || isRowExpanded(row
) || isRowDisassCode(row
))
704 int line
= rowToLine(row
);
705 const SourceLine
& sl
= m_sourceCode
[line
];
708 if (sl
.disass
.size() == 0) {
709 emit
disassemble(m_fileName
, line
);
715 void SourceWindow::actionCollapseRow(int row
)
717 if (row
< 0 || !isRowExpanded(row
) || isRowDisassCode(row
))
723 void SourceWindow::setTabWidth(int numChars
)
727 QFontMetrics
fm(currentFont());
729 int w
= fm
.width(s
.fill('x', numChars
));
733 void SourceWindow::cursorChanged(int row
)
738 if (m_curRow
>= 0 && m_curRow
< paragraphs())
739 clearParagraphBackground(m_curRow
);
741 setParagraphBackgroundColor(row
, colorGroup().background());
745 * We must override the context menu handling because QTextEdit's handling
746 * requires that it receives ownership of the popup menu; but the popup menu
747 * returned from the GUI factory is owned by the factory.
750 void SourceWindow::contextMenuEvent(QContextMenuEvent
* e
)
752 // get the context menu from the GUI factory
755 top
= top
->parentWidget();
756 while (!top
->isTopLevel());
757 KMainWindow
* mw
= static_cast<KMainWindow
*>(top
);
759 static_cast<Q3PopupMenu
*>(mw
->factory()->container("popup_files", mw
));
760 m
->exec(e
->globalPos());
763 bool SourceWindow::eventFilter(QObject
* watched
, QEvent
* e
)
765 if (e
->type() == QEvent::ContextMenu
&& watched
== viewport())
767 contextMenuEvent(static_cast<QContextMenuEvent
*>(e
));
770 return Q3TextEdit::eventFilter(watched
, e
);
773 HighlightCpp::HighlightCpp(SourceWindow
* srcWnd
) :
774 Q3SyntaxHighlighter(srcWnd
),
786 static const QString ckw
[] =
864 int HighlightCpp::highlightParagraph(const QString
& text
, int state
)
866 int row
= currentParagraph();
867 // highlight assembly lines
868 if (m_srcWnd
->isRowDisassCode(row
))
870 setFormat(0, text
.length(), Qt::blue
);
874 if (state
== -2) // initial state
877 // check for preprocessor line
878 if (state
== 0 && text
.stripWhiteSpace().startsWith("#"))
880 setFormat(0, text
.length(), QColor("dark green"));
884 // a font for keywords
885 QFont identFont
= textEdit()->currentFont();
886 identFont
.setBold(!identFont
.bold());
889 while (start
< text
.length())
896 setFormat(start
, end
-start
, QColor("gray50"));
899 end
= text
.find("*/", start
);
904 setFormat(start
, end
-start
, QColor("gray50"));
907 for (end
= start
+1; end
< int(text
.length()); end
++) {
908 if (text
[end
] == '\\') {
909 if (end
< int(text
.length()))
911 } else if (text
[end
] == text
[start
]) {
917 setFormat(start
, end
-start
, QColor("dark red"));
920 for (end
= start
+1; end
< int(text
.length()); end
++) {
921 if (!text
[end
].isLetterOrNumber() && text
[end
] != '_')
925 if (std::binary_search(ckw
, ckw
+ sizeof(ckw
)/sizeof(ckw
[0]),
926 text
.mid(start
, end
-start
)))
928 setFormat(start
, end
-start
, identFont
);
930 setFormat(start
, end
-start
, m_srcWnd
->colorGroup().text());
934 for (end
= start
; end
< int(text
.length()); end
++)
936 if (text
[end
] == '/')
938 if (end
+1 < int(text
.length())) {
939 if (text
[end
+1] == '/') {
940 state
= hlCommentLine
;
942 } else if (text
[end
+1] == '*') {
943 state
= hlCommentBlock
;
948 else if (text
[end
] == '"' || text
[end
] == '\'')
953 else if (text
[end
] >= 'A' && text
[end
] <= 'Z' ||
954 text
[end
] >= 'a' && text
[end
] <= 'z' ||
961 setFormat(start
, end
-start
, m_srcWnd
->colorGroup().text());
968 #include "sourcewnd.moc"