/***************************************************************************

  gview.cpp

  (c) 2000-2007 Benoit Minisini <gambas@users.sourceforge.net>

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 1, or (at your option)
  any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

***************************************************************************/

#define __G_VIEW_CPP

#include <qpainter.h>
#include <qscrollbar.h>
#include <qclipboard.h>
#include <qpixmap.h>
#include <qregexp.h>
#include <qapplication.h>
#include <qeventloop.h>
#include <qdragobject.h>
#include <qpopupmenu.h>
#include <qtimer.h>
#include <qdict.h>
#include <qcursor.h>
#include <qstyle.h>
#include <qcstring.h>
#include <qstring.h>

#include <ctype.h>

#include "main.h"
#include "gview.h"

#include <X11/Xlib.h>
#undef None

#if 0
static const char *breakpoint_xpm[] = 
{
"8 8 3 1",
"  c None",
". c #FF7F7F",
"+ c #FF0000",
" .++++. ",
".++++++.",
"++++++++",
"++++++++",
"++++++++",
"++++++++",
".++++++.",
" .++++. "
};
#endif

static QColor defaultColors[GLine::NUM_STATE] =
{
	Qt::white,
	Qt::black,
	QColor(0x00, 0x80, 0xFF),
	Qt::blue,
	Qt::blue,
	Qt::black,
	Qt::red,
	Qt::magenta,
	Qt::gray,
	QColor(0xD0, 0x00, 0x00),
	QColor(0x00, 0x80, 0xFF),
	QColor(0x00, 0x80, 0xFF),
	QColor(0xC0, 0xC0, 0xFF),
	QColor(0xFF, 0xFF, 0x00),
	QColor(0xE8, 0xE8, 0xF8),
	Qt::red
};

/**---- GEditor -----------------------------------------------------------*/

QPixmap *GEditor::cache = 0;
QPixmap *GEditor::breakpoint = 0;
int GEditor::count = 0;

GEditor::GEditor(QWidget *parent) : QGridView(parent, 0, WNoAutoErase), fm(font())
{
	int i;

	if (count == 0)
		cache = new QPixmap();

	count++;

	setNumCols(1);
	setKeyCompression(true);
	setFocusPolicy(WheelFocus);
	setPaletteBackgroundColor(defaultColors[GLine::Background]);
	setInputMethodEnabled(true);
	
	setMouseTracking(true);
	viewport()->setMouseTracking(true);
	viewport()->setCursor(ibeamCursor);
	//viewport()->setWFlags(WRepaintNoErase);

	x = y = xx = 0;
	x1m = x2m = 0;
	ym = -1;
	lastx = -1;
	cursor = false;
	margin = 0;
	lineNumberLength = 0;
	doc = 0;
	center = false;
	setDocument(NULL);
	largestLine = 0;
	flashed = false;
	painting = false;

	for (i = 0; i < GLine::NUM_STATE; i++)
	{
		styles[i].color = defaultColors[i];
		styles[i].bold = i == GLine::Keyword;
		styles[i].italic = i == GLine::Comment;
		styles[i].underline = i == GLine::Error;
	}

	flags = 0;
	//setFlag(ShowCurrentLine, true);
	setFont(QFont("monospace", QApplication::font().pointSize()));
	updateHeight();

	blinkTimer = new QTimer(this);
	connect(blinkTimer, SIGNAL(timeout()), this, SLOT(blinkTimerTimeout()));
	//startBlink();

	scrollTimer = new QTimer(this);
	connect(scrollTimer, SIGNAL(timeout()), this, SLOT(scrollTimerTimeout()));

	connect(this, SIGNAL(contentsMoving(int, int)), this, SLOT(baptizeVisible(int, int)));
}

GEditor::~GEditor()
{
	doc->unsubscribe(this);
	count--;
	if (count == 0)
	{
		delete cache;
		delete breakpoint;
		cache = 0;
		breakpoint = 0;
	}
}

void GEditor::setBreakpointPixmap(QPixmap *p)
{
	if (!breakpoint)
		breakpoint = new QPixmap;
		
	*breakpoint = *p;
}

void GEditor::setDocument(GDocument *d)
{
	if (doc)
	{
		doc->unsubscribe(this);
		//disconnect(doc, 0, this, 0);
	}

	doc = d;
	if (!doc)
		doc = new GDocument;

	doc->subscribe(this);
	findLargestLine();
	//connect(doc, SIGNAL(textChanged()), this, SLOT(docTextChanged()));
}

int GEditor::getCharWidth() const
{
	return fm.width('m');
}

void GEditor::updateCache()
{
	if (cache->width() < visibleWidth() || cache->height() < cellHeight())
	{
		int nw = QMAX(cache->width(), visibleWidth());
		int nh = cellHeight(); //QMAX(cache->height(), cellHeight());
		//qDebug("cache->resize(%d, %d)", nw, nh);
		cache->resize(nw, nh);
	}
}

int GEditor::lineWidth(int y) const
{
	// Add 2 so that we see the cursor at end of line
	return margin + fm.width(doc->lines.at(y)->s.getString()) + 2;
}

int GEditor::lineWidth(int y, int len) const
{
	if (len <= 0)
		return margin;
	else
		return margin + fm.width(doc->lines.at(y)->s.getString(), len);
}

int GEditor::posToColumn(int y, int px) const
{
	int i, lw, lw2;
	int len = doc->lineLength(y);
	
	if (len == 0)
		return 0;
	
	px += contentsX();
	
	lw = lineWidth(y, 0);
	for (i = 0; i < len; i++)
	{
		lw2 = lineWidth(y, i + 1);
		if (px <= ((lw + lw2) >> 1))
			return i;
		lw = lw2;
	}
	
	return len;
}

int GEditor::findLargestLine()
{
	int w, mw = 0;
	
	//qDebug("search largest line...");
	
	for (int y = 0; y < doc->numLines(); y++)
	{
		w = lineWidth(y);
		if (w > mw)
		{
			mw = w;
			largestLine = y;
		}
	}
	
	return mw;
}

void GEditor::updateWidth(int y)
{
	int w;
	
	if (largestLine < 0 || largestLine >= numLines())
	{
		findLargestLine();
		y = -1;
	}
	
	if (y < 0)
	{
		w = lineWidth(largestLine);
		goto UPDATE_WIDTH;
	}
	else
	{
		w = lineWidth(y);
	
		//qDebug("contentsWidth() = %d  cellWidth() = %d", contentsWidth(), cellWidth());
		//setCellWidth(QMAX(visibleWidth(), margin + charWidth * doc->getMaxLineLength() + 2));
		
		if (w > cellWidth())
		{
			largestLine = y;
			goto UPDATE_WIDTH;
		}
		else if (w < cellWidth() && y == largestLine)
		{
			w = findLargestLine();
			goto UPDATE_WIDTH;
		}
	}
	
	return;
	
UPDATE_WIDTH:
	
	w = QMAX(visibleWidth(), w);
	if (w != cellWidth())
	{
		setCellWidth(w);
		//qDebug("setCellWidth: %d (largestLine = %d)", w, largestLine);
	}
	updateCache();
}

void GEditor::updateHeight()
{
	setCellHeight(fm.lineSpacing());
	updateCache();

	if (pattern.height() < cellHeight())
		pattern.resize(16, cellHeight());
}

void GEditor::redrawContents()
{
	updateContents();
	if (contentsHeight() < visibleHeight())
		repaintContents(contentsX(), contentsHeight(), visibleWidth(), visibleHeight() - contentsHeight() + contentsX(), true);
}

void GEditor::fontChange(const QFont &oldFont)
{
	QGridView::fontChange(oldFont);
	
	fm = fontMetrics();
	
	italicFont = font();
	italicFont.setItalic(true);
	
	updateMargin();
	updateHeight();
	updateContents();
}

static int find_last_non_space(const QString &s)
{
	int i;
	
	for (i = s.length() - 1; i >= 0; i--)
	{
		if (s[i].unicode() > 32)
			return i;
	}
	
	return -1;
}

void GEditor::paintDottedSpaces(QPainter &p, int row, int ps, int ls)
{
	QPointArray pa;
	int i, y;
	
	y = fm.ascent();
	for (i = 0; i < ls; i++)
		pa.putPoints(i, 1, lineWidth(row, ps + i) + 1, y);
				
	p.drawPoints(pa);
}


void GEditor::paintText(QPainter &p, GLine *l, int x, int y, int xmin, int lmax, int row)
{
	int i;
	int len, style, pos;
	QString sd;
	GHighlightStyle *st;
	bool italic = false;
	int nx;
	int ps;

	pos = 0;
	p.setFont(font());

	ps = find_last_non_space(l->s.getString()) + 1;

	for (i = 0; i < GB.Count(l->highlight); i++)
	{
		if (pos > (xmin + lmax))
			break;

		style = l->highlight[i].state;
		len = l->highlight[i].len;
		st = &styles[style];
		
		nx = lineWidth(row, pos + len);

		if ((pos + len) >= xmin)
		{
			sd = l->s.mid(pos, len).getString();

			/*if (style == GLine::Keyword)
			{
				if (getFlag(DrawWithRelief))
				{
					p.setPen(styles[GLine::Normal].color);
					p.drawText(x + 1, y + 1, sd);
					p.setPen(styles[style].color);
					p.drawText(x, y, sd);
				}
				else
				{
					p.setPen(styles[style].color);
					p.drawText(x, y, sd);
					p.drawText(x + 1, y, sd);
				}
			}
			else if (style == GLine::Error)
			{
				p.setPen(styles[GLine::Current].color);
				p.drawLine(x, y + 2, x + len * charWidth - 1, y + 2);
				p.setPen(styles[GLine::Normal].color);
				p.drawText(x, y, sd);
			}
			else
			{
				p.setPen(styles[style].color);
				p.drawText(x, y, sd);
			}*/

			p.setPen(styles[style].color);
			
			if (ps >= 0 && pos >= ps)
			{
				paintDottedSpaces(p, row, pos, sd.length());
			}
			else
			{
				if (st->italic != italic)
				{
					italic = st->italic;
					p.setFont(italic ? italicFont : font());
				}

				p.drawText(x, y, sd);
				if (st->bold)
					p.drawText(x + 1, y, sd);
			}

			if (st->underline)
				p.drawLine(x, y + 2, nx - 1, y + 2);
		}

		pos += len;
		x = nx; //+= len * charWidth;
	}

	if (pos < (int)l->s.length() && pos < (xmin + lmax))
	{
		p.setPen(styles[GLine::Normal].color);
		p.drawText(x, y, l->s.mid(pos).getString());
		paintDottedSpaces(p, row, pos, l->s.length() - pos);
	}
}

static void make_blend(QPixmap &pix, QColor start, QColor end, int height) //, bool loop = false)
{
	double r, g, b, dr, dg, db;
	int n = height * 3 / 4;
	int i;
	QPainter p;

	pix.fill(end);

	r = start.red();
	g = start.green();
	b = start.blue();

	if (n == 0)
		n = 1;

	dr = (end.red() - r) / n;
	dg = (end.green() - g) / n;
	db = (end.blue() - b) / n;

	p.begin(&pix);

	//if (loop)
	//	n >>= 1;

	for (i = 0; i < n; i++)
	{
		QBrush brush(QColor((int)r, (int)g, (int)b));
		p.fillRect(0, i, pix.width(), 1, brush);
		//if (loop)
		//  p.fillRect(0, n - i, pix.width(), 1, brush);
		r += dr; g += dg; b += db;
	}

	p.end();
}

/*static QColor blend_color(QColor a, QColor b, QColor r)
{
	#define f(c) ((a.c()*r.c() + (255 - a.c())*(255 - r.c())) * b.c() / 255 / 255)
	return QColor(f(red), f(green), f(blue));
}*/

static QColor calc_color(QColor ca, QColor cb, QColor cd)
{
	int r = 1, g = 1, b = 1, n = 0;

	#define test(x) if (x.isValid()) { r *= x.red(); g *= x.green(); b *= x.blue(); n++; }

	test(ca);
	test(cb);
	//test(cc);

	if (n == 0)
		return cd;
	else
	{
		n = (n == 2) ? 255 : (n == 3) ? 255*255 : 1;
		return QColor(r / n, g / n, b / n);
	}
}

static void highlight_text(QPainter &p, int x, int y, QString s, QColor color)
{
	int i, j;
	
	p.setPen(color);
	
	for (i = -1; i <= 1; i++)
		for (j = -1; j <= 1; j++)
			p.drawText(x + i, y + j, s);
}

void GEditor::paintCell(QPainter * painter, int row, int)
{
	QRect ur;
	GLine *l;
	int x1, y1, x2, y2;
	QColor color, a, b, c;
	int xmin, lmax;
	int i;

	ur = cellGeometry(row, 0);
	contentsToViewport(ur.x(), ur.y(), x1, y1);
	ur.setRect(-x1, y1, ur.width(), ur.height());

	if (row >= numLines())
	{
		/*QColor color = styles[GLine::Background].color;
		if (flashed)
			color = QColor(QRgb(color.rgb() ^ 0x00FFFFFF));
		painter->fillRect(0, 0, ur.width(), ur.height(), styles[GLine::Background].color);*/
		return;
	}

	l = doc->lines.at(row);

	//xmin = (ur.left() - margin) / charWidth;
	//if (xmin < 0)
	//	xmin = 0;
	xmin = posToColumn(row, 0);
	//lmax = 2 + visibleWidth() / charWidth;
	lmax = 2 + posToColumn(row, visibleWidth()) - xmin;
	
	//if (row == 0)
	//	qDebug("%d: %d %d %d %d (%d %d)", row, ur.left(), ur.top(), ur.width(), ur.height(), xmin, lmax);

	// Line background

	if (l->flag & (1 << GLine::CurrentFlag))
		a = styles[GLine::Current].color;

	if (getFlag(ShowCurrentLine) && row == y)
		b = styles[GLine::Line].color;

	QPainter p(cache);
	
	color = calc_color(a, b, styles[GLine::Background].color);
	p.fillRect(0, 0, cellWidth(), cellHeight(), color);
	
	p.setFont(painter->font());
	//p.translate(-ur.left(), 0);

	// Procedure separation
	if (getFlag(ShowProcedureLimits) && l->proc)
	{
		if (getFlag(BlendedProcedureLimits))
		{
			make_blend(pattern, styles[GLine::Line].color, color, cellHeight());
			p.drawTiledPixmap(0, 0, cache->width(), cache->height(), pattern);
		}
		else
		{
			QBrush brush(styles[GLine::Selection].color, Qt::Dense4Pattern);
			p.fillRect(0, 0, cache->width(), 1, brush);
		}
	}

	p.translate(-ur.left(), 0);

	// Selection background
	if (doc->hasSelection())
	{
		doc->getSelection(&y1, &x1, &y2, &x2);

		if (row >= y1 && row <= y2 && !(row == y2 && x2 == 0))
		{
			if (row > y1 || x1 == 0)
				x1 = 0;
			else
				//x1 *= charWidth;
				x1 = lineWidth(y1, x1);

			if (row < y2)
				x2 = cellWidth() + 1;
			else
				//x2 *= charWidth;
				x2 = lineWidth(y2, x2);

			p.fillRect(x1, 0, x2 - x1, cellHeight(), styles[GLine::Selection].color);
		}
	}

	// Margin
	if (margin && (margin > ur.left()))
	{
		//if (!l->flag)
		//	p.fillRect(0, 0, margin, cellHeight(), color); //styles[GLine::Background].color);

		if (getFlag(ShowModifiedLines) && l->changed)
			p.fillRect(0, 0, margin - 2, cellHeight(), styles[GLine::Highlight].color);
		else //if (getFlag(ShowLineNumbers))
			p.fillRect(0, 0, margin - 2, cellHeight(), styles[GLine::Line].color);

		x1 = 0;

		if (getFlag(ShowLineNumbers))
		{
			int n = row + 1;
			if ((n % 10) == 0)
				p.setPen(styles[GLine::Normal].color);
			else
				p.setPen(styles[GLine::Selection].color);
			p.drawText(x1 + 2, fm.ascent(), QString::number(n).rightJustify(lineNumberLength));
		}
	}
	
	// Breakpoint symbol
	if ((l->flag & (1 << GLine::BreakpointFlag)) && breakpoint && !breakpoint->isNull())
	{
		//p.fillRect(margin - 10, 0, 8, cellHeight(), styles[GLine::Breakpoint].color);
		//updateBreakpoint(styles[GLine::Background].color.rgb(), styles[GLine::Breakpoint].color.rgb());
		p.drawPixmap(margin - (cellHeight() + breakpoint->width()) / 2, (cellHeight() - breakpoint->height()) / 2, *breakpoint);
	}

	// Highlight braces
	if (getFlag(HighlightBraces) && row == ym && x1m >= 0)
	{
		//highlight_text(p, x1m * charWidth + margin, fm.ascent(), l->s.getString().mid(x1m, 1), styles[GLine::Highlight].color);
		//highlight_text(p, x2m * charWidth + margin, fm.ascent(), l->s.getString().mid(x2m, 1), styles[GLine::Highlight].color);
		highlight_text(p, lineWidth(ym, x1m), fm.ascent(), l->s.getString().mid(x1m, 1), styles[GLine::Highlight].color);
		highlight_text(p, lineWidth(ym, x2m), fm.ascent(), l->s.getString().mid(x2m, 1), styles[GLine::Highlight].color);
		/*p.fillRect(x1m * charWidth + margin, 0, charWidth, cellHeight(), styles[GLine::Highlight].color);
		p.fillRect(x2m * charWidth + margin, 0, charWidth, cellHeight(), styles[GLine::Highlight].color);*/
	}

	// Line text
	if (l->s.length())
	{
		if (doc->getHighlightMode() == GDocument::None || (l->modified && row == y && !getFlag(HighlightCurrent)))
		{
			p.setPen(styles[GLine::Normal].color);
			//p.drawText(margin + xmin * charWidth, fm.ascent(), l->s.getString().mid(xmin, lmax));
			p.drawText(lineWidth(row, xmin), fm.ascent(), l->s.getString().mid(xmin, lmax));
		
			i = find_last_non_space(l->s.getString()) + 1;
			if (i >= 0 && i < (xmin + lmax))
				paintDottedSpaces(p, row, i, QMIN(xmin + lmax, (int)l->s.length()) - i);
		}
		/*else if (l->flag)
		{
			p.setPen(styles[GLine::Background].color);
			p.drawText(margin, fm.ascent(), l->s.getString().mid(xmin, lmax));
		}*/
		else
		{
			painting = true;
			doc->colorize(row);
			painting = false;
			paintText(p, l, margin, fm.ascent(), xmin, lmax, row);
		}
	}
	
	// Text cursor
	if (cursor && row == y)
		//p.fillRect(QMIN((int)l->s.length(), x) * charWidth + margin, 0, 1, cellHeight(), styles[GLine::Normal].color);
		p.fillRect(lineWidth(row, QMIN((int)l->s.length(), x)), 0, 1, cellHeight(), styles[GLine::Normal].color);

	// Flash
	if (flashed)
	{
		p.setRasterOp(XorROP);
		p.fillRect(0, 0, visibleWidth(), cellHeight(), Qt::white);
	}

	p.end();

	//if (cache->width() < visibleWidth())
	//	qDebug("cache->width() = %d < visibleWidth() = %d", cache->width(), visibleWidth());
	painter->drawPixmap(ur.left(), 0, *cache, 0, 0, cellWidth(), cellHeight());
}


void GEditor::checkMatching()
{
	static GString look("()[]{}");

	if (y < 0 || y >= (int)numLines())
		return;

	GString str = doc->lines.at(y)->s;
	int len = (int)str.length();

	char c, cr;
	int pos, d, l, bx, by, bx2;
	char s[len];
	bool ignore;
	int i;

	bx = -1;
	bx2 = -1;
	by = -1;
	ignore = false;

	if (len == 0)
		goto __OK;

	pos = -1;

	for (i = 0; i < len; i++)
	{
		c = str.at(i);

		if (ignore)
		{
			if (c == '\\')
			{
				s[i] = c;
				i++;
				s[i] = str.at(i);
				continue;
			}

			if (c == '"')
				ignore = false;
			else
				c = ' ';
		}
		else
		{
			if (c == '"')
				ignore = true;
		}

		s[i] = c;
	}

	c = 0;

	if (x > 0)
	{
		bx2 = x - 1;
		c = s[bx2];
		pos = look.find(c);
	}

	if (pos < 0 && x < len)
	{
		bx2 = x;
		c = s[bx2];
		pos = look.find(c);
	}

	if (c && pos >= 0)
	{
		d = (pos & 1) ? -1 : 1;
		cr = look.at(pos + d);

		pos = bx2;
		l = 0;
		for(;;)
		{
			pos += d;
			if (pos < 0 || pos >= len)
				break;

			if (s[pos] == c)
				l++;
			else if (s[pos] == cr)
			{
				l--;
				if (l < 0)
				{
					bx = pos;
					by = y;
					break;
				}
			}
		}
	}

__OK:

	if (by != ym || bx != x1m || bx2 != x2m)
	{
		if (ym >= 0 && ym != by)
			updateLine(ym);
		ym = by;
		x1m = bx;
		x2m = bx2;
		if (ym >= 0)
			updateLine(ym);
	}
}


bool GEditor::cursorGoto(int ny, int nx, bool mark)
{
	bool setxx;
	bool change = false;

	if (!mark)
	{
		if (doc->hasSelection())
			doc->hideSelection();
	}

	setxx = xx != nx;

	if (ny < 0)
	{
		nx = QMAX(0, nx);
		ny = 0;
	}
	else if (ny > (numLines() - 1))
	{
		ny = numLines() - 1;
		nx = QMIN(nx, lineLength(ny));
	}
	else if (ny == y)
	{
		if (nx < 0 && ny > 0)
		{
			ny--;
			nx = lineLength(ny);
		}
		else if (nx > lineLength(ny) && ny < (numLines() - 1))
		{
			ny++;
			nx = 0;
		}
	}

	if (nx < 0)
		nx = 0;
	else if (nx > lineLength(ny))
		nx = lineLength(ny);

	if (ny != y && getFlag(HighlightCurrent))
		doc->colorize(y);

	if (y != ny || x != nx)
	{
		int oy = y;
		
		if (mark)
		{
			if (!doc->hasSelection(this))
				doc->startSelection(this, y, x);
		}
		
		y = ny;
		x = nx;
		if (setxx)
			xx = x;
		
		updateLine(oy);
		
		if (hasFocus())
			startBlink();
		else
			updateLine(y);
			
		change = true;

		checkMatching();

		ensureCursorVisible();
		
		if (mark)
			doc->endSelection(y, x);

		emit cursorMoved();
	}

		//QTimer::singleShot(30, this, SLOT(ensureCursorVisible()));

	return change;
}

void GEditor::insert(QString text)
{
	doc->eraseSelection();
	doc->insert(y, x, text);
	cursorGoto(doc->yAfter, doc->xAfter, false);
}

void GEditor::cursorLeft(bool shift, bool ctrl)
{
	if (ctrl && x > 0)
	{
		int nx = doc->wordLeft(y, x);
		cursorGoto(y, nx, shift);
	}
	else
		cursorGoto(y, x - 1, shift);
}


void GEditor::cursorRight(bool shift, bool ctrl)
{
	if (ctrl && x < lineLength(y))
	{
		int nx = doc->wordRight(y, x);
		cursorGoto(y, nx, shift);
	}
	else
		cursorGoto(y, x + 1, shift);
}

void GEditor::cursorUp(bool shift, bool ctrl)
{
	if (!ctrl)
	{
		cursorGoto(y - 1, xx, shift);
		return;
	}

	int yy = y;
	for(;;)
	{
		yy--;
		if (yy <= 0)
			break;
		if (doc->lines.at(yy)->proc)
			break;
	}

	cursorGoto(yy, xx, shift);
}


void GEditor::cursorDown(bool shift, bool ctrl)
{
	if (!ctrl)
	{
		cursorGoto(y + 1, xx, shift);
		return;
	}

	int yy = y;
	for(;;)
	{
		yy++;
		if (yy >= numLines())
			break;
		if (doc->lines.at(yy)->proc)
			break;
	}

	cursorGoto(yy, xx, shift);
}

void GEditor::cursorHome(bool shift, bool ctrl)
{
	if (ctrl)
		cursorGoto(0, 0, shift);
	else
	{
		int indent = doc->getIndent(y);
		if (x == indent)
			cursorGoto(y, 0, shift);
		else
			cursorGoto(y, indent, shift);
	}
}


void GEditor::cursorEnd(bool shift, bool ctrl)
{
	if (ctrl)
		cursorGoto(numLines() - 1, lineLength(numLines() - 1), shift);
	else
		cursorGoto(y, lineLength(y), shift);
}

void GEditor::cursorPageUp(bool mark)
{
	int page = visibleHeight() / cellHeight();
	cursorGoto(y - page, 0, mark);
}

void GEditor::cursorPageDown(bool mark)
{
	int page = visibleHeight() / cellHeight();
	cursorGoto(y + page, 0, mark);
}

void GEditor::newLine()
{
	doc->eraseSelection();
	doc->insert(y, x, '\n' + doc->lines.at(y)->s.left(QMIN(x, doc->getIndent(y))));
	cursorGoto(doc->yAfter, doc->xAfter, false);
}

void GEditor::del(bool ctrl)
{
	if (doc->hasSelection())
	{
		doc->eraseSelection();
		return;
	}

	if (x == lineLength(y))
	{
		if (y < (numLines() - 1))
			doc->remove(y, x, y + 1, 0);
	}
	else
	{
		if (ctrl && x < lineLength(y))
		{
			int nx = doc->wordRight(y, x);
			doc->remove(y, x, y, nx);
		}
		else
			doc->remove(y, x, y, x + 1);
	}
}

void GEditor::backspace(bool ctrl)
{
	int indent;
	int yy;
	bool empty;

	if (doc->hasSelection())
	{
		doc->eraseSelection();
		return;
	}

	indent = doc->getIndent(y);

	if (x > 0 && x <= indent)
	{
		yy = y;
		indent = 0;
		while (yy > 0)
		{
			yy--;
			indent = doc->getIndent(yy, &empty);
			if (!empty && x > indent)
				break;
		}
		cursorGoto(y, indent, true);
		del(false);
	}
	else
	{
		if (ctrl && x > 0)
		{
			int nx = doc->wordLeft(y, x);
			doc->remove(y, nx, y, x);
		}
		else
		{
			if (cursorGoto(y, x - 1, false))
				del(false);
		}
	}
}

void GEditor::tab(bool back)
{
	QString ins;
	int y1, x1, y2, x2, yy;
	int indent, i;
	bool empty;
	int tw = doc->getTabWidth();

	if (!doc->hasSelection())
	{
		if (!back)
		{
			ins.fill(' ', tw - (x % tw));
			insert(ins);
		}
		return;
	}

	doc->getSelection(&y1, &x1, &y2, &x2);
	doc->startSelection(this, y1, 0);
	if (x2)
		y2++;
	doc->endSelection(y2, 0);

	indent = 65536;
	for (yy = y1; yy < y2; yy++)
	{
		i = doc->getIndent(yy, &empty);
		if (!empty)
			indent = QMIN(indent, i);
	}

	if (back && indent <= 0)
		return;

	doc->begin();

	if (!back)
	{
		ins.fill(' ', tw - (indent % tw));

		for (yy = y1; yy < y2; yy++)
		{
			doc->insert(yy, 0, ins);
			doc->colorize(yy);
		}
	}
	else
	{
		indent %= tw;
		if (indent == 0)
			indent = tw;

		ins.fill(' ', indent);

		for (yy = y1; yy < y2; yy++)
		{
			if (doc->lines.at(yy)->s.left(indent) == ins)
			{
				doc->remove(yy, 0, yy, indent);
				doc->colorize(yy);
			}
		}
	}

	doc->startSelection(this, y1, 0);
	doc->endSelection(y2, 0);

	doc->end();
}


void GEditor::copy(bool mouse)
{
	if (!doc->hasSelection())
		return;

	QString text = doc->getSelectedText().getString();
	QApplication::clipboard()->setText(text, mouse ? QClipboard::Selection : QClipboard::Clipboard);
}

void GEditor::cut()
{
	copy(false);
	doc->eraseSelection();
}

void GEditor::paste(bool mouse)
{
	QString text;
	QCString subType = "plain";
	uint i;
	QString tab;

	text = QApplication::clipboard()->text(subType, mouse ? QClipboard::Selection : QClipboard::Clipboard);
	if (text.length() == 0)
		return;

	tab.fill(' ', doc->getTabWidth());
	text.replace("\t", tab);

	for (i = 0; i < text.length(); i++)
	{
		if ((text[i] < ' ' || text[i].isSpace()) && text[i] != '\n')
			text[i] = ' ';
	}

	doc->begin();
	insert(text);
	doc->end();
}

void GEditor::undo()
{
	if (!doc->undo())
		cursorGoto(doc->yAfter, doc->xAfter, false);
}

void GEditor::redo()
{
	if (!doc->redo())
		cursorGoto(doc->yAfter, doc->xAfter, false);
}

void GEditor::selectAll()
{
	cursorGoto(0, 0, false);
	cursorGoto(numLines() - 1, lineLength(numLines() - 1), true);
}

void GEditor::keyPressEvent(QKeyEvent *e)
{
	bool shift = e->state() & ShiftButton;
	bool ctrl = e->state() & ControlButton;

	e->accept();

	if (doc->isReadOnly())
	{
		switch (e->key())
		{
			case Key_Left:
				cursorLeft(shift, ctrl); return;
			case Key_Right:
				cursorRight(shift, ctrl); return;
			case Key_Up:
				cursorUp(shift, ctrl); return;
			case Key_Down:
				cursorDown(shift, ctrl); return;
			case Key_Home:
				cursorHome(shift, ctrl); return;
			case Key_End:
				cursorEnd(shift, ctrl); return;
			case Key_Prior:
				cursorPageUp(shift); return;
			case Key_Next:
				cursorPageDown(shift); return;
		}

		if (!ctrl)
			goto __IGNORE;

		switch(e->key())
		{
			case Key_C:
				copy(); return;
			case Key_A:
				selectAll(); return;
		}
	}
	else
	{
		if (e->text().length()
				&& e->key() != Key_Return
				&& e->key() != Key_Enter
				&& e->key() != Key_Delete
				&& e->key() != Key_Backspace
				&& (!e->ascii() || e->ascii() >= 32))
		{
			insert(e->text());
			return;
		}

		switch (e->key())
		{
			case Key_Left:
				cursorLeft(shift, ctrl); return;
			case Key_Right:
				cursorRight(shift, ctrl); return;
			case Key_Up:
				cursorUp(shift, ctrl); return;
			case Key_Down:
				cursorDown(shift, ctrl); return;
			case Key_Enter: case Key_Return:
				newLine(); return;
			case Key_Home:
				cursorHome(shift, ctrl); return;
			case Key_End:
				cursorEnd(shift, ctrl); return;
			case Key_Backspace:
				backspace(ctrl); return;
			case Key_Delete:
				del(ctrl); return;
			case Key_Prior:
				cursorPageUp(shift); return;
			case Key_Next:
				cursorPageDown(shift); return;
			case Key_Tab:
				tab(false); return;
			case Key_BackTab:
				tab(true); return;
		}

		if (!ctrl)
			goto __IGNORE;

		switch(e->key())
		{
			case Key_C:
				copy(); return;
			case Key_X:
				cut(); return;
			case Key_V:
				paste(); return;
			case Key_Z:
				undo(); return;
			case Key_Y:
				redo(); return;
			case Key_A:
				selectAll(); return;
		}
	}

__IGNORE:

	e->ignore();
}


int GEditor::posToLine(int py) const
{
	int ny;

	ny = rowAt(contentsY() + py);
	if (ny < 0)
		ny = 0;
	else if (ny >= numLines())
		ny = numLines() - 1;
		
	return ny;
}

void GEditor::posToCursor(int px, int py, int *y, int *x) const
{
	int nx, ny;

	ny = posToLine(py);
	
	//nx = (contentsX() + px - margin) / charWidth;
	//nx = QMAX(0, QMIN(nx, lineLength(ny)));
	nx = posToColumn(ny, px);

	*y = ny;
	*x = nx;
}

void GEditor::cursorToPos(int y, int x, int *px, int *py) const
{
	int npx, npy;

	npy = y * cellHeight() - contentsY();
	//npx = x * charWidth - contentsX() + margin;
	npx = lineWidth(y, x) - contentsX();

	*py = npy;
	*px = npx;
}

bool GEditor::updateCursor()
{
	if (contentsX() + lastx >= margin)
	{
		viewport()->setCursor(Qt::IbeamCursor);
		return false;
	}
	else
	{
		viewport()->setCursor(Qt::ArrowCursor);  
		return true;
	}
}

void GEditor::mousePressEvent(QMouseEvent *e)
{
	bool shift = e->state() & ShiftButton;
	int nx, ny;
	//stopAutoScroll();
	//d->dnd_startpos = e->pos();

	if (e->button() != LeftButton && e->button() != MidButton)
		return;

	posToCursor(e->pos().x(), e->pos().y(), &ny, &nx);
	
	lastx = e->pos().x();
	left = updateCursor();
	
	if (!left)
		cursorGoto(ny, nx, shift);
}

void GEditor::mouseMoveEvent(QMouseEvent *e)
{
	if ((e->state() & Qt::MouseButtonMask) == LeftButton)
	{
		if (left)
		{
			//ly = posToLine(e->pos().y());
			if (!scrollTimer->isActive())
				cursorGoto(posToLine(e->pos().y()), 0, false);
		}
		
		if (!scrollTimer->isActive())
		{
			stopBlink();
			scrollTimer->start(25, false);
		}
	}

	lastx = e->pos().x();
	left = updateCursor();
}

void GEditor::mouseDoubleClickEvent(QMouseEvent *e)
{
	if (left)
	{
		emit marginDoubleClicked(posToLine(e->pos().y()));
		return;
	}
	
	if (e->button() == LeftButton && !(e->state() & ShiftButton))
	{
		int xl, xr;

		xl = doc->wordLeft(y, x, true);
		xr = doc->wordRight(y, x, true);

		if (xr > xl)
		{
			doc->hideSelection();
			cursorGoto(y, xl, false);
			cursorGoto(y, xr, true);
			copy(true);
		}
	}
}

void GEditor::mouseReleaseEvent(QMouseEvent *e)
{
	if (scrollTimer->isActive())
	{
		scrollTimer->stop();
		startBlink();
		copy(true);
		return;
	}

	if (left)
		emit marginClicked(posToLine(e->pos().y()));

	if (e->button() == MidButton)
		paste(true);
}


void GEditor::resizeEvent(QResizeEvent *e)
{
	QGridView::resizeEvent(e);
	baptizeVisible();
	updateWidth(-1);
}

bool GEditor::isCursorVisible() const
{
	int px, py;
	
	cursorToPos(y, x, &px, &py);
	
	return !(px < margin || px >= (visibleWidth() - 2) || py < 0 || py >= (visibleHeight() - cellHeight() - 1));
}

void GEditor::ensureCursorVisible()
{
	if (!isUpdatesEnabled())
		return;
	
	if (!isCursorVisible())
	{
		qApp->sendPostedEvents(viewport(), QEvent::Paint);
	
		if (center)
			//ensureVisible(x * charWidth, y * cellHeight() + cellHeight() / 2, margin + 2, visibleHeight() / 2);
			ensureVisible(lineWidth(y, x) + getCharWidth() / 2, y * cellHeight() + cellHeight() / 2, margin + 2, visibleHeight() / 2);
		else
			//ensureVisible(x * charWidth, y * cellHeight() + cellHeight() / 2, margin + 2, cellHeight());
			ensureVisible(lineWidth(y, x) + getCharWidth() / 2, y * cellHeight() + cellHeight() / 2, margin + 2, cellHeight());
	}
	center = false;
}

void GEditor::startBlink()
{
	blinkTimer->start(QApplication::cursorFlashTime() / 2, false);
	cursor = true;
	updateLine(y);
}

void GEditor::stopBlink()
{
	blinkTimer->stop();
	cursor = false;
	updateLine(y);
}

void GEditor::blinkTimerTimeout()
{
	cursor = !cursor;
	updateLine(y);
}

void GEditor::focusInEvent(QFocusEvent *e)
{
	startBlink();
	ensureCursorVisible();
	QGridView::focusInEvent(e);
}

void GEditor::focusOutEvent(QFocusEvent *e)
{
	stopBlink();
	QGridView::focusOutEvent(e);
}

void GEditor::scrollTimerTimeout()
{
	int ny, nx;
	QPoint pos = mapFromGlobal(QCursor::pos());

	posToCursor(pos.x(), pos.y(), &ny, &nx);
	cursorGoto(ny, nx, true);
}

void GEditor::setStyle(int index, GHighlightStyle *style)
{
	if (index < 0 || index >= GLine::NUM_STATE)
		return;

	styles[index] = *style;

	if (index == 0)
	{
		setPaletteBackgroundColor(style->color);
		redrawContents();
	}
	else
		updateContents();
}

void GEditor::setNumRows(int n)
{
	QGridView::setNumRows(n);
	QGridView::updateScrollBars();
	if (contentsHeight() < visibleHeight())
		repaintContents(contentsX(), contentsHeight(), visibleWidth(), visibleHeight() - contentsHeight() + contentsX(), true);
	//if (contentsHeight() < visibleHeight())
		//repaintContents(true);
}

void GEditor::getStyle(int index, GHighlightStyle *style)
{
	if (index < 0 || index >= GLine::NUM_STATE)
	{
		*style = styles[GLine::Background];
		return;
	}

	*style = styles[index];
}

void GEditor::setFlag(int f, bool v)
{
	if (v)
		flags |= (1 << f);
	else
		flags &= ~(1 << f);

	updateMargin();
	updateContents();
}

void GEditor::updateMargin()
{
	int charWidth = getCharWidth();
	int nm, lnl = 0;
	
	nm = 2;
	
	if (doc->getHighlightMode() != GDocument::None)
	{
		if (breakpoint && !breakpoint->isNull())
			nm += breakpoint->width();
		else
			nm += 8;
	}
	
	if (getFlag(ShowLineNumbers))
	{
		int cnt = numLines();

		while (cnt)
		{
			nm += charWidth;
			lnl++;
			cnt /= 10;
		}
		
		nm += 4;
	}
	
	if (getFlag(ShowModifiedLines) && nm < 6)
		nm = 6;

	if (nm != margin)
	{
		margin = nm;
		lineNumberLength = lnl;
		updateContents();
		updateCursor();
	}
}

void GEditor::docTextChangedLater()
{
	emit textChanged();
}

void GEditor::docTextChanged()
{
	if (painting)
		QTimer::singleShot(0, this, SLOT(docTextChangedLater()));
	else
		docTextChangedLater();
}


void GEditor::baptizeVisible()
{
	doc->baptizeUntil(lastVisibleRow());
}

void GEditor::baptizeVisible(int x, int y)
{
	doc->baptizeUntil(lastVisibleRow(y));
}


void GEditor::imStartEvent( QIMEvent *e )
{
}

void GEditor::imComposeEvent( QIMEvent *e )
{
}

void GEditor::imEndEvent( QIMEvent *e )
{
	insert(e->text());
}

bool GEditor::focusNextPrevChild(bool)
{
	return false;
}

static bool is_power_of_ten(int n)
{
	for(;;)
	{
		if (n % 10)
			return false;
		n /= 10;
		if (n == 1)
			return true;
	}
}

void GEditor::lineInserted(int y)
{
	if (largestLine >= y)
		largestLine++;
	
	if (getFlag(ShowLineNumbers) && is_power_of_ten(numLines()))
		updateMargin();
}

void GEditor::lineRemoved(int y)
{
	if (largestLine == y)
		updateWidth(y);
	else if (largestLine > y)
		largestLine--;
	
	if (getFlag(ShowLineNumbers) && is_power_of_ten(numLines() + 1))
		updateMargin();
}

void GEditor::flash()
{
	if (flashed)
		return;
		
	flashed = true;
	setPaletteBackgroundColor(QColor(QRgb(styles[GLine::Background].color.rgb() ^ 0xFFFFFF)));
	redrawContents();
	QTimer::singleShot(50, this, SLOT(unflash()));
}

void GEditor::unflash()
{
	flashed = false;
	setPaletteBackgroundColor(styles[GLine::Background].color);
	redrawContents();
}
