/*
 * Copyright Johannes Sixt
 * This file is licensed under the GNU General Public License Version 2.
 * See the file COPYING in the toplevel directory of the source directory.
 */

#include "debugger.h"
#include "sourcewnd.h"
#include <tqtextstream.h>
#include <tqpainter.h>
#include <tqbrush.h>
#include <tqfile.h>
#include <tqfileinfo.h>
#include <tqkeycode.h>
#include <tqpopupmenu.h>
#include <tdeapplication.h>
#include <kiconloader.h>
#include <tdeglobalsettings.h>
#include <tdemainwindow.h>
#include <algorithm>
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "mydebug.h"


SourceWindow::SourceWindow(const TQString& fileName, TQWidget* parent, const char* name) :
	TQTextEdit(parent, name),
	m_fileName(fileName),
	m_curRow(-1),
	m_widthItems(16),
	m_widthPlus(12),
	m_widthLineNo(30)
{
    // load pixmaps
    m_pcinner = UserIcon("pcinner");
    m_pcup = UserIcon("pcup");
    m_brkena = UserIcon("brkena");
    m_brkdis = UserIcon("brkdis");
    m_brktmp = UserIcon("brktmp");
    m_brkcond = UserIcon("brkcond");
    m_brkorph = UserIcon("brkorph");
    setFont(TDEGlobalSettings::fixedFont());
    setReadOnly(true);
    setMargins(m_widthItems+m_widthPlus+m_widthLineNo, 0, 0 ,0);
    setAutoFormatting(AutoNone);
    setTextFormat(PlainText);
    setWordWrap(NoWrap);
    connect(verticalScrollBar(), TQ_SIGNAL(valueChanged(int)),
	    this, TQ_SLOT(update()));
    connect(this, TQ_SIGNAL(cursorPositionChanged(int,int)), this, TQ_SLOT(cursorChanged(int)));
    viewport()->installEventFilter(this);

    // add a syntax highlighter
    if (TQRegExp("\\.(c(pp|c|\\+\\+)?|CC?|h(\\+\\+|h)?|HH?)$").search(m_fileName) >= 0)
    {
	new HighlightCpp(this);
    }
}

SourceWindow::~SourceWindow()
{
    delete syntaxHighlighter();
}

bool SourceWindow::loadFile()
{
    // first we load the code into TQTextEdit
    TQFile f(m_fileName);
    if (!f.open(IO_ReadOnly)) {
	return false;
    }

    TQTextStream t(&f);
    setText(t.read());
    f.close();

    // then we copy it into our own m_sourceCode
    int n = paragraphs();
    m_sourceCode.resize(n);
    m_rowToLine.resize(n);
    for (int i = 0; i < n; i++) {
	m_sourceCode[i].code = text(i);
	m_rowToLine[i] = i;
    }
    m_lineItems.resize(n, 0);

    // set a font for line numbers
    m_lineNoFont = currentFont();
    m_lineNoFont.setPixelSize(11);

    return true;
}

void SourceWindow::reloadFile()
{
    TQFile f(m_fileName);
    if (!f.open(IO_ReadOnly)) {
	// open failed; leave alone
	return;
    }

    // read text into m_sourceCode
    m_sourceCode.clear();		/* clear old text */

    TQTextStream t(&f);
    setText(t.read());
    f.close();

    m_sourceCode.resize(paragraphs());
    for (size_t i = 0; i < m_sourceCode.size(); i++) {
	m_sourceCode[i].code = text(i);
    }
    // expanded lines are collapsed: move existing line items up
    for (size_t i = 0; i < m_lineItems.size(); i++) {
	if (m_rowToLine[i] != i) {
	    m_lineItems[m_rowToLine[i]] |= m_lineItems[i];
	    m_lineItems[i] = 0;
	}
    }
    // allocate line items
    m_lineItems.resize(m_sourceCode.size(), 0);

    m_rowToLine.resize(m_sourceCode.size());
    for (size_t i = 0; i < m_sourceCode.size(); i++)
	m_rowToLine[i] = i;

    // Highlighting was applied above when the text was inserted into widget,
    // but at that time m_rowToLine was not corrected, yet, so that lines
    // that previously were assembly were painted incorrectly.
    if (syntaxHighlighter())
	syntaxHighlighter()->rehighlight();
    update();	// line numbers
}

void SourceWindow::scrollTo(int lineNo, const DbgAddr& address)
{
    if (lineNo < 0 || lineNo >= int(m_sourceCode.size()))
	return;

    int row = lineToRow(lineNo, address);
    scrollToRow(row);
}

void SourceWindow::scrollToRow(int row)
{
    setCursorPosition(row, 0);
    ensureCursorVisible();
}

void SourceWindow::drawFrame(TQPainter* p)
{
    TQTextEdit::drawFrame(p);

    // and paragraph at the top is...
    int top = paragraphAt(TQPoint(0,contentsY()));
    int bot = paragraphAt(TQPoint(0,contentsY()+visibleHeight()-1));
    if (bot < 0)
	bot = paragraphs()-1;

    p->save();

    // set a clip rectangle
    int fw = frameWidth();
    TQRect inside = rect();
    inside.addCoords(fw,fw,-fw,-fw);
    TQRegion clip = p->clipRegion();
    clip &= TQRegion(inside);
    p->setClipRegion(clip);

    p->setFont(m_lineNoFont);
    p->setPen(colorGroup().text());
    p->eraseRect(inside);

    for (int row = top; row <= bot; row++)
    {
	uchar item = m_lineItems[row];
	p->save();

	TQRect r = paragraphRect(row);
	TQPoint pt = contentsToViewport(r.topLeft());
	int h = r.height();
	p->translate(fw, pt.y()+viewport()->y());

	if (item & liBP) {
	    // enabled breakpoint
	    int y = (h - m_brkena.height())/2;
	    if (y < 0) y = 0;
	    p->drawPixmap(0,y,m_brkena);
	}
	if (item & liBPdisabled) {
	    // disabled breakpoint
	    int y = (h - m_brkdis.height())/2;
	    if (y < 0) y = 0;
	    p->drawPixmap(0,y,m_brkdis);
	}
	if (item & liBPtemporary) {
	    // temporary breakpoint marker
	    int y = (h - m_brktmp.height())/2;
	    if (y < 0) y = 0;
	    p->drawPixmap(0,y,m_brktmp);
	}
	if (item & liBPconditional) {
	    // conditional breakpoint marker
	    int y = (h - m_brkcond.height())/2;
	    if (y < 0) y = 0;
	    p->drawPixmap(0,y,m_brkcond);
	}
	if (item & liBPorphan) {
	    // orphaned breakpoint marker
	    int y = (h - m_brkcond.height())/2;
	    if (y < 0) y = 0;
	    p->drawPixmap(0,y,m_brkorph);
	}
	if (item & liPC) {
	    // program counter in innermost frame
	    int y = (h - m_pcinner.height())/2;
	    if (y < 0) y = 0;
	    p->drawPixmap(0,y,m_pcinner);
	}
	if (item & liPCup) {
	    // program counter somewhere up the stack
	    int y = (h - m_pcup.height())/2;
	    if (y < 0) y = 0;
	    p->drawPixmap(0,y,m_pcup);
	}
	p->translate(m_widthItems, 0);
	if (!isRowDisassCode(row) && m_sourceCode[rowToLine(row)].canDisass) {
	    int w = m_widthPlus;
	    int x = w/2;
	    int y = h/2;
	    p->drawLine(x-2, y, x+2, y);
	    if (!isRowExpanded(row)) {
		p->drawLine(x, y-2, x, y+2);
	    }
	}
	p->translate(m_widthPlus, 0);
	if (!isRowDisassCode(row)) {
	    p->drawText(0, 0, m_widthLineNo, h, AlignRight|AlignVCenter,
			TQString().setNum(rowToLine(row)+1));
	}
	p->restore();
    }
    p->restore();
}

void SourceWindow::updateLineItems(const KDebugger* dbg)
{
    // clear outdated breakpoints
    for (int i = m_lineItems.size()-1; i >= 0; i--) {
	if (m_lineItems[i] & liBPany) {
	    // check if this breakpoint still exists
	    int line = rowToLine(i);
	    TRACE(TQString("checking for bp at %1").arg(line));
	    KDebugger::BrkptROIterator bp = dbg->breakpointsBegin();
	    for (; bp != dbg->breakpointsEnd(); ++bp)
	    {
		if (bp->lineNo == line &&
		    fileNameMatches(bp->fileName) &&
		    lineToRow(line, bp->address) == i)
		{
		    // yes it exists; mode is changed below
		    break;
		}
	    }
	    if (bp == dbg->breakpointsEnd()) {
		/* doesn't exist anymore, remove it */
		m_lineItems[i] &= ~liBPany;
		update();
	    }
	}
    }

    // add new breakpoints
    for (KDebugger::BrkptROIterator bp = dbg->breakpointsBegin(); bp != dbg->breakpointsEnd(); ++bp)
    {
	if (fileNameMatches(bp->fileName)) {
	    TRACE(TQString("updating %1:%2").arg(bp->fileName).arg(bp->lineNo));
	    int i = bp->lineNo;
	    if (i < 0 || i >= int(m_sourceCode.size()))
		continue;
	    // compute new line item flags for breakpoint
	    uchar flags = bp->enabled ? liBP : liBPdisabled;
	    if (bp->temporary)
		flags |= liBPtemporary;
	    if (!bp->condition.isEmpty() || bp->ignoreCount != 0)
		flags |= liBPconditional;
	    if (bp->isOrphaned())
		flags |= liBPorphan;
	    // update if changed
	    int row = lineToRow(i, bp->address);
	    if ((m_lineItems[row] & liBPany) != flags) {
		m_lineItems[row] &= ~liBPany;
		m_lineItems[row] |= flags;
		update();
	    }
	}
    }
}

void SourceWindow::setPC(bool set, int lineNo, const DbgAddr& address, int frameNo)
{
    if (lineNo < 0 || lineNo >= int(m_sourceCode.size())) {
	return;
    }

    int row = lineToRow(lineNo, address);

    uchar flag = frameNo == 0  ?  liPC  :  liPCup;
    if (set) {
	// set only if not already set
	if ((m_lineItems[row] & flag) == 0) {
	    m_lineItems[row] |= flag;
	    update();
	}
    } else {
	// clear only if not set
	if ((m_lineItems[row] & flag) != 0) {
	    m_lineItems[row] &= ~flag;
	    update();
	}
    }
}

void SourceWindow::find(const TQString& text, bool caseSensitive, FindDirection dir)
{
    ASSERT(dir == 1 || dir == -1);
    if (TQTextEdit::find(text, caseSensitive, false, dir > 0))
	return;
    // not found; wrap around
    int para = dir > 0 ? 0 : paragraphs(), index = 0;
    TQTextEdit::find(text, caseSensitive, false, dir > 0, &para, &index);
}

void SourceWindow::mousePressEvent(TQMouseEvent* ev)
{
    // we handle left and middle button
    if (ev->button() != LeftButton && ev->button() != MidButton)
    {
	TQTextEdit::mousePressEvent(ev);
	return;
    }

    // get row
    TQPoint p = viewportToContents(TQPoint(0, ev->y() - viewport()->y()));
    int row = paragraphAt(p);
    if (row < 0)
	return;

    if (ev->x() > m_widthItems+frameWidth())
    {
	if (isRowExpanded(row)) {
	    actionCollapseRow(row);
	} else {
	    actionExpandRow(row);
	}
	return;
    }

    int sourceRow;
    int line = rowToLine(row, &sourceRow);

    // find address if row is disassembled code
    DbgAddr address;
    if (row > sourceRow) {
	// get offset from source code line
	int off = row - sourceRow;
	address = m_sourceCode[line].disassAddr[off-1];
    }

    switch (ev->button()) {
    case LeftButton:
	TRACE(TQString("left-clicked line %1").arg(line));
	emit clickedLeft(m_fileName, line, address,
			 (ev->state() & ShiftButton) != 0);
	break;
    case MidButton:
	TRACE(TQString("mid-clicked row %1").arg(line));
	emit clickedMid(m_fileName, line, address);
	break;
    default:;
    }
}

void SourceWindow::keyPressEvent(TQKeyEvent* ev)
{
    int top1, top2;
    TQPoint top;
    switch (ev->key()) {
    case Key_Plus:
	actionExpandRow(m_curRow);
	return;
    case Key_Minus:
	actionCollapseRow(m_curRow);
	return;
    case Key_Up:
	if (m_curRow > 0) {
	    setCursorPosition(m_curRow-1, 0);
	}
	return;
    case Key_Down:
	if (m_curRow < paragraphs()-1) {
	    setCursorPosition(m_curRow+1, 0);
	}
	return;
    case Key_Home:
	setCursorPosition(0, 0);
	return;
    case Key_End:
	setCursorPosition(paragraphs()-1, 0);
	return;
    case Key_Next:
    case Key_Prior:
	top = viewportToContents(TQPoint(0,0));
	top1 = paragraphAt(top);
    }

    TQTextEdit::keyPressEvent(ev);

    switch (ev->key()) {
    case Key_Next:
    case Key_Prior:
	top = viewportToContents(TQPoint(0,0));
	top2 = paragraphAt(top);
	setCursorPosition(m_curRow+(top2-top1), 0);
    }
}

static inline bool isident(TQChar c)
{
    return c.isLetterOrNumber() || c.latin1() == '_';
}

bool SourceWindow::wordAtPoint(const TQPoint& p, TQString& word, TQRect& r)
{
    TQPoint pv = viewportToContents(p - viewport()->pos());
    int row, col  = charAt(pv, &row);
    if (row < 0 || col < 0)
	return false;

    // isolate the word at row, col
    TQString line = text(row);
    if (!isident(line[col]))
	return false;

    int begin = col;
    while (begin > 0 && isident(line[begin-1]))
	--begin;
    do
	++col;
    while (col < int(line.length()) && isident(line[col]));

    r = TQRect(p, p);
    r.addCoords(-5,-5,5,5);
    word = line.mid(begin, col-begin);
    return true;
}

void SourceWindow::paletteChange(const TQPalette& oldPal)
{
    setFont(TDEGlobalSettings::fixedFont());
    TQTextEdit::paletteChange(oldPal);
}

/*
 * Two file names (possibly full paths) match if the last parts - the file
 * names - match.
 */
bool SourceWindow::fileNameMatches(const TQString& other)
{
    return TQFileInfo(other).fileName() == TQFileInfo(m_fileName).fileName();
}

void SourceWindow::disassembled(int lineNo, const std::list<DisassembledCode>& disass)
{
    TRACE("disassembled line " + TQString().setNum(lineNo));
    if (lineNo < 0 || lineNo >= int(m_sourceCode.size()))
	return;

    SourceLine& sl = m_sourceCode[lineNo];

    // copy disassembled code and its addresses
    sl.disass.resize(disass.size());
    sl.disassAddr.resize(disass.size());
    sl.canDisass = !disass.empty();
    int i = 0;
    for (std::list<DisassembledCode>::const_iterator c = disass.begin(); c != disass.end(); ++c, ++i)
    {
	TQString code = c->code;
	while (code.endsWith("\n"))
	    code.truncate(code.length()-1);
	sl.disass[i] = c->address.asString() + ' ' + code;
	sl.disassAddr[i] = c->address;
    }

    int row = lineToRow(lineNo);
    if (sl.canDisass) {
	expandRow(row);
    } else {
	// clear expansion marker
	update();
    }
}

int SourceWindow::rowToLine(int row, int* sourceRow)
{
    int line = row >= 0  ?  m_rowToLine[row]  :  -1;
    if (sourceRow != 0) {
	// search back until we hit the first entry with the current line number
	while (row > 0 && m_rowToLine[row-1] == line)
	    row--;
	*sourceRow = row;
    }
    return line;
}

/*
 * Rows showing diassembled code have the same line number as the
 * corresponding source code line number. Therefore, the line numbers in
 * m_rowToLine are monotonically increasing with blocks of equal line
 * numbers for a source line and its disassembled code that follows it.
 *
 * Hence, m_rowToLine always obeys the following condition:
 *
 *    m_rowToLine[i] <= i
 */

int SourceWindow::lineToRow(int line)
{
    // line is zero-based!

    assert(line < int(m_rowToLine.size()));

    // quick test for common case
    if (line < 0 || m_rowToLine[line] == line)
	return line;

    assert(m_rowToLine[line] < line);

    /*
     * Binary search between row == line and end of list. In the loop below
     * we use the fact that the line numbers m_rowToLine do not contain
     * holes.
     */
    int l = line;
    int h = m_rowToLine.size();
    while (l < h && m_rowToLine[l] != line)
    {
	assert(h == int(m_rowToLine.size()) || m_rowToLine[l] < m_rowToLine[h]);

	/*
	 * We want to round down the midpoint so that we find the
	 * lowest row that belongs to the line we seek.
	 */
	int mid = (l+h)/2;
	if (m_rowToLine[mid] <= line)
	    l = mid;
	else
	    h = mid;
    }
    // Found! Result is in l:
    assert(m_rowToLine[l] == line);

    /*
     * We might not have hit the lowest index for the line.
     */
    while (l > 0 && m_rowToLine[l-1] == line)
	--l;

    return l;
}

int SourceWindow::lineToRow(int line, const DbgAddr& address)
{
    int row = lineToRow(line);
    if (isRowExpanded(row)) {
	row += m_sourceCode[line].findAddressRowOffset(address);
    }
    return row;
}

bool SourceWindow::isRowExpanded(int row)
{
    assert(row >= 0);
    return row < int(m_rowToLine.size())-1 &&
	m_rowToLine[row] == m_rowToLine[row+1];
}

bool SourceWindow::isRowDisassCode(int row)
{
    return row > 0 && row < int(m_rowToLine.size()) &&
	m_rowToLine[row] == m_rowToLine[row-1];
}

void SourceWindow::expandRow(int row)
{
    TRACE("expanding row " + TQString().setNum(row));
    // get disassembled code
    int line = rowToLine(row);
    const std::vector<TQString>& disass = m_sourceCode[line].disass;

    // remove PC (must be set again in slot of signal expanded())
    m_lineItems[row] &= ~(liPC|liPCup);

    // adjust current row
    if (m_curRow > row) {
	m_curRow += disass.size();
	// highlight is moved automatically
    }

    // insert new lines
    setUpdatesEnabled(false);
    ++row;
    for (size_t i = 0; i < disass.size(); i++) {
	m_rowToLine.insert(m_rowToLine.begin()+row, line);
	m_lineItems.insert(m_lineItems.begin()+row, 0);
	insertParagraph(disass[i], row++);
    }
    setUpdatesEnabled(true);
    viewport()->update();
    update();		// line items

    emit expanded(line);		/* must set PC */
}

void SourceWindow::collapseRow(int row)
{
    TRACE("collapsing row " + TQString().setNum(row));
    int line = rowToLine(row);

    // find end of this block
    int end = row+1;
    while (end < int(m_rowToLine.size()) && m_rowToLine[end] == m_rowToLine[row]) {
	end++;
    }
    ++row;
    // adjust current row
    if (m_curRow >= row) {
	m_curRow -= end-row;
	if (m_curRow < row)	// was m_curRow in disassembled code?
	    m_curRow = -1;
    }
    setUpdatesEnabled(false);
    while (--end >= row) {
	m_rowToLine.erase(m_rowToLine.begin()+end);
	m_lineItems.erase(m_lineItems.begin()+end);
	removeParagraph(end);
    }
    setUpdatesEnabled(true);
    viewport()->update();
    update();		// line items

    emit collapsed(line);
}

void SourceWindow::activeLine(int& line, DbgAddr& address)
{
    int row = m_curRow;

    int sourceRow;
    line = rowToLine(row, &sourceRow);
    if (row > sourceRow) {
	int off = row - sourceRow;	/* offset from source line */
	address = m_sourceCode[line].disassAddr[off-1];
    }
}

/**
 * Returns the offset from the line displaying the source code to
 * the line containing the specified address. If the address is not
 * found, 0 is returned.
 */
int SourceWindow::SourceLine::findAddressRowOffset(const DbgAddr& address) const
{
    if (address.isEmpty())
	return 0;

    for (size_t i = 0; i < disassAddr.size(); i++) {
	if (disassAddr[i] == address) {
	    // found exact address
	    return i+1;
	}
	if (disassAddr[i] > address) {
	    /*
	     * We have already advanced too far; the address is before this
	     * index, but obviously we haven't found an exact match
	     * earlier. address is somewhere between the displayed
	     * addresses. We return the previous line.
	     */
	    return i;
	}
    }
    // not found
    return 0;
}

void SourceWindow::actionExpandRow(int row)
{
    if (row < 0 || isRowExpanded(row) || isRowDisassCode(row))
	return;

    // disassemble
    int line = rowToLine(row);
    const SourceLine& sl = m_sourceCode[line];
    if (!sl.canDisass)
	return;
    if (sl.disass.size() == 0) {
	emit disassemble(m_fileName, line);
    } else {
	expandRow(row);
    }
}

void SourceWindow::actionCollapseRow(int row)
{
    if (row < 0 || !isRowExpanded(row) || isRowDisassCode(row))
	return;

    collapseRow(row);
}

void SourceWindow::setTabWidth(int numChars)
{
    if (numChars <= 0)
	numChars = 8;
    TQFontMetrics fm(currentFont());
    TQString s;
    int w = fm.width(s.fill('x', numChars));
    setTabStopWidth(w);
}

void SourceWindow::cursorChanged(int row)
{
    if (row == m_curRow)
	return;

    if (m_curRow >= 0 && m_curRow < paragraphs())
	clearParagraphBackground(m_curRow);
    m_curRow = row;
    setParagraphBackgroundColor(row, colorGroup().background());
}

/*
 * We must override the context menu handling because TQTextEdit's handling
 * requires that it receives ownership of the popup menu; but the popup menu
 * returned from the GUI factory is owned by the factory.
 */

void SourceWindow::contextMenuEvent(TQContextMenuEvent* e)
{
    // get the context menu from the GUI factory
    TQWidget* top = this;
    do
	top = top->parentWidget();
    while (!top->isTopLevel());
    TDEMainWindow* mw = static_cast<TDEMainWindow*>(top);
    TQPopupMenu* m =
	static_cast<TQPopupMenu*>(mw->factory()->container("popup_files", mw));
    m->exec(e->globalPos());
}

bool SourceWindow::eventFilter(TQObject* watched, TQEvent* e)
{
    if (e->type() == TQEvent::ContextMenu && watched == viewport())
    {
	contextMenuEvent(static_cast<TQContextMenuEvent*>(e));
	return true;
    }
    return TQTextEdit::eventFilter(watched, e);
}

HighlightCpp::HighlightCpp(SourceWindow* srcWnd) :
	TQSyntaxHighlighter(srcWnd),
	m_srcWnd(srcWnd)
{
}

enum HLState {
    hlCommentLine = 1,
    hlCommentBlock,
    hlIdent,
    hlString
};

static const TQString ckw[] =
{
    "and",
    "and_eq",
    "asm",
    "auto",
    "bitand",
    "bitor",
    "bool",
    "break",
    "case",
    "catch",
    "char",
    "class",
    "compl",
    "const",
    "const_cast",
    "continue",
    "default",
    "delete",
    "do",
    "double",
    "dynamic_cast",
    "else",
    "enum",
    "explicit",
    "export",
    "extern",
    "false",
    "float",
    "for",
    "friend",
    "goto",
    "if",
    "inline",
    "int",
    "long",
    "mutable",
    "namespace",
    "new",
    "not",
    "not_eq",
    "operator",
    "or",
    "or_eq",
    "private",
    "protected",
    "public",
    "reinterpret_cast",
    "register",
    "return",
    "short",
    "signed",
    "sizeof",
    "static",
    "static_cast",
    "struct",
    "switch",
    "template",
    "this",
    "throw",
    "true",
    "try",
    "typedef",
    "typeid",
    "typename",
    "using",
    "union",
    "unsigned",
    "virtual",
    "void",
    "volatile",
    "wchar_t",
    "while",
    "xor",
    "xor_eq"
};

int HighlightCpp::highlightParagraph(const TQString& text, int state)
{
    int row = currentParagraph();
    // highlight assembly lines
    if (m_srcWnd->isRowDisassCode(row))
    {
	setFormat(0, text.length(), blue);
	return state;
    }

    if (state == -2)		// initial state
	state = 0;

    // check for preprocessor line
    if (state == 0 && text.stripWhiteSpace().startsWith("#"))
    {
	setFormat(0, text.length(), TQColor("dark green"));
	return 0;
    }

    // a font for keywords
    TQFont identFont = textEdit()->currentFont();
    identFont.setBold(!identFont.bold());

    unsigned start = 0;
    while (start < text.length())
    {
	int end;
	switch (state) {
	case hlCommentLine:
	    end = text.length();
	    state = 0;
	    setFormat(start, end-start, TQColor("gray50"));
	    break;
	case hlCommentBlock:
	    end = text.find("*/", start);
	    if (end >= 0)
		end += 2, state = 0;
	    else
		end = text.length();
	    setFormat(start, end-start, TQColor("gray50"));
	    break;
	case hlString:
	    for (end = start+1; end < int(text.length()); end++) {
		if (text[end] == '\\') {
		    if (end < int(text.length()))
			++end;
		} else if (text[end] == text[start]) {
		    ++end;
		    break;
		}
	    }
	    state = 0;
	    setFormat(start, end-start, TQColor("dark red"));
	    break;
	case hlIdent:
	    for (end = start+1; end < int(text.length()); end++) {
		if (!text[end].isLetterOrNumber() && text[end] != '_')
		    break;
	    }
	    state = 0;
	    if (std::binary_search(ckw, ckw + sizeof(ckw)/sizeof(ckw[0]),
			text.mid(start, end-start)))
	    {
		setFormat(start, end-start, identFont);
	    } else {
		setFormat(start, end-start, m_srcWnd->colorGroup().text());
	    }
	    break;
	default:
	    for (end = start; end < int(text.length()); end++)
	    {
		if (text[end] == '/')
		{
		    if (end+1 < int(text.length())) {
			if (text[end+1] == '/') {
			    state = hlCommentLine;
			    break;
			} else if (text[end+1] == '*') {
			    state = hlCommentBlock;
			    break;
			}
		    }
		}
		else if (text[end] == '"' || text[end] == '\'')
		{
		    state = hlString;
		    break;
		}
		else if (text[end] >= 'A' && text[end] <= 'Z' ||
			 text[end] >= 'a' && text[end] <= 'z' ||
			 text[end] == '_')
		{
		    state = hlIdent;
		    break;
		}
	    }
	    setFormat(start, end-start, m_srcWnd->colorGroup().text());
	}
	start = end;
    }
    return state;
}

#include "sourcewnd.moc"
