/* ****************************************************************************
  This file is part of KBabel

  Copyright (C) 1999-2000 by Matthias Kiefer
                            <matthias.kiefer@gmx.de>
		2001-2003 by Stanislav Visnovsky
			    <visnovsky@kde.org>
			    
  Alt+123 feature idea taken from KOffice by David Faure <david@mandrakesoft.com>.

  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 2 of the License, 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.

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


#include "mymultilineedit.h"
#include "editcmd.h"
#include "resources.h"

#include <qpixmap.h>
#include <qpainter.h>
#include <qvaluelist.h>
#include <qstringlist.h>
#include <qregexp.h>
#include <qclipboard.h>
#include <qapplication.h>
#include <qdragobject.h>
#include <private/qrichtext_p.h>
#include <qpopupmenu.h>

#include <kdebug.h>
#include <kglobal.h>
#include <kmessagebox.h>
#include <kstdaccel.h>

MyMultiLineEdit::MyMultiLineEdit(QWidget* parent,const char* name)
                :QTextEdit(parent,name), emitUndo(true), _firstChangedLine(0), 
		_lastChangedLine(0), _lastParagraph(0), 
		_lastParagraphOffset(0), _dontUpdate(false), _menu(0),
		_overwrite(false)
{
   setUndoRedoEnabled(false); // we handle this ourselves
   setWordWrap( NoWrap );
   viewport()->setAcceptDrops( false ); // we need our parent to get drops
}

void MyMultiLineEdit::processCommand(EditCommand* cmd, bool undo)
{
   if(cmd->terminator()!=0)
      return;

   DelTextCmd* delcmd = (DelTextCmd*) cmd;
   bool ins =  true;
   if (delcmd->type() == EditCommand::Delete )
      ins = undo;
   else if (delcmd->type() == EditCommand::Insert )
      ins = !undo;
   else
   {
      return;
   }

  // avoid duplicate update of catalog
  bool oldEmitUndo = emitUndo;
  emitUndo = false;

  QPalette _visibleHighlight( palette() );
  QPalette _invisibleHighlight( palette() );
  QColorGroup newcg( colorGroup() );
  newcg.setColor( QColorGroup::HighlightedText, newcg.text() );
  newcg.setColor( QColorGroup::Highlight, newcg.base() );
  if( hasFocus() ) _invisibleHighlight.setActive( newcg );
  else _invisibleHighlight.setInactive( newcg );
  setPalette( _invisibleHighlight );   
   
  if(delcmd->offset <= (int)_lastParagraphOffset)
  {
    _lastParagraph=0;
    _lastParagraphOffset=0;
  }
  
   if ( ins )
   {
      int row, col;
      
      offset2Pos( delcmd->offset, row, col );
      setCursorPosition( row, col );
      
      _firstChangedLine=row;
      if(delcmd->str.find("\n")>0 )_lastChangedLine=row+delcmd->str.contains("\n");
      else _lastChangedLine=row;
      
      QTextEdit::insert( delcmd->str ); 

      offset2Pos( delcmd->offset+delcmd->str.length(), row, col );
      setCursorPosition( row, col);
   }
   else
   { // del

      int row, col, rowEnd, colEnd;

      offset2Pos( delcmd->offset, row, col );
      offset2Pos( delcmd->offset + delcmd->str.length(), rowEnd, colEnd );
      
      setSelection( row, col, rowEnd, colEnd, 0 );
      _firstChangedLine=_lastChangedLine=row;
      QTextEdit::removeSelectedText();
   }


   setPalette( _visibleHighlight );   

   emitUndo = oldEmitUndo;
      
   emitCursorPosition();
}

int MyMultiLineEdit::beginOfMarkedText()
{
	int beginX=0;
	int beginY=0;
	int endX=0;
	int endY=0;

	int pos=-1;
	
	getSelection(&beginY,&beginX,&endY,&endX);
	if( hasSelectedText() )
	{
		pos = pos2Offset(beginY,beginX);
	}

	return pos;
}

void MyMultiLineEdit::emitCursorPosition()
{
    int line=0;
    int col=0;
    getCursorPosition(&line,&col);

    emit cursorPositionChanged(line,col);
}

void MyMultiLineEdit::wheelEvent(QWheelEvent *e)
{
    e->ignore();
}

void MyMultiLineEdit::focusInEvent(QFocusEvent *e)
{
    QTextEdit::focusInEvent(e);
    emitCursorPosition();
}

void MyMultiLineEdit::contentsContextMenuEvent( QContextMenuEvent * e)
{
    e->accept();
    if( _menu ) _menu->exec( e->globalPos() );
}

QPopupMenu * MyMultiLineEdit::createPopupMenu()
{
    return _menu;
}

QPopupMenu * MyMultiLineEdit::createPopupMenu(const QPoint &)
{
    return 0;
}

void MyMultiLineEdit::setContextMenu( QPopupMenu * menu )
{
    _menu = menu;
}

void MyMultiLineEdit::doKeyboardAction( KeyboardAction action )
{
  int row,col;
  getCursorPosition(&row, &col);
  
  switch( action ) {
      case ActionDelete: 
        _firstChangedLine=_lastChangedLine=row;
	my_del(); break;

      case ActionBackspace:
        _firstChangedLine=_lastChangedLine=row;
	my_backspace(); break;

      case ActionReturn:
	if( emitUndo) 
	    emit signalUndoCmd( new InsTextCmd(currentIndex(), "\n") );
	break;
  
      case ActionKill:
        _firstChangedLine=_lastChangedLine=row;
        if(emitUndo)
	{
	    int x,y;
	    getCursorPosition( &x, &y );
	    QString s = text(x);
	    if( y < (int)s.length()-1  ) // not the end of paragraph
	    {
		QString delText = s.mid( y, s.length()-y-1);
	        emit signalUndoCmd( new DelTextCmd(currentIndex(), delText ) );
	    } else 
		if( x < paragraphs()-1 ) // not the end of text
		    emit signalUndoCmd( new DelTextCmd(currentIndex(), "\n" ) );
	}
        break;
      default: break;
  }

  QTextEdit::doKeyboardAction( action );

  emitCursorPosition();
}

void MyMultiLineEdit::setText(const QString& s)
{
    _lastParagraph=0;
    _lastParagraphOffset=0;
    // workaround, since insert does not interpret markup
    setTextFormat( Qt::PlainText );
    _firstChangedLine=_lastChangedLine=0;
    QTextEdit::setText(s);
    setTextFormat( Qt::AutoText );
    // now the number of lines is known, let's do highlight
    _lastChangedLine=paragraphs();
    emit textChanged();
    emitCursorPosition();
}

void MyMultiLineEdit::insertAt( const QString & s, int line, int col, bool mark )
{
   // it will invoke insert, don't need to send InsTextCmd
   QTextEdit::insertAt(s,line,col);
   
   // code from QMultiLineEdit
   if( mark )  
	setSelection( line, col, line, col + s.length(), 0 );
   // end of copied code
   
   emitCursorPosition();
}

void MyMultiLineEdit::insert( const QString & text, bool indent, bool checkNewLine, bool removeSelected )
{
   int row,col;

   bool noSelectionRemoved = true;
   setUpdatesEnabled(false);
   if( removeSelected && hasSelectedText() )
   {
	int endRow,endCol;
	getSelection(&row,&col,&endRow,&endCol);

	if( row<(int)_lastParagraph )
	{
	    _lastParagraph=0;
	    _lastParagraphOffset=0;
	}
	
	_firstChangedLine=_lastChangedLine=row;
	removeSelectedText();
	noSelectionRemoved = false;
   }
	
   getCursorPosition(&row,&col);
   _firstChangedLine=row;
   _lastChangedLine=row;

   if( emitUndo)
   {
	emit signalUndoCmd( new BeginCommand());
	// reimplemented overwrite
	if( _overwrite && noSelectionRemoved) 
	{
	    doKeyboardAction( ActionDelete );
	}
	
	emit signalUndoCmd( new InsTextCmd(currentIndex(), text) );
	emit signalUndoCmd( new EndCommand());
   }
   
   int n=text.find("\n");
   if( n > 0 ) _lastChangedLine+=n;
   
   // setup palettes
   
   QPalette _visibleHighlight( palette() );
   QPalette _invisibleHighlight( palette() );
   QColorGroup newcg( colorGroup() );
   newcg.setColor( QColorGroup::HighlightedText, newcg.text() );
   newcg.setColor( QColorGroup::Highlight, newcg.base() );
   if( hasFocus() ) _invisibleHighlight.setActive( newcg );
   else _invisibleHighlight.setInactive( newcg );
   setPalette( _invisibleHighlight );   
   QTextEdit::insert(text, indent, checkNewLine, removeSelected);
   setPalette( _visibleHighlight );   
 
   setUpdatesEnabled(true);

   emitCursorPosition();
}

void MyMultiLineEdit::removeLine ( int line )
{
   kdDebug(KBABEL) << "removeLine invoked" << endl;

   QTextEdit::removeParagraph(line);
   emitCursorPosition();
}

void MyMultiLineEdit::clear()
{
   kdDebug(KBABEL) << "clear invoked" << endl;
   
    _lastParagraph=0;
    _lastParagraphOffset=0;
    
    _dontUpdate=true;

   QString s = text();
   if( !s.isEmpty() && emitUndo ) {
	emit signalUndoCmd( new BeginCommand() );
	emit signalUndoCmd( new DelTextCmd(0,s) );
	emit signalUndoCmd( new EndCommand() );
   }
   
   QTextEdit::clear();
   
   _dontUpdate=false;
   
   _firstChangedLine=_lastChangedLine=0;
   emitCursorPosition();
}


void MyMultiLineEdit::my_backspace()
{

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

   if( hasSelectedText())
   {
	  Q_ASSERT( "backspace: This should never happen, why is not invoked removeSelectedText()?");
   }
   else if(! (cursorY==0 && cursorX==0) )
   {
      if(emitUndo)
      {
         int offset = currentIndex();

         QString s= text(cursorY);
         if(cursorX != 0)
         {
            QString delTxt(s[cursorX-1]);
            emit signalUndoCmd(new DelTextCmd(offset-1,delTxt));
         }
         else if( cursorY > 0 || cursorX > 0 ) // not at the beginning
         {
            emit signalUndoCmd(new DelTextCmd(offset-1,"\n"));
         }
      }
   }
}

void MyMultiLineEdit::my_del()
{

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

   if( hasSelectedText())
   {
	  Q_ASSERT( "del: This should never happen, why is not invoked removeSelectedText()?");
   }
   else if(! (cursorY==paragraphs()-1 && cursorX==paragraphLength( cursorY )) )
   {
      if(emitUndo)
      {
         int offset = pos2Offset(cursorY, cursorX);

         QString s=text(cursorY);
         if(cursorX != (int)s.length()-1)
         {
            QString delTxt(s[cursorX]);
            emit signalUndoCmd(new DelTextCmd(offset,delTxt));
         }
         else if( cursorY < (int)paragraphs()-1 || ( (cursorY == (int)paragraphs()-1) && (cursorX < (int)text( paragraphs()-1 ).length()-1 ) ) )// !atEnd() )
         {
            emit signalUndoCmd(new DelTextCmd(offset,"\n"));
         }
      }
   }
}

void MyMultiLineEdit::removeSelectedText(int selNum)
{
    if( selNum != 0 ) 
    {
	_lastParagraph=0;
	_lastParagraphOffset=0;
	
	QTextEdit::removeSelectedText(selNum);
    }
    else 
    {
	  int paraFrom, idxFrom, paraTo, idxTo;
	  QTextEdit::getSelection( &paraFrom, &idxFrom, &paraTo, &idxTo );
	  
	  if( paraFrom < (int)_lastParagraph )
	  {
	    _lastParagraph=0;
	    _lastParagraphOffset=0;
	  }

	  int offset = pos2Offset( paraFrom, idxFrom );
	  emit signalUndoCmd(new DelTextCmd( offset, selectedText() ) );
	  QTextEdit::removeSelectedText(selNum);
    }

    emitCursorPosition();
}

void MyMultiLineEdit::paste()
{
    QTextEdit::paste();    
    emitCursorPosition();
}

int MyMultiLineEdit::currentIndex()
{
    int para; // paragraph of current position
    int index; // index in the current paragraph
    
    QTextEdit::getCursorPosition(&para,&index);
    
    return pos2Offset( para, index );
}


void MyMultiLineEdit::offset2Pos(int offset, int &paragraph, int &index) const
{
    if (offset <= 0)
    {
	paragraph = 0;
	index = 0;
	return;
    }
    else
    {
       int charsLeft = offset;
       int i;

       for( i = 0; i < paragraphs(); ++i )
       {
           if (paragraphLength( i ) < charsLeft)
              charsLeft -= paragraphLength( i );
           else
           {
              paragraph = i;
              index = charsLeft;
              return;
           }
	   --charsLeft;
       }
    
          paragraph = i-1;
          index = charsLeft;
       return;
    }
}

int MyMultiLineEdit::pos2Offset(uint paragraph, uint index)
{
    paragraph = QMAX( QMIN( (int)paragraph, paragraphs() - 1), 0 ); // Sanity check
	index  = QMAX( QMIN( (int)index,  paragraphLength( paragraph )), 0 ); // Sanity check
    {
        uint lastI;
	lastI  = paragraphLength( paragraph );
	uint i = 0; 
	uint tmp = 0;

	if( paragraph>=_lastParagraph )
	{
	    tmp = _lastParagraphOffset;
	    i  = _lastParagraph;
	}

	for( ;i < paragraph ; i++ )
	{
	    tmp += paragraphLength( i ) + 1;
	}
	
	_lastParagraphOffset=tmp;
	_lastParagraph=paragraph;

	tmp += QMIN( lastI, index );

	return tmp;
    }
}

void MyMultiLineEdit::setReadOnly(bool on)
{
	// I want this backgroundmode, also when readonly==true
	if(on) 
	{
		setBackgroundMode(PaletteBase);
	}
	
	QTextEdit::setReadOnly(on);
}

void MyMultiLineEdit::setOverwriteMode( bool b )
{
    _overwrite = b;
}

/*******************************************************************************/
MsgMultiLineEdit::MsgMultiLineEdit(QWidget* parent,const char* name)
                :MyMultiLineEdit(parent,name),
                _quotes(false),
                _cleverEditing(false),
                _highlightBg(false),
                _spacePoints(false),
                _bgColor(colorGroup().base().dark(110)),
		_textColor(Qt::black),
		_errorColor(Qt::red),
		_currentColor(Qt::black),
                _hlSyntax(true),
                _quoteColor(Qt::darkGreen),
                _unquoteColor(Qt::red),
                _cformatColor(Qt::blue),
                _accelColor(Qt::darkMagenta),
                _showDiff(false),
                _diffUnderlineAdd(true),
                _diffStrikeOutDel(true),
                _diffAddColor(Qt::darkGreen),
                _diffDelColor(Qt::darkRed),
		_currentUnicodeNumber(0)
{
   _whitespace = new QPixmap(2,2,-1,QPixmap::BestOptim);
   _whitespace->fill(_textColor);
   _errorWhitespace = new QPixmap(2,2,-1,QPixmap::BestOptim);
   _errorWhitespace->fill(_errorColor);

   _whitespaceNB = new QPixmap(3,3,-1,QPixmap::BestOptim);
   _whitespaceNB->fill();
   _errorWhitespaceNB = new QPixmap(3,3,-1,QPixmap::BestOptim);
   _errorWhitespaceNB->fill();

   QPainter p(_whitespaceNB);
   p.setPen( _textColor );
   p.drawEllipse(_whitespaceNB->rect());

   QPainter q(_errorWhitespaceNB);
   q.setPen( _errorColor );
   q.drawEllipse(_errorWhitespaceNB->rect());
					  
   diffPos.setAutoDelete(true);
   diffPos.clear();
   connect( this, SIGNAL( selectionChanged() ), this, SLOT( paintSpacePoints() ) );   
   connect( this, SIGNAL( cursorPositionChanged( int, int ) ), this, SLOT( paintSpacePoints(int, int) ) );
}

void MsgMultiLineEdit::setText(const QString& s)
{
    QString str = s;
    
    if(_showDiff)
    {
        diffPos.clear();
        int lines = s.contains('\n');
        diffPos.resize(lines+1);

        QStringList lineList = QStringList::split('\n',s,true);
        
        int lineCounter=-1;
        bool haveAdd=false;
        bool haveDel=false;
        bool multiline=false;
        QStringList::Iterator it;
        for(it = lineList.begin(); it != lineList.end(); ++it)
        {
            lineCounter++;
            
            int lastPos=0;
            bool atEnd=false;

            while(!atEnd)
            {            
                int addPos=-1;
                int delPos=-1;
         
                if(haveAdd && multiline)
                {
                    addPos=0;
                }
                else
                {
                    addPos = (*it).find("<KBABELADD>",lastPos);
                }
            
                if(haveDel && multiline)
                {
                    delPos=0;
                }
                else
                {
                    delPos = (*it).find("<KBABELDEL>",lastPos);
                }

                if(delPos >= 0 && addPos >= 0)
                {
                    if(delPos <= addPos)
                    {
                        haveDel=true;
                        haveAdd=false;
                    }
                    else
                    {
                        haveDel=false;
                        haveAdd=true;
                    }
                }
                else if(delPos >= 0)
                {
                    haveDel=true;
                    haveAdd=false;
                }
                else if(addPos >= 0)
                {
                    haveDel=false;
                    haveAdd=true;
                }
                else
                {
                    atEnd=true;
                    haveAdd=false;
                    haveDel=false;
                }
                
                DiffInfo di;
                di.begin=-1;
                
                if(haveAdd)
                {
                    if(!multiline)
                    {
                        (*it).remove(addPos,11);                    
                    }
                    
                    int endPos = (*it).find("</KBABELADD>",addPos);
                    if(endPos < 0)
                    {
                        endPos = (*it).length();
                        atEnd=true;
                        multiline=true;
                    }
                    else
                    {
                        (*it).remove(endPos,12);
                        haveAdd=false;
                        multiline=false;
                    }
                    
                    lastPos=endPos;

                    di.begin=addPos;
                    di.end=endPos-1;
                    di.add=true;
                }
                else if(haveDel)
                {
                    if(!multiline)
                    {
                        (*it).remove(delPos,11); 
                    }
                    
                    int endPos = (*it).find("</KBABELDEL>",delPos);
                    if(endPos < 0)
                    {
                        endPos = (*it).length();
                        atEnd=true;
                        multiline=true;
                    }
                    else
                    {
                        (*it).remove(endPos,12);
                        haveDel=false;
                        multiline=false;
                    }
                    
                    lastPos=endPos;

                    di.begin=delPos;
                    di.end=endPos-1;
                    di.add=false;
                }

                if(di.begin >= 0)
                {
                    QValueList<DiffInfo> *list = diffPos[lineCounter];
                    if(!list)
                    {
                        list = new QValueList<DiffInfo>;
                        diffPos.insert(lineCounter,list);
                    }

                    list->append(di);
                }
                
            }
        }
        
        QRegExp reg("</?KBABELADD>");
        str.replace(reg,"");
        reg.setPattern("</?KBABELDEL>");
        str.replace(reg,"");
    }
    
    MyMultiLineEdit::setText(str);
    paintSpacePoints();
}

void MsgMultiLineEdit::setQuotes(bool on)
{
   _quotes=on;
   update();
}

void MsgMultiLineEdit::setCleverEditing(bool on)
{
   _cleverEditing=on;
}


void MsgMultiLineEdit::setHighlightBg(bool on)
{
   _highlightBg=on;
   update();
}


void MsgMultiLineEdit::setBgColor(const QColor& color)
{
   _bgColor=color;

   if(_highlightBg)
      update();
}

void MsgMultiLineEdit::setSpacePoints(bool on)
{
   _spacePoints=on;

   update();
}

void MsgMultiLineEdit::setHighlightSyntax(bool on)
{
   _hlSyntax=on;
   
   update();
}

void MsgMultiLineEdit::setHighlightColors(const QColor& quoteColor, const QColor& unquoteColor
                 , const QColor& cformatColor, const QColor& accelColor, const QColor& tagColor)
{
   _quoteColor=quoteColor;
   _unquoteColor=unquoteColor;
   _cformatColor=cformatColor;
   _accelColor=accelColor;
   _tagColor=tagColor;

   update();
}


void MsgMultiLineEdit::setFont(const QFont& font)
{
   QTextEdit::setFont(font);

   QFontMetrics fm(font);
   _wsOffsetX = QMAX(fm.width(' ')/2-2,1);
   _wsOffsetY = QMAX(fm.height()/2-1,0);

   repaint();
}

void MsgMultiLineEdit::setDiffDisplayMode(bool addUnderline, bool delStrikeOut)
{
    _diffUnderlineAdd = addUnderline;
    _diffStrikeOutDel = delStrikeOut;
    
    if(_showDiff)
        update();
}

void MsgMultiLineEdit::setDiffColors(const QColor& addColor
        , const QColor& delColor)
{
    _diffAddColor = addColor;
    _diffDelColor = delColor;

    if(_showDiff)
        update();
}

void MsgMultiLineEdit::setTextColor(const QColor &color )
{
  QPalette p( palette() );
  QColorGroup newcg( colorGroup() );
  newcg.setColor( QColorGroup::Text, color );
  if( hasFocus() ) p.setActive( newcg );
  else p.setInactive( newcg );
  setPalette( p );
  _textColor = color;
}

void MsgMultiLineEdit::setErrorColor(const QColor &color )
{
  _errorColor = color;
}

void MsgMultiLineEdit::setCurrentColor(const TextColor color)
{
  if( color == NormalColor ) _currentColor = _textColor;
  else _currentColor = _errorColor;
  
  setUpdatesEnabled(false);
  selectAll();
  setColor( _currentColor );
  removeSelection();
  setColor(_currentColor);
  setUpdatesEnabled(true);
  forceUpdate();
}

void MsgMultiLineEdit::paintSpacePoints(int, int )
{
    paintSpacePoints();
}

void MsgMultiLineEdit::paintSpacePoints()
{
    QRect r;
    QPoint p;
    QPainter painter(viewport() );

#if (QT_VERSION < 305)
    QTextParag* par = document()->firstParag();
#else
    QTextParagraph* par = document()->firstParagraph();
#endif	
    int row = 0;
    int rowY = 2;

    while( par && rowY < contentsY())
    {
	row++;
	rowY+=par->lineHeight(0);
	par=par->next();
    }

    if( _spacePoints ) {
	painter.setPen( _currentColor );

#if (QT_VERSION < 305)
	QTextParag* mypar = par;
#else
	QTextParagraph* mypar = par;
#endif
	int myrow = row;
	int myrowY = rowY;
	
        QPixmap* ws, *wsnb;
	
	if( _currentColor== _errorColor )
	{
	    ws = _errorWhitespace;
	    wsnb = _errorWhitespaceNB;
	}
	else
	{
	    ws = _whitespace;
	    wsnb = _whitespaceNB;
	}
													
        while( mypar )
	{
	    QString s = text(myrow);
	    int i = s.find( " " );
	    while( (i >= 0) && (i < (int)s.length()-1) ) // -1 because text will end by EOLN
	    {
		QTextStringChar* ch=mypar->at(i);
		p = contentsToViewport( QPoint(ch->x,myrowY));
		if( ch->c.unicode() == 0x00A0U )
		    bitBlt(viewport(), QPoint(p.x()+_wsOffsetX, p.y()+_wsOffsetY), wsnb, wsnb->rect(), Qt::CopyROP);
		else
		    bitBlt(viewport(), QPoint(p.x()+_wsOffsetX, p.y()+_wsOffsetY), ws, wsnb->rect(), Qt::CopyROP);
		i = s.find( " ", i+1 );
    	    }
	    myrowY+=mypar->lineHeight(0);
	    if( myrowY > contentsY()+visibleHeight() ) break;

	    mypar=mypar->next();
	    myrow++;
        }
    }
    
    if( _quotes ) {

	QFontMetrics fm( font() );
	QRect qs = fm.boundingRect("\"");
        
	for( int myrow = row; myrow < paragraphs() ; myrow++ )
	{
	    r = paragraphRect(myrow);
	    if( r.y() > contentsY()+visibleHeight() ) break;
	    
	    QPoint pos( r.x()+r.width()-4, r.y()+r.height()-qs.height() );
	    pos = contentsToViewport(pos);
	    painter.drawText( pos, "\""); // 4 is Qt source
	    pos = QPoint( 0, r.y()+r.height()-qs.height() );
	    pos = contentsToViewport(pos);
	    painter.drawText( pos, "\""); // 4 is Qt source
        }
    }
    
    if( _showDiff && (!_diffUnderlineAdd || !_diffStrikeOutDel) )
    {
	if( paragraphs() == (int)diffPos.size() ) // sanity check
	{
	painter.setRasterOp( Qt::AndROP );
	for( int myrow = row; myrow < paragraphs() ; myrow++ ) {
	    r = paragraphRect(myrow);
	    if( r.y() > contentsY()+visibleHeight() ) break;

    	    QValueList<DiffInfo> *list = diffPos[myrow];
    	    if(list)
    	    {
        	QValueList<DiffInfo>::ConstIterator it;
        	for(it = list->begin(); it != list->end(); ++it)
        	{
            	    QRect beg =  mapToView( myrow, (*it).begin );
		    QRect end =  mapToView( myrow, (*it).end );
//		    if( beg && end ) 
		    {
			QRect diff  =beg.unite( end);
		    
            		if( (*it).add  && !_diffUnderlineAdd)
            		{
                	    painter.fillRect( diff, _diffAddColor);
            		}
            		else if(!(*it).add && !_diffStrikeOutDel)
            		{
                	    painter.fillRect( diff, _diffDelColor );
            		}
		    }
        	}
    	    }
	}
	}
    }

    if( _showDiff && (_diffUnderlineAdd || _diffStrikeOutDel) )
    {
	if( paragraphs() == (int)diffPos.size() ) // sanity check
	{
	for( int myrow = row ; myrow < paragraphs() ; myrow++ ) {
	    r = paragraphRect(myrow);
	    if( r.y() > contentsY()+visibleHeight() ) break;

    	    QValueList<DiffInfo> *list = diffPos[myrow];
    	    if(list)
    	    {
        	QPen addPen(_diffAddColor,2);
        	QPen delPen(_diffDelColor,2);
        	QValueList<DiffInfo>::ConstIterator it;
        	for(it = list->begin(); it != list->end(); ++it)
        	{
		    QRect beg = mapToView( myrow, (*it).begin );
		    QRect end = mapToView( myrow, (*it).end );
//		    if( beg && end )
		    {
			QRect diff  = beg.unite( end);

                	if( (*it).add && _diffUnderlineAdd)
            		{
                	    painter.setPen(addPen);
                	    painter.drawLine(diff.x(),diff.y()+diff.height()-1,diff.x()+diff.width(),diff.y()+diff.height()-1);
            	        }
            		else if(!(*it).add && _diffStrikeOutDel)
            		{
                	    painter.setPen(delPen);
                	    painter.drawLine(diff.x(),diff.y()+diff.height()/2,diff.x()+diff.width(),diff.y()+diff.height()/2);
            		}
		    }
        	}
    	    }
	}
	}
    }
}

void MsgMultiLineEdit::repaint()
{
    highlight();
    MyMultiLineEdit::repaint();
}

void MsgMultiLineEdit::forceUpdate()
{
    _firstChangedLine=0;
    _lastChangedLine=paragraphs()-1;
    highlight();
    MyMultiLineEdit::repaint();
}

void MsgMultiLineEdit::ensureCursorVisible()
{
    if( isUpdatesEnabled() )
	MyMultiLineEdit::ensureCursorVisible();
}

void MsgMultiLineEdit::highlight()
{
    if( _dontUpdate ) return;
    
    QColor bg;
    if( _highlightBg ) bg = _bgColor;
    else bg = colorGroup().base(); 

    for( int i = 0 ; i < paragraphs() ; i++ )
	    setParagraphBackgroundColor( i, bg );
	

    if(_hlSyntax) 
    {
	blockSignals(true); // block signals to avoid recursion
	setUpdatesEnabled(false);
	int cursorParagraph, cursorIndex;

	getCursorPosition( &cursorParagraph, &cursorIndex );

	// setup new colors
	
	uint i;
	
	QRegExp markup("(\\\\)|(\")|(\\\\[abfnrtv'\"\?\\\\])|(\\\\\\d+)|(\\\\x[\\dabcdef]+)"
	    "|(%[\\ddioxXucsfeEgGphln]+)|(&[^\\s])|(&[\\w-]+;)");

	for( i = QMAX(_firstChangedLine,0) ; i < QMIN(_lastChangedLine+1,(uint)paragraphs()) ; i++ ) {
	
	QString line=text(i);

	//remove old highlighting
	setSelection(i,0,i,line.length());
	setColor( _currentColor );
	removeSelection();
	
	QColor colorToUse;

	int index=0;
	index=markup.search( line, index );
	while(index>=0)
	{
	    switch( line[index].latin1() )
	    {
		case '\\':
		    if( markup.matchedLength() == 1 ) colorToUse=_unquoteColor;
		    else colorToUse=_quoteColor;
		    break;
		case '\"':
		    colorToUse=_unquoteColor;
		    break;
		case '%':
		    colorToUse=_cformatColor;
		    break;
		case '&':
		    colorToUse=_accelColor;
		    break;
	    }
	    
	    setSelection( i, index, i, index+markup.matchedLength(), 0);
	    setColor( colorToUse );
	    removeSelection();
	    index=markup.search( line, index+markup.matchedLength() );
	}
	}

	// Color XML and HTML tags
	
	int tagindex=0;
	int taglength=0;
	int lineindex=0;
	uint index=0;
	int startPara, endPara, startIndex, endIndex;
	QString t= text();
	
	if(_lastParagraph <= _firstChangedLine)
	{
	    index=_lastParagraph;
	    lineindex=_lastParagraphOffset;
	}
	
	for( ; index<_firstChangedLine ; index++)
	    lineindex+=paragraphLength(index)+1;
	
        QRegExp re("<.*>");
	re.setMinimal(true);

	if( _firstChangedLine >0 )
	{
	    QColor c;
	    QFont f;
	    VerticalAlignment v;
	    getFormat(_firstChangedLine-1, paragraphLength(_firstChangedLine-1)-1, &f, &c, &v);
	    QString l = text(_firstChangedLine-1);
	    if( c==_tagColor  && !l.endsWith(">") ) // hope _tagColor will be different than other colors
	    {
		QRegExp endtag("[^<]*>");
		tagindex=endtag.search(t, lineindex);
		taglength=endtag.matchedLength();
	    } else {
		tagindex=re.search(t, lineindex);
		taglength=re.matchedLength();
	    }
	} else 	{
	    tagindex=re.search( t, lineindex );
	    taglength=re.matchedLength();
	}
	
	while( tagindex >= 0 && (int)index<paragraphs())
	{
	    while( tagindex>=lineindex  && index<_lastChangedLine+2) 
		lineindex+=paragraphLength(index++)+1;
	    if(index==_lastChangedLine+2) break;
	    lineindex-=paragraphLength(index-1);
	    lineindex--;
	    index--;
		
	    startPara=index;
	    startIndex=tagindex-lineindex;
	    
	    tagindex+=taglength;

	    while( tagindex>=lineindex && (int)index<paragraphs()) 
		lineindex+=paragraphLength(index++)+1;
	    lineindex-=paragraphLength(index-1);
	    lineindex--;
	    index--;
	    
	    endPara=index;
	    endIndex=tagindex-lineindex;

	    setSelection( startPara, startIndex, endPara, endIndex, 0 );
            setColor( _tagColor );
            removeSelection();

	    if(index>_lastChangedLine) break;
            tagindex=re.search( t, tagindex );
	    taglength=re.matchedLength();
        }
																																																
	setCursorPosition( cursorParagraph, cursorIndex );
	setColor( _textColor );
	setUpdatesEnabled(true);
	blockSignals(false); // block signals to avoid recursion
	updateContents();
    }
    ensureCursorVisible();
}

void MsgMultiLineEdit::drawContents( QPainter *painter, int clipx, int clipy, int clipw, int cliph )
{
    MyMultiLineEdit::drawContents( painter, clipx, clipy, clipw, cliph );
    paintSpacePoints();
}

void MsgMultiLineEdit::paintEvent( QPaintEvent *event )
{
    MyMultiLineEdit::paintEvent( event );
    paintSpacePoints();
}

/**
 * WARNING:
 * This function contains some undocumented magic inspired by Qt source code.
 * It can be quite non-portable to new versions of Qt (even minor revisions)
 */
 
QRect MsgMultiLineEdit::mapToView( int para, int index )
{
    if( para < 0 || para > paragraphs() || index < 0 || index > paragraphLength(para) ) return QRect(); //invalid rectangle

    QPainter painter(viewport() );
    QTextDocument * doc = document();
#if (QT_VERSION < 305)
    QTextParag * par = doc->firstParag();
#else
    QTextParagraph * par = doc->firstParagraph();
#endif
    int rowY = 2; // found out by experiment

    for( int row = 0; row < para ; row++ ) 
    {
	rowY += par->lineHeight(0); // we have no wrap
        par = par->next();
    }
	
    QTextStringChar *chr = par->at(index);
    
    QPoint p( chr->x, rowY );
    p = contentsToViewport(p);
    
    return QRect( p.x(), p.y(), par->string()->width(index), par->lineHeight(0) );
}

void MsgMultiLineEdit::keyPressEvent(QKeyEvent *e)
{
    if(!_cleverEditing || isReadOnly())
    {
        MyMultiLineEdit::keyPressEvent(e);
        return;
    }

    KKey key( e );
    
    if(e->key() == Key_Return || e->key() == Key_Enter)
    { 
        emit signalUndoCmd(new BeginCommand());

        int row, col;
        getCursorPosition(&row,&col);
        QString str=text(row);

        if(e->state() & ShiftButton)
        {            
            if(col > 0 && !str.isEmpty())
            {
                if(str.at(col-1) == '\\' && !isMasked(&str,col-1))
                {
                    insert("n",false);
                }
                else
                {
                    insert("\\n",false);
                }
            }
            else
            {
                insert("\\n",false);
            }
        }    
        else if(!(e->state() & ControlButton))
        {
            if(col > 0 && !str.isEmpty() && !str.at(col-1).isSpace())
            {
                if(str.at(col-1)=='\\' && !isMasked(&str,col-1))
                {
                    insert("\\",false);
                }
                
                // if there is not a new line at the end
                if(col < 2 || str.mid(col-2,2)!="\\n")
                {
                    insert(" ",false);
                }
            }
            else if(str.isEmpty())
            {
                insert("\\n",false);
            }
        }    
     
        if( !str.isEmpty())
        {
	    // construct new event without modifiers
    	    MyMultiLineEdit::keyPressEvent( new QKeyEvent(e->type(), e->key(), e->ascii(), 0,
		e->text(), e->isAutoRepeat(), e->count() ) );
	    e->accept();
        }

        emit signalUndoCmd(new EndCommand());
        return;
    }
    else if(e->key() == Key_Tab)
    {
        insert("\\t",false);
        emit textChanged();
        e->accept();
        return;
    }
    else if ( KStdAccel::deleteWordBack().contains( key ) ) {
      moveCursor(QTextEdit::MoveWordBackward, true);
      if ( hasSelectedText() )
        del();
      
      e->accept();
      return;
    }
    else if ( KStdAccel::deleteWordForward().contains( key ) ) {
      // Workaround for QT bug where
      moveCursor(QTextEdit::MoveWordForward ,true);
      if ( hasSelectedText() )
        del();
      
      e->accept();
      return;
    }
    else if(e->key() == Key_Delete 
            || ((e->state() & ControlButton) && e->key() == Key_D) )
    {
        emit signalUndoCmd(new BeginCommand());
        
        if(!hasSelectedText())
        {
            int row, col;
            getCursorPosition(&row,&col);
            QString str=text(row);

            if(!str.isEmpty() && col < (int)str.length() && str.at(col) == '\\'
                        && !isMasked(&str,col))
            {
                QString spclChars="abfnrtv'\"?\\";
                if(col < (int)str.length()-1 
                        && spclChars.contains(str.at(col+1)))
                {
                    del();
                }
            }
        }
        
        del();

        emit signalUndoCmd(new EndCommand());
        emit textChanged();
        e->accept();
        return;
    }
    else if(e->key() == Key_BackSpace
            || ((e->state() & ControlButton) && e->key() == Key_H) )
    {
        emit signalUndoCmd(new BeginCommand()); 
        
        if(!hasSelectedText())
        {
            int row, col;
            getCursorPosition(&row,&col);
            QString str=text(row);

            QString spclChars="abfnrtv'\"?\\";
            if(!str.isEmpty() && col > 0 && spclChars.contains(str.at(col-1)))
            {
                if(col > 1 && str.at(col-2)=='\\' && !isMasked(&str,col-2))
                {
                    MyMultiLineEdit::keyPressEvent(e);
                }
            }

        }
        
        MyMultiLineEdit::keyPressEvent(e);

        emit signalUndoCmd(new EndCommand());

        e->accept();
        return;
    }
    else if(e->text() == "\"")   
    {
        emit signalUndoCmd(new BeginCommand());

        int row, col;
        getCursorPosition(&row,&col);
        QString str=text(row);
    
        if(col == 0 || str.at(col-1) != '\\' || isMasked(&str,col-1) )
        {
            insert("\\\"",false);
        }
        else
        {
            insert("\"",false);
        }

        e->accept();

        emit signalUndoCmd(new EndCommand());
        return;
    }
    else if(e->key() == Key_Space && ( e->state() & AltButton ) )
    {
	insert( QChar( 0x00a0U ) );
	e->accept();
	return;
    }
    // ALT+123 feature
    else if(( e->state() & AltButton ) && e->text()[0].isDigit() )
    {
	QString text=e->text();
	while ( text[0].isDigit() ) {
	    _currentUnicodeNumber = 10*_currentUnicodeNumber+(text[0].digitValue());
	    text.remove( 0, 1 );
	}
    }
    else
    { 
        MyMultiLineEdit::keyPressEvent(e);
    }
}

void MsgMultiLineEdit::keyReleaseEvent(QKeyEvent* e)
{
    if ( e->key() == Key_Alt && _currentUnicodeNumber >= 32 )
    {
        QString text = QChar( _currentUnicodeNumber );
	_currentUnicodeNumber=0;
        insert( text );
    }
}

void MsgMultiLineEdit::setDiffMode(bool on)
{
    _showDiff=on;
    
    if(!on)
    {
        diffPos.clear();
    }
}

bool MsgMultiLineEdit::isMasked(QString *str, uint col)
{
    if(col == 0 || !str)
        return false;

    uint counter=0;
    int pos=col;
    
    while(pos >= 0 && str->at(pos) == '\\')
    {
        counter++;
        pos--;
    }
    
    return !(bool)(counter%2);
}

void MsgMultiLineEdit::emitCursorPosition()
{
    MyMultiLineEdit::emitCursorPosition();
}

void MsgMultiLineEdit::emittedTextChanged()
{
    highlight();
    paintSpacePoints();    
}

#include "mymultilineedit.moc"
