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

  Copyright (C) 1999-2000 by Matthias Kiefer
                            <matthias.kiefer@gmx.de>
		2001-2004 by Stanislav Visnovsky
			    <visnovsky@kde.org>

  Alt+123 feature idea taken from KOffice by David Faure <david@mandrakesoft.com>.
  Word wrap support by Jarno Elonen <elonen@iki.fi>, 2003

  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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

  In addition, as a special exception, the copyright holders give
  permission to link the code of this program with any edition of
  the Qt library by Trolltech AS, Norway (or with modified versions
  of Qt that use the same license as Qt), and distribute linked
  combinations including the two.  You must obey the GNU General
  Public License in all respects for all of the code used other than
  Qt. If you modify this file, you may extend this exception to
  your version of the file, but you are not obligated to do so.  If
  you do not wish to do so, delete this exception statement from
  your version.

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


#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 <kglobalsettings.h>
#include <kmessagebox.h>
#include <kstdaccel.h>

#include "kbhighlighting.h"

using namespace KBabel;

MyMultiLineEdit::MyMultiLineEdit(int ID, QWidget* parent,const char* name)
                :KTextEdit(parent,name), emitUndo(true), _firstChangedLine(0), 
		_lastChangedLine(0), _lastParagraph(0), 
		_lastParagraphOffset(0), _dontUpdate(false), _myID (ID),
		 _menu(0), _overwrite(false)
{
   setUndoRedoEnabled(false); // we handle this ourselves
   setWordWrap( WidgetWidth );
   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;
      
      KTextEdit::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;
      KTextEdit::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)
{
    KTextEdit::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", _myID) );
	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, _myID ) );
	    } else 
		if( x < paragraphs()-1 ) // not the end of text
		    emit signalUndoCmd( new DelTextCmd(currentIndex(), "\n", _myID ) );
	}
        break;
     default: break;
  }

  KTextEdit::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;
    KTextEdit::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
   KTextEdit::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(-1,UndefPart));
	// reimplemented overwrite
	if( _overwrite && noSelectionRemoved) 
	{
	    doKeyboardAction( ActionDelete );
	}
	
	emit signalUndoCmd( new InsTextCmd(currentIndex(), text, _myID) );
	emit signalUndoCmd( new EndCommand(-1,UndefPart));
   }
   
   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 );   
   KTextEdit::insert(text, indent, checkNewLine, removeSelected);
   setPalette( _visibleHighlight );   
 
   setUpdatesEnabled(true);

   emitCursorPosition();
}

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

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

void MyMultiLineEdit::clear()
{
    _lastParagraph=0;
    _lastParagraphOffset=0;
    
    _dontUpdate=true;

   QString s = text();
   if( !s.isEmpty() && emitUndo ) {
	emit signalUndoCmd( new BeginCommand(-1,UndefPart) );
	emit signalUndoCmd( new DelTextCmd(0,s,_myID) );
	emit signalUndoCmd( new EndCommand(-1,UndefPart) );
   }
   
   KTextEdit::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,_myID));
         }
         else if( cursorY > 0 || cursorX > 0 ) // not at the beginning
         {
            emit signalUndoCmd(new DelTextCmd(offset-1,"\n",_myID));
         }
      }
   }
}

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,_myID));
         }
         else if( cursorY < (int)paragraphs()-1 || ( (cursorY == (int)paragraphs()-1) && (cursorX < (int)text( paragraphs()-1 ).length()-1 ) ) )// !atEnd() )
         {
            emit signalUndoCmd(new DelTextCmd(offset,"\n",_myID));
         }
      }
   }
}

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

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

    emitCursorPosition();
}

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

int MyMultiLineEdit::currentIndex()
{
    int para; // paragraph of current position
    int index; // index in the current paragraph
    
    KTextEdit::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(int ID, KSpell* spell, QWidget* parent,const char* name)
                :MyMultiLineEdit(ID, parent,name),
                _quotes(false),
                _cleverEditing(false),
                _highlightBg(false),
                _spacePoints(false),
                _bgColor(colorGroup().base().dark(110)),
		_textColor(KGlobalSettings::textColor()),
		_errorColor(Qt::red),
		_currentColor(KGlobalSettings::textColor()),
		_whitespace(0),
                _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),
		highlighter(0),
		_tagStartPara(0), _tagStartIndex(0), _tagEndPara(0), _tagEndIndex(0)
{
   diffPos.setAutoDelete(true);
   diffPos.clear();
   
   _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());

   // this will setup bitBlt pixmaps
   setFont( font() );
   highlighter = new KBabelHighlighter( this, spell );
   connect( this, SIGNAL( signalSyntaxHighlightingChanged( bool ) ), highlighter, SLOT( setSyntaxHighlighting( bool ) ) );
   
   connect( this, SIGNAL( selectionChanged() ), this, SLOT( paintSpacePoints() ) );   
   connect( this, SIGNAL( cursorPositionChanged( int, int ) ), this, SLOT( paintSpacePoints(int, int) ) );
   connect( this, SIGNAL( textChanged() ), this, SLOT( emittedTextChanged() ) );
}

MsgMultiLineEdit::~MsgMultiLineEdit ()
{
    if(highlighter)
	delete highlighter;
}

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;
   
   emit signalSyntaxHighlightingChanged (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;

   highlighter->setHighlightColor( KBabelHighlighter::Tag, tagColor );
   highlighter->setHighlightColor( KBabelHighlighter::Entity, accelColor );
   highlighter->setHighlightColor( KBabelHighlighter::CFormat, cformatColor );
   highlighter->setHighlightColor( KBabelHighlighter::Masked, quoteColor );
   
   update();
}


void MsgMultiLineEdit::setFont(const QFont& font)
{
   KTextEdit::setFont(font);
   
   // we don't need to calculate a special offset for non-breaking space, since
   // they are very similar in size
   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;
  highlighter->setHighlightColor( KBabelHighlighter::Normal, color );
}

void MsgMultiLineEdit::setErrorColor(const QColor &color )
{
  _errorColor = color;
  highlighter->setHighlightColor( KBabelHighlighter::Error, color );
}

void MsgMultiLineEdit::setCurrentColor(const TextColor color)
{
  if( color == NormalColor ) {
    _currentColor = _textColor;
    highlighter->setHasErrors( false );
  } else {
    _currentColor = _errorColor;
    highlighter->setHasErrors( true );
  }
  
  /*
  setUpdatesEnabled(false);
  // need to block signals (especially textChanged() to avoid recursion with KBabelView::autoCheck
  blockSignals(true);
  selectAll();
  setColor( _currentColor );
  removeSelection();
  setColor(_currentColor);
  blockSignals(false);
  setUpdatesEnabled(true);
  */
  forceUpdate();
}

void MsgMultiLineEdit::setSpellChecker(KSpell* spell)
{
    highlighter->setSpellChecker(spell);
}

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

void MsgMultiLineEdit::paintSpacePoints()
{
    QRect r;
    QPainter painter(viewport() );
    const QFontMetrics& fm = fontMetrics();

    int paranum = paragraphAt(QPoint(contentsX(), contentsY()));    

    if( _spacePoints )
    {
        int curpara = paranum;
        painter.setPen( _currentColor );
        QPixmap* ws, *wsnb;

        if( _currentColor== _errorColor )
        {
            ws = _errorWhitespace;
            wsnb = _errorWhitespaceNB;
        }
        else
        {
            ws = _whitespace;
            wsnb = _whitespaceNB;
        }

        while( curpara < paragraphs())
        {
            if ( paragraphRect( curpara ).top() > contentsY()+visibleHeight()) break;

            const QString& s = text(curpara);
            int i = s.find( " " );
            while( (i >= 0) && (i < (int)s.length()-1) ) // -1 because text will end by EOLN
            {
                QPixmap* pm = ( s.at(i).unicode() == 0x00A0U ) ? wsnb : ws;
                QRect r = mapToView( curpara, i );
                r.moveBy( r.width()/2, (r.height() - fm.descent())/2 );
                r.moveBy( -pm->rect().width()/2, -pm->rect().height()/2-1 );
                bitBlt(viewport(), r.topLeft(), pm, pm->rect(), Qt::CopyROP);
                i = s.find( " ", i+1 );
            }
            ++curpara;
        }
    }

    if( _quotes )
    {
        QFontMetrics fm( font());
        QRect qs = fm.boundingRect("\"");

        for( int curpara = paranum; curpara < paragraphs() ; curpara++ )
        {
            r = paragraphRect(curpara);
            if( r.y() > contentsY()+visibleHeight() ) break;

            painter.drawText( QPoint( 0, mapToView( curpara, 0 ).top()) +
                    QPoint(0, qs.height() + 4), "\""); // 4 = hardcoded margin in QT
            painter.drawText( mapToView( curpara, QMAX( 0,
                ((int)text( curpara ).length())-1)).topRight() +
                QPoint(0, qs.height() + 4), "\"");  // 4 = hardcoded margin in QT
        }
    }

    if( _showDiff && (!_diffUnderlineAdd || !_diffStrikeOutDel) )
    {
        if( paragraphs() == (int)diffPos.size() ) // sanity check
        {
            painter.setRasterOp( Qt::AndROP );
            for( int curpara = paranum; curpara < paragraphs() ; curpara++ )
            {
                r = paragraphRect(curpara);
                if( r.y() > contentsY()+visibleHeight() ) break;

                QValueList<DiffInfo> *list = diffPos[curpara];
                if(list)
                {
                    QValueList<DiffInfo>::ConstIterator it;
                    for(it = list->begin(); it != list->end(); ++it)
                    {
                        QRect beg =  mapToView( curpara, (*it).begin );
                        QRect end =  mapToView( curpara, (*it).end );

                        QColor* c = 0;
                        if( (*it).add  && !_diffUnderlineAdd)
                            c = &_diffAddColor;
                        else if(!(*it).add && !_diffStrikeOutDel)
                            c = &_diffDelColor;

                        if ( c != 0 )
                        {
                            // Single or multiple lines?
                            if ( beg.top() == end.top())
                            {
                                painter.fillRect( QRect( beg.topLeft(),
                                    QPoint( end.right(), end.bottom())), *c );
                            }
                            else
                            {
                                painter.fillRect( QRect(
                                    beg.topLeft(),
                                    QPoint( r.right(), beg.bottom())), *c );
				if( end.top()-beg.bottom() > 2 ) {
				    // there is a line, not only thin space
                            	    painter.fillRect( QRect(
                                	QPoint( r.left(), beg.bottom()),
                                	QPoint( r.right(), end.top())), *c );
				}
                                painter.fillRect( QRect(
                                    QPoint( r.left(), end.top()),
                                    QPoint( end.right(), end.bottom())), *c );
                            }
                        }
                    }
                }
            }
        }
    }

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

                QValueList<DiffInfo> *list = diffPos[curpara];
                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( curpara, (*it).begin );
                        QRect end = mapToView( curpara, (*it).end );

                        QPen* p = 0;
                        int dy = 0;
                        if( (*it).add && _diffUnderlineAdd)
                            p = &addPen;
                        else if(!(*it).add && _diffStrikeOutDel)
                        {
                            p = &delPen;
                            dy = fm.ascent()/2-1;
                        }

                        if ( p != 0 )
                        {
                            painter.setPen( *p );

                            // Single or multiple lines?
                            if ( beg.top() == end.top())
                                painter.drawLine(
                                    beg.topLeft() + QPoint(0, fm.ascent()-dy),
                                    end.topRight()+ QPoint(0, fm.ascent()-dy));
                            else
                            {
                                int y = beg.top() + fm.ascent();
                                painter.drawLine(
                                    QPoint(beg.left(), y),
                                    QPoint(r.right(), y));
                                y += fm.lineSpacing();
                                while (y < end.top() + fm.ascent())
                                {
                                    painter.drawLine(
                                        QPoint(r.left(), y),
                                        QPoint(r.right(), y));
                                    y += fm.lineSpacing();
                                }
                                painter.drawLine(
                                    QPoint(r.left(), end.top() + fm.ascent()),
                                    QPoint(end.right(), end.top() + fm.ascent()));
                            }
                        }
                    }
                }
            }
        }
    }
}

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

void MsgMultiLineEdit::forceUpdate()
{
    _firstChangedLine=0;
    _lastChangedLine=paragraphs()-1;
    highlighter->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();
}

QRect MsgMultiLineEdit::mapToView( int para, int index )
{
    if( para < 0 || para > paragraphs() ||
        index < 0 || index > paragraphLength(para) )
            return QRect(); //invalid rectangle

    const QFontMetrics& fm = fontMetrics();
    const QString& paratext = text(para);

    // Find index of the first character on the same line as parameter
    // 'index' using binary search. Very fast, even for long texts.
    int linestart = 0;
    int indexline = lineOfChar( para, index );
    if ( indexline > 0 )
    {
        int min = 0, max = index;
        int i = (min + max)/2;
        int iline = lineOfChar( para, i );
        while ( iline != indexline-1 ||
                lineOfChar( para, i+1 ) != indexline )
        {
            Q_ASSERT( min != max && min != i && max != i );
            if ( iline < indexline )
                min = i;
            else
                max = i;
            i = (min + max)/2;
            iline = lineOfChar( para, i );
        }
        linestart = i+1;
    }
    Q_ASSERT( linestart >= 0 );
    
    int linewidth;
    
    // if the tag is not valid, easy
    if( (_tagStartPara == _tagEndPara) && (_tagStartIndex == _tagEndIndex) ) {
	linewidth = fm.width( paratext.mid( linestart, index-linestart ));
    } else {
	int tso = pos2Offset( _tagStartPara, _tagStartIndex );
	int teo = pos2Offset( _tagEndPara, _tagEndIndex );
	int off = pos2Offset( para, index );
    
	if( off < tso ) {
	    // it is surely before the tag
	    linewidth = fm.width( paratext.mid( linestart, index-linestart ));
	} else if( off >= teo ) {
	    // it is surely after the tag
	
	    // is it on the same line as the end of the tag?
	    if( _tagEndPara < para || lineOfChar( _tagEndPara, _tagEndIndex ) < indexline ) {
		// no tag on the line, no bold
		linewidth = fm.width( paratext.mid( linestart, index-linestart ));
	    } else {
		QFont f( font() );
		f.setBold( true );
		QFontMetrics bfm( f );
		// is tag single displayed line?
		if( _tagStartPara == _tagEndPara 
		    && lineOfChar( _tagStartPara, _tagStartIndex ) == lineOfChar( _tagEndPara, _tagEndIndex ) ) 
		{
		    // yes, count the non-bold before the tag start
		    linewidth = fm.width( paratext.mid( linestart, _tagStartIndex-linestart ) )
			+ bfm.width( paratext.mid( _tagStartIndex, _tagEndIndex-_tagStartIndex ) );
		} 
		else
		{
		    // count the part of the tag itself
		    linewidth = bfm.width( paratext.mid( linestart, _tagEndIndex-linestart ) );
		}
	    
		// add the rest from tag to the index
		linewidth += fm.width( paratext.mid( _tagEndIndex, index-_tagEndIndex ) );
	    }
	}
	else {
	    // in tag
	    QFont f( font() );
	    f.setBold( true );
	    QFontMetrics bfm( f );
	    // is it the first line of the tag?
	    if( para == _tagStartPara && indexline == lineOfChar( _tagStartPara, _tagStartIndex ) ) {
		// start of the line is normal
		linewidth = fm.width( paratext.mid( linestart, _tagStartIndex-linestart ) )
		    + bfm.width( paratext.mid( _tagStartIndex, index-_tagStartIndex ) );
	    } else {
		// whole is bold
		linewidth = bfm.width( paratext.mid( linestart, index-linestart ) );
	    }
	}
    }
    
    // FIXME as soon as it's possible to ask real margins from QTextEdit:
    const int left_margin = 4;
    // const int top_margin = 4;
    
    QPainter painter( viewport());
    const QRect& linerect = paragraphRect(para);
    return QRect(
        contentsToViewport( QPoint(
            left_margin + linerect.left() + linewidth , 
            /*top_margin + */linerect.top() + indexline * fm.lineSpacing() + fm.leading())),
        QSize(
            fm.charWidth( paratext, index ),
            fm.lineSpacing()
        ));
}

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(-1,UndefPart));

        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(-1,UndefPart));
        return;
    }
    else if(e->key() == Key_Tab)
    {
        insert("\\t",false);
        emit textChanged();
        e->accept();
        return;
    }
    else if((e->key() == Key_Delete && !(e->state() & ControlButton))
            || ((e->state() & ControlButton) && e->key() == Key_D) )
    {
        emit signalUndoCmd(new BeginCommand(-1,UndefPart));
        
        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(-1,UndefPart));
        emit textChanged();
        e->accept();
        return;
    }
    else if(e->key() == Key_BackSpace
            || ((e->state() & ControlButton) && e->key() == Key_H) )
    {
        emit signalUndoCmd(new BeginCommand(-1,UndefPart)); 
        
        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(-1,UndefPart));

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

        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(-1,UndefPart));
        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::emittedTextChanged()
{
    highlight();
    paintSpacePoints();    
}

void MsgMultiLineEdit::selectTag(int start, int length)
{
    setUpdatesEnabled(false);
    setSelection( _tagStartPara, _tagStartIndex, _tagEndPara, _tagEndIndex);
    setBold( false );

    offset2Pos(start, _tagStartPara, _tagStartIndex);
    offset2Pos(start+length, _tagEndPara, _tagEndIndex);
    
    setSelection( _tagStartPara, _tagStartIndex, _tagEndPara, _tagEndIndex);
    setBold( true );
    setUpdatesEnabled(true);
}

#include "mymultilineedit.moc"
