3 // Copyright by Johannes Sixt
4 // This file is under GPL, the GNU General Public Licence
8 #include <qtextstream.h>
13 #include <qpopupmenu.h>
15 #include <kiconloader.h>
16 #include <kglobalsettings.h>
17 #include <kmainwindow.h>
25 SourceWindow::SourceWindow(const char* fileName
, QWidget
* parent
, const char* name
) :
26 QTextEdit(parent
, name
),
33 m_pcinner
= UserIcon("pcinner");
34 m_pcup
= UserIcon("pcup");
35 m_brkena
= UserIcon("brkena");
36 m_brkdis
= UserIcon("brkdis");
37 m_brktmp
= UserIcon("brktmp");
38 m_brkcond
= UserIcon("brkcond");
39 m_brkorph
= UserIcon("brkorph");
40 setFont(KGlobalSettings::fixedFont());
42 setMargins(m_widthItems
+m_widthPlus
, 0, 0 ,0);
43 setAutoFormatting(AutoNone
);
44 setTextFormat(PlainText
);
46 connect(verticalScrollBar(), SIGNAL(valueChanged(int)),
47 this, SLOT(update()));
48 connect(this, SIGNAL(cursorPositionChanged(int,int)), this, SLOT(cursorChanged(int)));
49 viewport()->installEventFilter(this);
51 // add a syntax highlighter
52 if (QRegExp("\\.(c(pp|c|\\+\\+)?|CC?|h(\\+\\+|h)?|HH?)$").search(m_fileName
))
54 new HighlightCpp(this);
58 SourceWindow::~SourceWindow()
60 delete syntaxHighlighter();
63 bool SourceWindow::loadFile()
65 // first we load the code into QTextEdit
67 if (!f
.open(IO_ReadOnly
)) {
75 // then we copy it into our own m_sourceCode
77 m_sourceCode
.resize(n
);
78 m_rowToLine
.resize(n
);
79 for (int i
= 0; i
< n
; i
++) {
80 m_sourceCode
[i
].code
= text(i
);
83 m_lineItems
.resize(n
, 0);
88 void SourceWindow::reloadFile()
91 if (!f
.open(IO_ReadOnly
)) {
92 // open failed; leave alone
96 // read text into m_sourceCode
97 m_sourceCode
.clear(); /* clear old text */
103 m_sourceCode
.resize(paragraphs());
104 for (size_t i
= 0; i
< m_sourceCode
.size(); i
++) {
105 m_sourceCode
[i
].code
= text(i
);
107 // allocate line items
108 m_lineItems
.resize(m_sourceCode
.size(), 0);
110 m_rowToLine
.resize(m_sourceCode
.size());
111 for (size_t i
= 0; i
< m_sourceCode
.size(); i
++)
115 void SourceWindow::scrollTo(int lineNo
, const DbgAddr
& address
)
117 if (lineNo
< 0 || lineNo
>= int(m_sourceCode
.size()))
120 int row
= lineToRow(lineNo
, address
);
124 void SourceWindow::scrollToRow(int row
)
126 setCursorPosition(row
, 0);
127 ensureCursorVisible();
130 void SourceWindow::drawFrame(QPainter
* p
)
132 QTextEdit::drawFrame(p
);
134 // and paragraph at the top is...
135 int top
= paragraphAt(QPoint(0,contentsY()));
136 int bot
= paragraphAt(QPoint(0,contentsY()+visibleHeight()-1));
138 bot
= paragraphs()-1;
142 // set a clip rectangle
143 int fw
= frameWidth();
144 QRect inside
= rect();
145 inside
.addCoords(fw
,fw
,-fw
,-fw
);
146 QRegion clip
= p
->clipRegion();
147 clip
&= QRegion(inside
);
148 p
->setClipRegion(clip
);
150 p
->setPen(colorGroup().text());
151 p
->eraseRect(inside
);
153 for (int row
= top
; row
<= bot
; row
++)
155 uchar item
= m_lineItems
[row
];
158 QRect r
= paragraphRect(row
);
159 QPoint pt
= contentsToViewport(r
.topLeft());
161 p
->translate(fw
, pt
.y()+viewport()->y());
164 // enabled breakpoint
165 int y
= (h
- m_brkena
.height())/2;
167 p
->drawPixmap(0,y
,m_brkena
);
169 if (item
& liBPdisabled
) {
170 // disabled breakpoint
171 int y
= (h
- m_brkdis
.height())/2;
173 p
->drawPixmap(0,y
,m_brkdis
);
175 if (item
& liBPtemporary
) {
176 // temporary breakpoint marker
177 int y
= (h
- m_brktmp
.height())/2;
179 p
->drawPixmap(0,y
,m_brktmp
);
181 if (item
& liBPconditional
) {
182 // conditional breakpoint marker
183 int y
= (h
- m_brkcond
.height())/2;
185 p
->drawPixmap(0,y
,m_brkcond
);
187 if (item
& liBPorphan
) {
188 // orphaned breakpoint marker
189 int y
= (h
- m_brkcond
.height())/2;
191 p
->drawPixmap(0,y
,m_brkorph
);
194 // program counter in innermost frame
195 int y
= (h
- m_pcinner
.height())/2;
197 p
->drawPixmap(0,y
,m_pcinner
);
200 // program counter somewhere up the stack
201 int y
= (h
- m_pcup
.height())/2;
203 p
->drawPixmap(0,y
,m_pcup
);
205 if (!isRowDisassCode(row
) && m_sourceCode
[rowToLine(row
)].canDisass
) {
207 p
->translate(m_widthItems
, 0);
210 p
->drawLine(x
-2, y
, x
+2, y
);
211 if (!isRowExpanded(row
)) {
212 p
->drawLine(x
, y
-2, x
, y
+2);
220 void SourceWindow::updateLineItems(const KDebugger
* dbg
)
222 // clear outdated breakpoints
223 for (int i
= m_lineItems
.size()-1; i
>= 0; i
--) {
224 if (m_lineItems
[i
] & liBPany
) {
225 // check if this breakpoint still exists
226 int line
= rowToLine(i
);
227 TRACE(QString().sprintf("checking for bp at %d", line
));
229 for (j
= dbg
->numBreakpoints()-1; j
>= 0; j
--) {
230 const Breakpoint
* bp
= dbg
->breakpoint(j
);
231 if (bp
->lineNo
== line
&&
232 fileNameMatches(bp
->fileName
) &&
233 lineToRow(line
, bp
->address
) == i
)
235 // yes it exists; mode is changed below
240 /* doesn't exist anymore, remove it */
241 m_lineItems
[i
] &= ~liBPany
;
247 // add new breakpoints
248 for (int j
= dbg
->numBreakpoints()-1; j
>= 0; j
--) {
249 const Breakpoint
* bp
= dbg
->breakpoint(j
);
250 if (fileNameMatches(bp
->fileName
)) {
251 TRACE(QString().sprintf("updating %s:%d", bp
->fileName
.data(), bp
->lineNo
));
253 if (i
< 0 || i
>= int(m_sourceCode
.size()))
255 // compute new line item flags for breakpoint
256 uchar flags
= bp
->enabled
? liBP
: liBPdisabled
;
258 flags
|= liBPtemporary
;
259 if (!bp
->condition
.isEmpty() || bp
->ignoreCount
!= 0)
260 flags
|= liBPconditional
;
261 if (bp
->isOrphaned())
264 int row
= lineToRow(i
, bp
->address
);
265 if ((m_lineItems
[row
] & liBPany
) != flags
) {
266 m_lineItems
[row
] &= ~liBPany
;
267 m_lineItems
[row
] |= flags
;
274 void SourceWindow::setPC(bool set
, int lineNo
, const DbgAddr
& address
, int frameNo
)
276 if (lineNo
< 0 || lineNo
>= int(m_sourceCode
.size())) {
280 int row
= lineToRow(lineNo
, address
);
282 uchar flag
= frameNo
== 0 ? liPC
: liPCup
;
284 // set only if not already set
285 if ((m_lineItems
[row
] & flag
) == 0) {
286 m_lineItems
[row
] |= flag
;
290 // clear only if not set
291 if ((m_lineItems
[row
] & flag
) != 0) {
292 m_lineItems
[row
] &= ~flag
;
298 void SourceWindow::find(const QString
& text
, bool caseSensitive
, FindDirection dir
)
300 ASSERT(dir
== 1 || dir
== -1);
301 if (m_sourceCode
.size() == 0 || text
.isEmpty())
306 activeLine(line
, dummyAddr
);
309 int curLine
= line
; /* remember where we started */
312 // advance and wrap around
315 line
= m_sourceCode
.size()-1;
316 else if (line
>= int(m_sourceCode
.size()))
319 found
= m_sourceCode
[line
].code
.find(text
, 0, caseSensitive
) >= 0;
320 } while (!found
&& line
!= curLine
);
322 scrollTo(line
, DbgAddr());
325 void SourceWindow::mousePressEvent(QMouseEvent
* ev
)
327 // we handle left and middle button
328 if (ev
->button() != LeftButton
&& ev
->button() != MidButton
)
330 QTextEdit::mousePressEvent(ev
);
335 QPoint p
= viewportToContents(QPoint(0, ev
->y() - viewport()->y()));
336 int row
= paragraphAt(p
);
340 if (ev
->x() > m_widthItems
+frameWidth())
342 if (isRowExpanded(row
)) {
343 actionCollapseRow(row
);
345 actionExpandRow(row
);
351 int line
= rowToLine(row
, &sourceRow
);
353 // find address if row is disassembled code
355 if (row
> sourceRow
) {
356 // get offset from source code line
357 int off
= row
- sourceRow
;
358 address
= m_sourceCode
[line
].disassAddr
[off
-1];
361 switch (ev
->button()) {
363 TRACE(QString().sprintf("left-clicked line %d", line
));
364 emit
clickedLeft(m_fileName
, line
, address
,
365 (ev
->state() & ShiftButton
) != 0);
368 TRACE(QString().sprintf("mid-clicked row %d", line
));
369 emit
clickedMid(m_fileName
, line
, address
);
375 void SourceWindow::keyPressEvent(QKeyEvent
* ev
)
381 actionExpandRow(m_curRow
);
384 actionCollapseRow(m_curRow
);
388 setCursorPosition(m_curRow
-1, 0);
392 if (m_curRow
< paragraphs()-1) {
393 setCursorPosition(m_curRow
+1, 0);
397 setCursorPosition(0, 0);
400 setCursorPosition(paragraphs()-1, 0);
404 top
= viewportToContents(QPoint(0,0));
405 top1
= paragraphAt(top
);
408 QTextEdit::keyPressEvent(ev
);
413 top
= viewportToContents(QPoint(0,0));
414 top2
= paragraphAt(top
);
415 setCursorPosition(m_curRow
+(top2
-top1
), 0);
419 static inline bool isident(QChar c
)
421 return c
.isLetterOrNumber() || c
.latin1() == '_';
424 bool SourceWindow::wordAtPoint(const QPoint
& p
, QString
& word
, QRect
& r
)
426 QPoint pv
= viewportToContents(p
- viewport()->pos());
427 int row
, col
= charAt(pv
, &row
);
428 if (row
< 0 || col
< 0)
431 // isolate the word at row, col
432 QString line
= text(row
);
433 if (!isident(line
[col
]))
437 while (begin
> 0 && isident(line
[begin
-1]))
441 while (col
< int(line
.length()) && isident(line
[col
]));
444 r
.addCoords(-5,-5,5,5);
445 word
= line
.mid(begin
, col
-begin
);
449 void SourceWindow::paletteChange(const QPalette
& oldPal
)
451 setFont(KGlobalSettings::fixedFont());
452 QTextEdit::paletteChange(oldPal
);
456 * Two file names (possibly full paths) match if the last parts - the file
459 bool SourceWindow::fileNameMatches(const QString
& other
)
461 const QString
& me
= fileName();
463 // check for null file names first
464 if (me
.isNull() || other
.isNull()) {
465 return me
.isNull() && other
.isNull();
469 * Get file names. Note: Either there is a slash, then skip it, or
470 * there is no slash, then -1 + 1 = 0!
472 int sme
= me
.findRev('/') + 1;
473 int sother
= other
.findRev('/') + 1;
474 return strcmp(me
.data() + sme
, other
.data() + sother
) == 0;
477 void SourceWindow::disassembled(int lineNo
, const QList
<DisassembledCode
>& disass
)
479 TRACE("disassembled line " + QString().setNum(lineNo
));
480 if (lineNo
< 0 || lineNo
>= int(m_sourceCode
.size()))
483 SourceLine
& sl
= m_sourceCode
[lineNo
];
485 // copy disassembled code and its addresses
486 sl
.disass
.resize(disass
.count());
487 sl
.disassAddr
.resize(disass
.count());
488 sl
.canDisass
= disass
.count() > 0;
489 for (uint i
= 0; i
< disass
.count(); i
++) {
490 const DisassembledCode
* c
=
491 const_cast<QList
<DisassembledCode
>&>(disass
).at(i
);
492 QString code
= c
->code
;
493 while (code
.endsWith("\n"))
494 code
.truncate(code
.length()-1);
495 sl
.disass
[i
] = c
->address
.asString() + ' ' + code
;
496 sl
.disassAddr
[i
] = c
->address
;
499 int row
= lineToRow(lineNo
);
503 // clear expansion marker
508 int SourceWindow::rowToLine(int row
, int* sourceRow
)
510 int line
= row
>= 0 ? m_rowToLine
[row
] : -1;
511 if (sourceRow
!= 0) {
512 // search back until we hit the first entry with the current line number
513 while (row
> 0 && m_rowToLine
[row
-1] == line
)
521 * Rows showing diassembled code have the same line number as the
522 * corresponding source code line number. Therefore, the line numbers in
523 * m_rowToLine are monotonically increasing with blocks of equal line
524 * numbers for a source line and its disassembled code that follows it.
526 * Hence, m_rowToLine always obeys the following condition:
528 * m_rowToLine[i] <= i
531 int SourceWindow::lineToRow(int line
)
533 // line is zero-based!
535 assert(line
< int(m_rowToLine
.size()));
537 // quick test for common case
538 if (line
< 0 || m_rowToLine
[line
] == line
)
541 assert(m_rowToLine
[line
] < line
);
544 * Binary search between row == line and end of list. In the loop below
545 * we use the fact that the line numbers m_rowToLine do not contain
549 int h
= m_rowToLine
.size();
550 while (l
< h
&& m_rowToLine
[l
] != line
)
552 assert(h
== int(m_rowToLine
.size()) || m_rowToLine
[l
] < m_rowToLine
[h
]);
555 * We want to round down the midpoint so that we find the
556 * lowest row that belongs to the line we seek.
559 if (m_rowToLine
[mid
] <= line
)
564 // Found! Result is in l:
565 assert(m_rowToLine
[l
] == line
);
568 * We might not have hit the lowest index for the line.
570 while (l
> 0 && m_rowToLine
[l
-1] == line
)
576 int SourceWindow::lineToRow(int line
, const DbgAddr
& address
)
578 int row
= lineToRow(line
);
579 if (isRowExpanded(row
)) {
580 row
+= m_sourceCode
[line
].findAddressRowOffset(address
);
585 bool SourceWindow::isRowExpanded(int row
)
588 return row
< int(m_rowToLine
.size())-1 &&
589 m_rowToLine
[row
] == m_rowToLine
[row
+1];
592 bool SourceWindow::isRowDisassCode(int row
)
594 return row
> 0 && row
< int(m_rowToLine
.size()) &&
595 m_rowToLine
[row
] == m_rowToLine
[row
-1];
598 void SourceWindow::expandRow(int row
)
600 TRACE("expanding row " + QString().setNum(row
));
601 // get disassembled code
602 int line
= rowToLine(row
);
603 const std::vector
<QString
>& disass
= m_sourceCode
[line
].disass
;
605 // remove PC (must be set again in slot of signal expanded())
606 m_lineItems
[row
] &= ~(liPC
|liPCup
);
608 // adjust current row
609 if (m_curRow
> row
) {
610 m_curRow
+= disass
.size();
611 // highlight is moved automatically
615 setUpdatesEnabled(false);
617 for (size_t i
= 0; i
< disass
.size(); i
++) {
618 m_rowToLine
.insert(m_rowToLine
.begin()+row
, line
);
619 m_lineItems
.insert(m_lineItems
.begin()+row
, 0);
620 insertParagraph(disass
[i
], row
++);
622 setUpdatesEnabled(true);
623 viewport()->update();
624 update(); // line items
626 emit
expanded(line
); /* must set PC */
629 void SourceWindow::collapseRow(int row
)
631 TRACE("collapsing row " + QString().setNum(row
));
632 int line
= rowToLine(row
);
634 // find end of this block
636 while (end
< int(m_rowToLine
.size()) && m_rowToLine
[end
] == m_rowToLine
[row
]) {
640 // adjust current row
641 if (m_curRow
>= row
) {
643 if (m_curRow
< row
) // was m_curRow in disassembled code?
646 setUpdatesEnabled(false);
647 while (--end
>= row
) {
648 m_rowToLine
.erase(m_rowToLine
.begin()+end
);
649 m_lineItems
.erase(m_lineItems
.begin()+end
);
650 removeParagraph(end
);
652 setUpdatesEnabled(true);
653 viewport()->update();
654 update(); // line items
656 emit
collapsed(line
);
659 void SourceWindow::activeLine(int& line
, DbgAddr
& address
)
664 line
= rowToLine(row
, &sourceRow
);
665 if (row
> sourceRow
) {
666 int off
= row
- sourceRow
; /* offset from source line */
667 address
= m_sourceCode
[line
].disassAddr
[off
-1];
672 * Returns the offset from the line displaying the source code to
673 * the line containing the specified address. If the address is not
674 * found, 0 is returned.
676 int SourceWindow::SourceLine::findAddressRowOffset(const DbgAddr
& address
) const
678 if (address
.isEmpty())
681 for (size_t i
= 0; i
< disassAddr
.size(); i
++) {
682 if (disassAddr
[i
] == address
) {
683 // found exact address
686 if (disassAddr
[i
] > address
) {
688 * We have already advanced too far; the address is before this
689 * index, but obviously we haven't found an exact match
690 * earlier. address is somewhere between the displayed
691 * addresses. We return the previous line.
700 void SourceWindow::actionExpandRow(int row
)
702 if (row
< 0 || isRowExpanded(row
) || isRowDisassCode(row
))
706 int line
= rowToLine(row
);
707 const SourceLine
& sl
= m_sourceCode
[line
];
710 if (sl
.disass
.size() == 0) {
711 emit
disassemble(m_fileName
, line
);
717 void SourceWindow::actionCollapseRow(int row
)
719 if (row
< 0 || !isRowExpanded(row
) || isRowDisassCode(row
))
725 void SourceWindow::setTabWidth(int numChars
)
729 QFontMetrics
fm(currentFont());
731 int w
= fm
.width(s
.fill('x', numChars
));
735 void SourceWindow::cursorChanged(int row
)
740 if (m_curRow
>= 0 && m_curRow
< paragraphs())
741 clearParagraphBackground(m_curRow
);
743 setParagraphBackgroundColor(row
, colorGroup().background());
749 * We must override the context menu handling because QTextEdit's handling
750 * requires that it receives ownership of the popup menu; but the popup menu
751 * returned from the GUI factory is owned by the factory.
754 void SourceWindow::contextMenuEvent(QContextMenuEvent
* e
)
756 // get the context menu from the GUI factory
759 top
= top
->parentWidget();
760 while (!top
->isTopLevel());
761 KMainWindow
* mw
= static_cast<KMainWindow
*>(top
);
763 static_cast<QPopupMenu
*>(mw
->factory()->container("popup_files", mw
));
764 m
->exec(e
->globalPos());
767 bool SourceWindow::eventFilter(QObject
* watched
, QEvent
* e
)
769 if (e
->type() == QEvent::ContextMenu
&& watched
== viewport())
771 contextMenuEvent(static_cast<QContextMenuEvent
*>(e
));
774 return QTextEdit::eventFilter(watched
, e
);
777 HighlightCpp::HighlightCpp(SourceWindow
* srcWnd
) :
778 QSyntaxHighlighter(srcWnd
),
790 static const QString ckw
[] =
868 int HighlightCpp::highlightParagraph(const QString
& text
, int state
)
870 int row
= currentParagraph();
871 // highlight assembly lines
872 if (m_srcWnd
->isRowDisassCode(row
))
874 setFormat(0, text
.length(), blue
);
878 if (state
== -2) // initial state
881 // check for preprocessor line
882 if (state
== 0 && text
.stripWhiteSpace().startsWith("#"))
884 setFormat(0, text
.length(), QColor("dark green"));
888 // a font for keywords
889 QFont identFont
= textEdit()->currentFont();
890 identFont
.setBold(!identFont
.bold());
893 while (start
< text
.length())
900 setFormat(start
, end
-start
, QColor("gray50"));
903 end
= text
.find("*/", start
);
908 setFormat(start
, end
-start
, QColor("gray50"));
911 for (end
= start
+1; end
< int(text
.length()); end
++) {
912 if (text
[end
] == '\\') {
913 if (end
< int(text
.length()))
915 } else if (text
[end
] == text
[start
]) {
921 setFormat(start
, end
-start
, QColor("dark red"));
924 for (end
= start
+1; end
< int(text
.length()); end
++) {
925 if (!text
[end
].isLetterOrNumber() && text
[end
] != '_')
929 if (std::binary_search(ckw
, ckw
+ sizeof(ckw
)/sizeof(ckw
[0]),
930 text
.mid(start
, end
-start
)))
932 setFormat(start
, end
-start
, identFont
);
934 setFormat(start
, end
-start
, m_srcWnd
->colorGroup().text());
938 for (end
= start
; end
< int(text
.length()); end
++)
940 if (text
[end
] == '/')
942 if (end
+1 < int(text
.length())) {
943 if (text
[end
+1] == '/') {
944 state
= hlCommentLine
;
946 } else if (text
[end
+1] == '*') {
947 state
= hlCommentBlock
;
952 else if (text
[end
] == '"' || text
[end
] == '\'')
957 else if (text
[end
] >= 'A' && text
[end
] <= 'Z' ||
958 text
[end
] >= 'a' && text
[end
] <= 'z' ||
965 setFormat(start
, end
-start
, m_srcWnd
->colorGroup().text());
972 #include "sourcewnd.moc"