//
// ksyntaxmle.C
//
// written by Michael Riedel <Michael.Riedel@gmx.de>
//

#include <qpainter.h>
#include <qscrbar.h>
#include <qclipbrd.h>
#include <qpixmap.h>
#include <qpmcache.h>
#include <qpoint.h>
#include <kapp.h>

#include "EditorSectionName.h"
#include "FindReplace.h"
#include "KColorMLE.h"
#include "StateCache.h"
#include "KColorMLE.moc"


#define BORDER 3		// pixel
#define SCROLL_TIME 25 	// ms



//////////////////////////////////////////////
void Marker::setMarkBase(int posy, int posx)
{ 
	int ay = AnchorY, dy = DragY;
	AnchorY = DragY = posy; AnchorX = DragX = posx; 
//	warning("setMarkBase(%d, %d);", posy, posx);
	
	Edit->updateMarkedLines(ay, dy);
}
//////////////////////////////////////////////



//////////////////////////////////////////////
void Marker::extendMark(int posy, int posx)
{ 
	int oldDy = DragY;
	DragY = posy; DragX = posx; 
//	warning("extendMark(%d, %d);", posy, posx);		

	Edit->updateMarkedLines(oldDy, DragY);
}
//////////////////////////////////////////////



//////////////////////////////////////////////
QRegExp reWord("[a-zA-Z_][a-zA-Z0-9_]*");  	 // configurable!!
QRegExp reWordChars("[a-zA-Z0-9_]");            // configurable!!
//////////////////////////////////////////////



//////////////////////////////////////////////
inline int tabWidth(const QFontMetrics &fm)
{
	return fm.width('x');
}
//////////////////////////////////////////////



//////////////////////////////////////////////
KSyntaxMultiLineEdit::KSyntaxMultiLineEdit(const QString& mode, QWidget *parent, const char *name)
	: QMultiLineEdit(parent, name),
	  edSettings(EditorSectionName), 
	  mark(this)
{
	colorizer = 0;
	stateCache = 0;
	Mode = mode;
    setDirty(false);
	
		// make settings work here:
	updateFromSettings();

		// cursor off at startup:
	smle_cursorOn = false;
	blinkTimerId  = 0;

		// make the child dialog pointers null:
	replace_dialog = 0;
	srchdialog     = 0;
	gotodialog     = 0;
	last_search    = NONE;
	actuallyMarking = false;
	scrollingTimer  = 0;

		// internal drawing variables:
	charsDrawn = 0;
	drawXPos = 0;
	
		// connect internal signals/slots:
	connect(this, SIGNAL(textChanged()), this, SLOT(textChangedSlot()));
}
//////////////////////////////////////////////



//////////////////////////////////////////////
KSyntaxMultiLineEdit::~KSyntaxMultiLineEdit()
{
}
//////////////////////////////////////////////



//////////////////////////////////////////////
void KSyntaxMultiLineEdit::updateFromSettings()
{
	if(edSettings.font() != font())
		setFont(edSettings.font());

		// needed only in case the use color flag or the
		// mode changed:
	initColorHandler(Mode);
	repaint();
}
//////////////////////////////////////////////



//////////////////////////////////////////////
void KSyntaxMultiLineEdit::initColorHandler(const char* rule_file)
{
		// delete old colorizer
	if(colorizer)
		delete colorizer;
		
		// create a new one with either a rule file for coloring
		// a null filename to edit a text without coloring:
	if(edSettings.useColors())
		colorizer = new Colorizer(rule_file);
	else
		colorizer = new Colorizer(0);
		
		// a new colorizer requires a new state cache:
	initStateCache();
}
//////////////////////////////////////////////



//////////////////////////////////////////////
void KSyntaxMultiLineEdit::setNewMode(const QString& mode)
{
	Mode = mode;
	initColorHandler(Mode);
	repaint();
}
//////////////////////////////////////////////



//////////////////////////////////////////////
void KSyntaxMultiLineEdit::initStateCache()
{
	if(stateCache)
		delete stateCache;
	stateCache = new StateCache(colorizer, this);
	stateCache->parse();
}
//////////////////////////////////////////////



//////////////////////////////////////////////
void KSyntaxMultiLineEdit::deleteStateCache()
{
	if(stateCache)
	{
		delete stateCache;
		stateCache = 0;
	}
}
//////////////////////////////////////////////



//////////////////////////////////////////////
void KSyntaxMultiLineEdit::setFontInfo(QPainter& p, int state)
{
	const FontInfo& info = colorizer->getStateInfo(state);
	info.setDrawingAttrs(p, font());
}
//////////////////////////////////////////////



//////////////////////////////////////////////
static QPixmap *getCacheBuffer( QSize sz )
{
	QString name;
	name.sprintf("%d_%d", sz.width(), sz.height());
	
	QPixmap* pm = QPixmapCache::find(name);
	if(!pm)
	{
		pm = new QPixmap(sz);
		QPixmapCache::insert(name, pm);
	}
	return pm;
}
//////////////////////////////////////////////



//////////////////////////////////////////////
void KSyntaxMultiLineEdit::paintCell( QPainter *p2, int row, int col)
{
	if(!colorizer || !stateCache)
	{
		QMultiLineEdit::paintCell(p2, row, col);
		return;
	}

    QColorGroup  g = colorGroup();
    QFontMetrics fm(p2->font());
    QString* s = getString(row);

    QRect updateR   = cellUpdateRect();
    QPixmap *buffer = getCacheBuffer(updateR.size());
    ASSERT(buffer);
    buffer->fill(g.base());

    static QPainter* p = new QPainter;
    if(!p->begin(buffer))
    {
    	warning("QPainter::begin() failed!");
    	return;
    }
    
    p->setFont(p2->font());
    p->translate( -updateR.left(), -updateR.top() );
    p->setTabStops(tabStopDist(fm));

    int yPos = (fm.leading() >> 1) - 1;
	int markAX, markAY, markDX, markDY;
	bool hasMark = getMarkedRegion(&markAY, &markAX, &markDY, &markDX);
	int markX1 = 0, markX2 = 0;				// in x-coordinate pixels
	bool thisRowIsMarked = false;
    if(hasMark) 
    {
		int markBeginX, markBeginY;
		int markEndX, markEndY;
		if(markAY < markDY) 
		{
			markBeginX = markAX;
			markBeginY = markAY;
			markEndX   = markDX;
			markEndY   = markDY;
		} 
		else 
		{
		    markBeginX = markDX;
		 	markBeginY = markDY;
		    markEndX   = markAX;
		    markEndY   = markAY;
		}
		if(markAY == markDY && markBeginX > markEndX) 
		{
			int tmp    = markBeginX;
	 		markBeginX = markEndX;
			markEndX   = tmp;
		}
		if(row >= markBeginY && row <= markEndY) 
		{
			thisRowIsMarked = true;
		    if(row == markBeginY) 
	    	{
				markX1 = markBeginX;
				if(row == markEndY)			// both marks on same row
				    markX2 = markEndX;	
				else
		    		markX2 = s->length();   // mark til end of line
		    } 
		    else 
	    	{
				if(row == markEndY) 
				{
			    	markX1 = 0;
				    markX2 = markEndX;
				} 
				else 
				{
		    		markX1 = 0;				// whole line is marked
			    	markX2 = s->length();	// whole line is marked
				}
		    }
		}
	}
	
	charsDrawn = 0;			// helper vars for own drawText version!
	drawXPos = BORDER;
	int cWidth = cellWidth(row) - BORDER, 
		cHeight = cellHeight(row);
	unsigned int lastmatch = 0;
	int actualpos = BORDER;	// actual x drawing position
	int prevState = 0;
	
	StateCacheLineElem* lelem = stateCache->getLineElem(row);
	if(lelem)
	{	
		QList<StateCacheElem>& li = lelem->getUnitList();
		prevState = lelem->prevLineEndState();
	
		StateCacheElem* elem = li.first();
		while(elem)
		{
		    	QString s1 = s->mid(lastmatch, elem->pos() - lastmatch);
			QString s2 = s->mid(elem->pos(), elem->len()); 

			if(!s1.isEmpty())
			{
				setFontInfo(*p, prevState);
   				drawText(p, actualpos,  yPos, cWidth, cHeight, ExpandTabs, s1);
				actualpos = drawXPos;
			}

			if(!s2.isEmpty())
			{
				setFontInfo(*p, elem->state());
	   			drawText(p, actualpos,  yPos, cWidth, cHeight, ExpandTabs, s2);
				actualpos = drawXPos;
			}
			lastmatch = elem->pos() + elem->len();
			prevState = elem->afterState();
		
				// take the next syntax unit:
			elem = li.next();
		}
	}
	
    if(lastmatch < s->length())
    {
		setFontInfo(*p, prevState);
		drawText(p, actualpos, yPos, cWidth, cHeight,
				 ExpandTabs, s->right(s->length() - lastmatch));
    }

	
		// make mark visible
    if(thisRowIsMarked) 
    {
		int sLength = s->length();
		int xpos1   =  BORDER + textWidthWithTabs(fm, s->data(), markX1);
		int xpos2   =  BORDER + textWidthWithTabs(fm, s->data(), markX2) - 1;
		int fillxpos1 = xpos1;
		int fillxpos2 = xpos2;
		if(markX1 == 0)
	    	fillxpos1 -= 2;
		if(markX2 == sLength)
		    fillxpos2 += 3;
		    
//		p->setClipping(true);
//		p->setClipRect(fillxpos1 - updateR.left(), 0, fillxpos2 - fillxpos1, cellHeight(row));
		p->setRasterOp(XorROP);
		p->fillRect(fillxpos1, 0, fillxpos2 - fillxpos1, cellHeight(row), white/*g.text()*/);
//		p->setClipping(false);
    }

		// paint cursor:
	int cx, cy;
	getCursorPosition(&cy, &cx);
    if(row == cy && smle_cursorOn && !isReadOnly()) 
    {
		int cursorPos = QMIN((int)s->length(), cx);
		int cXPos   = BORDER + textWidthWithTabs(fm, *s, cursorPos) - 1;
		int cYPos   = 0;
		if(hasFocus()) 
		{
			p->setPen(black);
		    p->drawLine(cXPos - 2, cYPos, cXPos + 2, cYPos );
	    	p->drawLine(cXPos    , cYPos, cXPos    , cYPos + fm.height() - 2);
		    p->drawLine(cXPos - 2, cYPos + fm.height() - 2, cXPos + 2, cYPos + fm.height() - 2);
		}
    }
    p->end();
	    
    p2->drawPixmap(updateR.left(), updateR.top(), *buffer);
}
//////////////////////////////////////////////



#define _X_ 3

//////////////////////////////////////////////
void KSyntaxMultiLineEdit::drawText(QPainter* p, int x, int y, int w, int h,
									int tf, const char* str, int len = -1)
{
	QFontMetrics fm(font());
	int tw = tabWidth(fm);

	if(len == -1)
		len = strlen(str);
		
	char* tab = strchr(str, '\t');
	if(!tab)
	{
		p->drawText(x, y, w, h, tf, str, len);
		charsDrawn += len;
		drawXPos += len * tw;
		return;
	}
	else
	{
		const char* lastTab = str;
		bool noText = (tab == str);
		int drawn = 0;
		do
		{
			if(!noText)
			{
				p->drawText(drawXPos, y, w, h, tf, str + drawn, tab - lastTab);
				drawn += tab - lastTab;
				charsDrawn += tab - lastTab;
				drawXPos += (tab - lastTab) * tw;
			}
				
			int ts = edSettings.tabSize() - (charsDrawn % edSettings.tabSize());
			if(ts == 0)
				ts = edSettings.tabSize();
			int newDrawXPos = drawXPos + ts * tw;
			
				// ´draw´ tabs (for debugging purpose):
			if(edSettings.tabsVisible())
			{
				QPen pen = p->pen();
				p->setPen(lightGray);
				p->drawLine(drawXPos + _X_, y + (fm.height() >> 1) - 2, 
							drawXPos + _X_, y + (fm.height() >> 1) + 2);
				p->drawLine(newDrawXPos - _X_, y + (fm.height() >> 1) - 2, 
							newDrawXPos - _X_, y + (fm.height() >> 1) + 2);
				p->drawLine(drawXPos + _X_, y + (fm.height() >> 1), 
							newDrawXPos - _X_, y + (fm.height() >> 1));
				p->setPen(pen);
			}

				// add tab-size to charsDrawn/drawXPos:
			charsDrawn += ts;
			drawXPos = newDrawXPos;
			drawn++;	// a tab is one char

			lastTab = tab + 1;
			tab = strchr(lastTab, '\t');
			noText = (tab == lastTab);			// don´t call p->drawText(...) for tabs
												
		}
		while(tab);
		
		if(drawn < len)
		{
			p->drawText(drawXPos, y, w, h, tf, str + drawn, len - drawn + 1);
			charsDrawn += len - drawn;
			drawXPos += (len - drawn) * tw;
		}
	}
}
//////////////////////////////////////////////



//////////////////////////////////////////////
void KSyntaxMultiLineEdit::mousePressEvent(QMouseEvent* me)
{
		// bring modeless dialogs to the top:
	if(srchdialog)
		if(srchdialog->isVisible())
			srchdialog->raise();

	if(replace_dialog)
		if(replace_dialog->isVisible())
			replace_dialog->raise();

    QFontMetrics fm(font());

    int cy = findRow(me->pos().y());
    if (cy < 0)
		cy = lastRowVisible();
    cy = QMIN((int)numLines() - 1, cy);
    int cx = xPosToCursorPos(*getString(cy), fm,
							 me->pos().x() - BORDER + xOffset(), 
							 cellWidth() - 2 * BORDER);

		// calc mark state:
	bool extendMarking = false;
	
	if(mark.isSet() && me->state() & ShiftButton)
		extendMarking = true;


		// set new cursor position, put action into undo list:
	int x, y;
	getCursorPosition(&y, &x);
	QPoint p1(x, y), p2(cx, cy);
	Action* a = new CursorPlacementAction(this, p1, p2, extendMarking, !extendMarking);
	UndoRedoList.AddAction(a);

	makeCursorVisibleImmediatly();
//	emit cursorPosChanged(); -> already in CursorPlacementAction
	
	actuallyMarking = true;
	oldMark = cy;
}
//////////////////////////////////////////////



//////////////////////////////////////////////
void KSyntaxMultiLineEdit::mouseMoveEvent(QMouseEvent* me)
{
	if(actuallyMarking)
	{
	    if(rect().contains(me->pos())) 
	    {
			if(scrollingTimer) 
			{
			    killTimer(scrollingTimer);
			    scrollingTimer = 0;
			}
	    } 
	    else
	    {
	    	if(!scrollingTimer) 
				scrollingTimer = startTimer(SCROLL_TIME);

	    	return; // timerEvent does the marking!
		}

	    QFontMetrics fm(font());

	    int cy = findRow(me->pos().y());
	    if (cy < 0)
	    	return;
	    cy = QMIN((int)numLines() - 1, cy);
	    int cx = xPosToCursorPos(*getString(cy), fm,
								 me->pos().x() - BORDER + xOffset(), 
								 cellWidth() - 2 * BORDER);

			// set new mark:						 
		setCursorPosition(cy, cx, true);

			// if no change in the last marked line occurs:		
		if(oldMark == cy)
			return;
			
			// update selected lines to ensure the entire marked 
			// region is shown inverted:
		updateMarkedLines(oldMark, cy);
/*		int startLine, endLine;
		if(cy < oldMark)
		{	
			startLine = cy;
			endLine   = oldMark; 
		}
		else
		{	
			startLine = oldMark;
			endLine   = cy; 
		}
		for(int line = startLine; line <= endLine; line++)
			updateCell(line, 0, false);
*/
		oldMark = cy;			
		emit cursorPosChanged();
	}
}
//////////////////////////////////////////////



//////////////////////////////////////////////
void KSyntaxMultiLineEdit::mouseReleaseEvent(QMouseEvent*)
{
	if(actuallyMarking)
	{
		actuallyMarking = false;
		killTimer(scrollingTimer);
		scrollingTimer = 0;
		
			// set new cursor position, put action into undo list:
//		int x, y;
//		getCursorPosition(&y, &x);
//		QPoint p1(x, y), r2(cx, cy);
//		Action* a = new CursorPlacementAction(this, p1, p2, me->state() & ShiftButton);
//		UndoRedoList.AddAction(a);
	}
}
//////////////////////////////////////////////



//////////////////////////////////////////////
void KSyntaxMultiLineEdit::mouseDoubleClickEvent(QMouseEvent*)
{
	int cx, cy;
	getCursorPosition(&cy, &cx);
	smle_markWord(cx, cy);
	updateCell(cy, 0, false);

	emit cursorPosChanged();
}
//////////////////////////////////////////////



//////////////////////////////////////////////
void KSyntaxMultiLineEdit::timerEvent(QTimerEvent *t)
{
		// horrible hack:
		// we assume the first occurring timer event
		// to be for cursor blinking; this should be
		// true, since the other timer used is for 
		// scrolling (and it works as long as Troll Tech
		// doesn´t create a timer that fires before the 
		// blinking timer):
	if(!blinkTimerId)
		blinkTimerId = t->timerId();

	if(hasFocus() && t->timerId() == blinkTimerId) 
		smle_cursorOn = !smle_cursorOn;
		
	if(t->timerId() == scrollingTimer)
	{
		QPoint p = mapFromGlobal(QCursor::pos());
		if(p.y() < 0)
		    cursorUp(true);
		else if(p.y() > height())
		    cursorDown(true);
		else if(p.x() < 0)
		    cursorLeft(true, false);
		else if(p.x() > width())
		    cursorRight(true, false);
	}
	else
		QMultiLineEdit::timerEvent(t);
}
//////////////////////////////////////////////



//////////////////////////////////////////////
void KSyntaxMultiLineEdit::wordLeft(bool mark, bool)
{
	int posx, posy;
	int oldstart = -1;
	bool cursorReached = false;
	getCursorPosition(&posy, &posx);

	while(oldstart == -1 && posy >= 0)
	{
	    QString* s = getString(posy);
    	int len = 0;
	    register int start = 0;

	    while(start != -1 && !cursorReached)
    	{
	    	start = reWord.match(s->data(), start, &len);
		    if(start != -1)
		    {
	    		if(start >= posx)
	    		{
	    			cursorReached = true; // this word is of no interest, use previous one
	    			break;
	    		}
		    	oldstart = start;
		    	start += len;
	    	}
	    }
	    
	    if(oldstart == -1)
	    {
		    	// try next line above, from the very end:
	    	posy--; 
	    	if(posy >= 0)
			    posx = getString(posy)->length();
		    cursorReached = false;
	    }
	}
	

	if(oldstart != -1)
	{
		int r, c;
		getCursorPosition(&r, &c);
		QPoint p1(c, r), p2(oldstart, posy);
		Action* a = new CursorPlacementAction(this, p1, p2, mark);
		UndoRedoList.AddAction(a);

			// now that the action ist executed, we can update the cells 
			// that are marked additionally:
		for(; r > posy; r--)
			updateCell(r, 0, false);
	}
	
	makeCursorVisibleImmediatly();
}
//////////////////////////////////////////////



//////////////////////////////////////////////
void KSyntaxMultiLineEdit::wordRight(bool mark, bool)
{
	int posx, posy;
	bool wordFound = false;
	getCursorPosition(&posy, &posx);
    register int start = posx, start2;
   	int len = 0;
   	bool firstLine = true;
   	

	while(!wordFound && posy < numLines())
	{
	    QString* s = getString(posy);
    	start2 = reWord.match(s->data(), start, &len);

	    if(start2 != -1)
	    {
	    	if(start == start2 && firstLine)
	    	{
	    		start = start2 + len;
	    		continue;
	    	}
	    	else
		    {
		    	start = start2;
    			wordFound = true;
    			break;
	    	}
	    }

	    	// try next line below:
    	posy++; 
	    start = 0;
	    firstLine = false;
	}
	

	if(wordFound)
	{
		int r, c;
		getCursorPosition(&r, &c);
		QPoint p1(c, r), p2(start, posy);

		Action* a = new CursorPlacementAction(this, p1, p2, mark);
		UndoRedoList.AddAction(a);

			// now that the action ist executed, we can update the cells 
			// that are marked additionally:
		for(; r < posy; r++)
			updateCell(r, 0, false);
	}
	
	makeCursorVisibleImmediatly();
}
//////////////////////////////////////////////



//////////////////////////////////////////////
bool KSyntaxMultiLineEdit::getMarkedRegion(int* ay, int* ax, int* dy, int* dx)
{
	if(ay)
		*ay = mark.anchorY();
	if(ax)
		*ax = mark.anchorX();
	if(dy)
		*dy = mark.dragY();
	if(dx)
		*dx = mark.dragX();
		
	return mark.isSet();
}
//////////////////////////////////////////////



//////////////////////////////////////////////
void KSyntaxMultiLineEdit::setMark(int startx, int starty, int endx, int endy)
{
	warning("setnewMark l:%d c:%d to l:%d c:%d", starty, startx, endy, endx);
	mark.setMarkBase(starty, startx);
	mark.extendMark(endy, endx);
	
/*	
	setCursorPosition(starty, startx, false);
	setCursorPosition(endy, endx, true);
*/	
}
//////////////////////////////////////////////



//////////////////////////////////////////////
void KSyntaxMultiLineEdit::extendMark(int posy, int posx, bool c)
{
	if(mark.isSet())
	    if(mark.dragX() == posx && mark.dragY() == posy /*&& cursorX == posx && cursorY == posy*/)
			return;

	mark.extendMark(posy, posx);

    if(c)
		copyText();
}
//////////////////////////////////////////////



//////////////////////////////////////////////
QString KSyntaxMultiLineEdit::markedText() const
{
    int x1, x2, y1, y2;
    if(!mark.getMark(x1, y1, x2, y2))
        return "";
    
    if(y1 == y2)    // one line
    {
        QString* s = getString(y1);
        return s->mid(x1, x2 - x1);
    }
    else            // multiline
    {
        QString *s1 = getString(y1),
                *s2 = getString(y2),
                result;
                
        result = s1->mid(x1, s1->length() - x1);
        for(int i = y1 + 1; i < y2; i++)
        {
            result += '\n';
            result += *getString(i);
        }
        result += '\n';
        
        if(x2)
            result += s2->left(x2);
       
        return result;
    }
}
//////////////////////////////////////////////



//////////////////////////////////////////////
void KSyntaxMultiLineEdit::setCursorPosition(int row, int col, bool markText)
{
		// a bug in QMultilineEdit leads to a strange behaviour of 
		// the text marking function: if the last marked region of 
		// text and a new marking pos have the same end position,
		// no marking is done!
		// we therefore implement our own text marking routines.
		

	if(markText)
		extendMark(row, col);
	else
//	{
/*		if(mark.isSet())
			mark.unset();
		else
*/		
		mark.setMarkBase(row, col);
//	}
		
		// the marking feature of QMultiLineEdit will never be used:
	QMultiLineEdit::setCursorPosition(row, col, false);
	makeCursorVisibleImmediatly();
}
//////////////////////////////////////////////



//////////////////////////////////////////////
void KSyntaxMultiLineEdit::cursorLeft(bool markText, bool wrap)
{
	QMultiLineEdit::cursorLeft(false, wrap);

	int cursorY, cursorX;
	getCursorPosition(&cursorY, &cursorX);

	if(markText)
		mark.extendMark(cursorY, cursorX);
	else
	    mark.setMarkBase(cursorY, cursorX);
}
//////////////////////////////////////////////



//////////////////////////////////////////////
void KSyntaxMultiLineEdit::cursorRight(bool markText, bool wrap)
{
	QMultiLineEdit::cursorRight(false, wrap);

	int cursorY, cursorX;
	getCursorPosition(&cursorY, &cursorX);

	if(markText)
		mark.extendMark(cursorY, cursorX);
	else
	    mark.setMarkBase(cursorY, cursorX);
}
//////////////////////////////////////////////



//////////////////////////////////////////////
void KSyntaxMultiLineEdit::cursorDown(bool markText)
{
	int cursorY, cursorX, cursorY2;
	getCursorPosition(&cursorY, &cursorX);
	int cPos = cursor2View(cursorX, cursorY);

	setAutoUpdate(false);
	QMultiLineEdit::cursorDown(false);
	setAutoUpdate(true);
	
	getCursorPosition(&cursorY2, 0);
	cursorX = view2Cursor(cPos, cursorY2);
	
	setCursorPosition(cursorY2, cursorX, markText);
}
//////////////////////////////////////////////



//////////////////////////////////////////////
void KSyntaxMultiLineEdit::cursorUp(bool markText)
{
	int cursorY, cursorX, cursorY2;
	getCursorPosition(&cursorY, &cursorX);
	int cPos = cursor2View(cursorX, cursorY);

	setAutoUpdate(false);
	QMultiLineEdit::cursorUp(false);
	setAutoUpdate(true);
	
	getCursorPosition(&cursorY2, 0);
	cursorX = view2Cursor(cPos, cursorY2);
	
	setCursorPosition(cursorY2, cursorX, markText);
}
//////////////////////////////////////////////



//////////////////////////////////////////////
int KSyntaxMultiLineEdit::view2Cursor(int x, int y)
{
    QString *s = getString(y);
    if(!s) 
    	return 0;
    QFontMetrics fm(font());
    int ix = xPosToCursorPos(*s, fm, x - BORDER, cellWidth() - (BORDER<<1));
    return ix;
}
//////////////////////////////////////////////



//////////////////////////////////////////////
int KSyntaxMultiLineEdit::cursor2View(int ix, int y)
{
    QString *s = getString(y);
    ix = QMIN((int)s->length(), ix);
    QFontMetrics fm(font());
    return BORDER + textWidthWithTabs(fm, *s, ix) - 1;
}
//////////////////////////////////////////////



//////////////////////////////////////////////
void KSyntaxMultiLineEdit::smle_markWord(int posx, int posy)
{
    QString* s = getString(posy);
    int i = posx - 1;

    int len;
    while(i >= 0 && reWordChars.match(s->data(), i, &len) == i)
		i--;
    i++;
    int startx = i;

    int lim = s->length();
    i = posx;
    while(i < lim && reWordChars.match(s->data(), i, &len) == i)
		i++;
		
	setMark(startx, posy, i, posy);	
//	warning("setmark %d, %d, line %d", startx, i, posy);
	if(mark.isSet())
		copyText();
}
//////////////////////////////////////////////



//////////////////////////////////////////////
void KSyntaxMultiLineEdit::pageUp(bool markText)
{
	int x, y, x2, y2;
	getCursorPosition(&y, &x);
	QMultiLineEdit::pageUp(false);
	getCursorPosition(&y2, &x2);
	
	QPoint p1(x, y), p2(x2, y2);
	Action* a = new CursorPlacementAction(this, p1, p2, markText);
	UndoRedoList.AddAction(a, false);	// action already done in QMLE::pageUp()
	emit cursorPosChanged();			// since it was not exec'd

	if(markText)
		mark.extendMark(y2, x2);
	else
	    mark.setMarkBase(y2, x2);
}
//////////////////////////////////////////////



//////////////////////////////////////////////
void KSyntaxMultiLineEdit::pageDown(bool markText)
{
	int x, y, x2, y2;
	getCursorPosition(&y, &x);
	if(y >= numLines() - 1)	// do nothing if we are already in the last line!
		return;

	QMultiLineEdit::pageDown(false);
	getCursorPosition(&y2, &x2);
	
	QPoint p1(x, y), p2(x2, y2);
	Action* a = new CursorPlacementAction(this, p1, p2, markText);
	UndoRedoList.AddAction(a, false);	// action already done in QMLE::pageDown()
	emit cursorPosChanged();			// since it was not exec'd
	
	if(markText)
		mark.extendMark(y2, x2);
	else
	    mark.setMarkBase(y2, x2);
}
//////////////////////////////////////////////



/*
//////////////////////////////////////////////
void KSyntaxMultiLineEdit::updateCellWidth()
{
    QString *s = getString(0);
    QFontMetrics fm(font());
    int maxW = 0, w, line = 0;
    while(s) 
    {
		w = textWidthWithTabs(fm, s->data(), s->length()) + (BORDER << 1);
		if (w > maxW)
		    maxW = w;
		s = getString(++line);
    }
    
    	// update?
    setCellWidth(maxW);
}
//////////////////////////////////////////////
*/


//////////////////////////////////////////////
inline int KSyntaxMultiLineEdit::tabStopDist( const QFontMetrics &fm )
{
	   	// sets tabstops to four times the width of an 'average character'
    return edSettings.tabSize() * tabWidth(fm);
}
//////////////////////////////////////////////



//////////////////////////////////////////////
int KSyntaxMultiLineEdit::textWidthWithTabs(const QFontMetrics &fm, 
											const char *s, int nChars)
{
    if(!s)
		return 0;
    if(nChars == -1)
		nChars = strlen(s);

    register int         dist = 0;
    register const char *tmp = s;
    register int         tabCharWidth = tabWidth(fm);
    register int		 chars = 0;
    
    while (*tmp && tmp - s < nChars) 
    {
		if (*tmp == '\t') 
		{
			int ts = edSettings.tabSize() - (chars % edSettings.tabSize());
			if(ts == 0)
				ts = edSettings.tabSize();
			chars += ts;
			dist += ts * tabCharWidth;
		} 
		else
		{
		    dist += fm.width(tmp, 1);
		    chars++;
		}
		tmp++;
    }
    return dist;
}
//////////////////////////////////////////////



//////////////////////////////////////////////
int KSyntaxMultiLineEdit::xPosToCursorPos( const char *s, const QFontMetrics &fm,
			    		   int xPos, int width )
{
    const char *tmp;
    int	  dist;
    int tabDist;

    if ( !s )
		return 0;
    if ( xPos > width )
		xPos = width;
    if ( xPos <= 0 )
		return 0;

    int     distBeforeLastTab = 0;
    dist    = 0;
    tmp	    = s;
    tabDist = tabStopDist(fm);
    while ( *tmp && dist < xPos ) 
    {
		if ( *tmp == '\t') 
		{
		    distBeforeLastTab = dist;
		    dist = (dist/tabDist + 1) * tabDist;
		} 
		else 
		{
		    dist += fm.width( tmp, 1 );
		}
		tmp++;
    }
    if(dist > xPos) 
    {
		if(dist > width) 
		{
		    tmp--;
		} 
		else 
		{
		    if(*(tmp - 1) == '\t') 
		    { // dist equals a tab stop position
				if(xPos - distBeforeLastTab < (dist - distBeforeLastTab)/2)
			    	tmp--;
		    } 
		    else 
		    {
			if(fm.width(tmp - 1, 1)/2 < dist-xPos)
			    tmp--;
	    	}
		}
    }
    return tmp - s;
}
//////////////////////////////////////////////



//////////////////////////////////////////////
void KSyntaxMultiLineEdit::makeCursorVisibleImmediatly()
{
	int cy;
	getCursorPosition(&cy, 0);
	smle_cursorOn = true; 
	updateCell(cy, 0, FALSE);
}
//////////////////////////////////////////////



//////////////////////////////////////////////
void KSyntaxMultiLineEdit::updateMarkedLines(int oldMarkedLine, int newMarkedLine)
{
		// update newly (un)selected lines to ensure the entire marked 
		// region is shown inverted:
	int h;
	if(newMarkedLine > oldMarkedLine)
	{	
		h 			  = newMarkedLine;
		newMarkedLine = oldMarkedLine; 
		oldMarkedLine = h;
	}
	for(int line = newMarkedLine; line <= oldMarkedLine; line++)
		if(rowIsVisible(line))
			updateCell(line, 0, false);
}
//////////////////////////////////////////////

