/* ****************************************************************************
  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 TQt library by Trolltech AS, Norway (or with modified versions
  of TQt that use the same license as TQt), 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
  TQt. 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 <tqpixmap.h>
#include <tqpainter.h>
#include <tqvaluelist.h>
#include <tqstringlist.h>
#include <tqregexp.h>
#include <tqclipboard.h>
#include <tqapplication.h>
#include <tqdragobject.h>
//#include <private/tqrichtext_p.h>
#include <tqpopupmenu.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, TQWidget* parent,const char* name)
                :KTextEdit(parent,name), emitUndo(true), 
		_firstChangedLine(0), 
		_lastChangedLine(0), 
        	_lastParagraph(0), 
		_lastParagraphOffset(0), 
		_lastSelectionStart(-1), 
		_lastSelectionEnd(-1),
		_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

   connect(this, TQT_SIGNAL(selectionChanged()), this, TQT_SLOT( onSelectionChanged() ) );
}

void MyMultiLineEdit::onSelectionChanged()
{
    kdDebug(KBABEL) << "MyMultiLineEdit::onSelectionChanged" << endl;
    int parFrom, parTo, indexFrom, indexTo;
    if ( hasSelectedText() ) {
        getSelection( &parFrom, &indexFrom, &parTo, &indexTo );
        kdDebug(KBABEL) << "parFrom=" << parFrom << "indexFrom=" << indexFrom << "parTo=" << parTo << "indexTo=" << indexTo << endl;
        _lastSelectionStart = beginOfMarkedText();
    }
    else
    {
        _lastSelectionStart = -1; // no selection
        _lastSelectionEnd = -1;
    }

    //kdDebug(KBABEL) << "_lastSelectionStart=" << _lastSelectionStart << endl;
}

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;

  TQPalette _visibleHighlight( palette() );
  TQPalette _invisibleHighlight( palette() );
  TQColorGroup newcg( colorGroup() );
  newcg.setColor( TQColorGroup::HighlightedText, newcg.text() );
  newcg.setColor( TQColorGroup::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::beginOfLastMarkedText()
{
    if ( _lastSelectionStart != -1 )
        return _lastSelectionStart;
    else
        return currentIndex();
}

int MyMultiLineEdit::endOfLastMarkedText()
{
    if ( _lastSelectionEnd != -1 )
        return _lastSelectionEnd;
    else
        return currentIndex();
}

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(TQWheelEvent *e)
{
    e->ignore();
}

void MyMultiLineEdit::focusInEvent(TQFocusEvent *e)
{
    KTextEdit::focusInEvent(e);
    emitCursorPosition();
}

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

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

TQPopupMenu * MyMultiLineEdit::createPopupMenu(const TQPoint &)
{
    return 0;
}

void MyMultiLineEdit::setContextMenu( TQPopupMenu * 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 );
	    TQString s = text(x);
	    if( y < (int)s.length()-1  ) // not the end of paragraph
	    {
		TQString 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 TQString& s)
{
    _lastParagraph=0;
    _lastParagraphOffset=0;
    // workaround, since insert does not interpret markup
    setTextFormat( TQt::PlainText );
    _firstChangedLine=_lastChangedLine=0;
    KTextEdit::setText(s);
    setTextFormat( TQt::AutoText );
    // now the number of lines is known, let's do highlight
    _lastChangedLine=paragraphs();
    emit textChanged();
    emitCursorPosition();
}

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

void MyMultiLineEdit::insert( const TQString & 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
   
   TQPalette _visibleHighlight( palette() );
   TQPalette _invisibleHighlight( palette() );
   TQColorGroup newcg( colorGroup() );
   newcg.setColor( TQColorGroup::HighlightedText, newcg.text() );
   newcg.setColor( TQColorGroup::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;

   TQString 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();

         TQString s= text(cursorY);
         if(cursorX != 0)
         {
            TQString 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);

         TQString s=text(cursorY);
         if(cursorX != (int)s.length()-1)
         {
            TQString 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 = TQMAX( TQMIN( (int)paragraph, paragraphs() - 1), 0 ); // Sanity check
    index  = TQMAX( TQMIN( (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 += TQMIN( lastI, index );

	return tmp;
    }
}

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

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

/*******************************************************************************/
MsgMultiLineEdit::MsgMultiLineEdit(int ID, KSpell* spell, TQWidget* 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(TQt::red),
		_currentColor(KGlobalSettings::textColor()),
		_whitespace(0),
                _hlSyntax(true),
                _quoteColor(TQt::darkGreen),
                _unquoteColor(TQt::red),
                _cformatColor(TQt::blue),
                _accelColor(TQt::darkMagenta),
                _showDiff(false),
                _diffUnderlineAdd(true),
                _diffStrikeOutDel(true),
                _diffAddColor(TQt::darkGreen),
                _diffDelColor(TQt::darkRed),
		_currentUnicodeNumber(0),
		highlighter(0),
		_tagStartPara(0), _tagStartIndex(0), _tagEndPara(0), _tagEndIndex(0)
{
   diffPos.setAutoDelete(true);
   diffPos.clear();
   
   _whitespace = new TQPixmap(2,2,-1,TQPixmap::BestOptim);
   _whitespace->fill(_textColor);
   _errorWhitespace = new TQPixmap(2,2,-1,TQPixmap::BestOptim);
   _errorWhitespace->fill(_errorColor);
   
   _whitespaceNB = new TQPixmap(3,3,-1,TQPixmap::BestOptim);
   _whitespaceNB->fill();
   _errorWhitespaceNB = new TQPixmap(3,3,-1,TQPixmap::BestOptim);
   _errorWhitespaceNB->fill();
   
   TQPainter p(_whitespaceNB);
   p.setPen( _textColor );
   p.drawEllipse(_whitespaceNB->rect());
   
   TQPainter q(_errorWhitespaceNB);
   q.setPen( _errorColor );
   q.drawEllipse(_errorWhitespaceNB->rect());

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

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

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

        TQStringList lineList = TQStringList::split('\n',s,true);
        
        int lineCounter=-1;
        bool haveAdd=false;
        bool haveDel=false;
        bool multiline=false;
        TQStringList::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)
                {
                    TQValueList<DiffInfo> *list = diffPos[lineCounter];
                    if(!list)
                    {
                        list = new TQValueList<DiffInfo>;
                        diffPos.insert(lineCounter,list);
                    }

                    list->append(di);
                }
                
            }
        }
        
        TQRegExp 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 TQColor& 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 TQColor& quoteColor, const TQColor& unquoteColor
                 , const TQColor& cformatColor, const TQColor& accelColor, const TQColor& 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 TQFont& font)
{
   KTextEdit::setFont(font);
   
   // we don't need to calculate a special offset for non-breaking space, since
   // they are very similar in size
   TQFontMetrics fm(font);
   _wsOffsetX = TQMAX(fm.width(' ')/2-2,1);
   _wsOffsetY = TQMAX(fm.height()/2-1,0);
   
   repaint();
}

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

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

    if(_showDiff)
        update();
}

void MsgMultiLineEdit::setTextColor(const TQColor &color )
{
  TQPalette p( palette() );
  TQColorGroup newcg( colorGroup() );
  newcg.setColor( TQColorGroup::Text, color );
  if( hasFocus() ) p.setActive( newcg );
  else p.setInactive( newcg );
  setPalette( p );
  _textColor = color;
  highlighter->setHighlightColor( KBabelHighlighter::Normal, color );
}

void MsgMultiLineEdit::setErrorColor(const TQColor &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()
{
    TQRect r;
    TQPainter painter(viewport() );
    const TQFontMetrics& fm = fontMetrics();

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

    if( _spacePoints )
    {
        int curpara = paranum;
        painter.setPen( _currentColor );
        TQPixmap* 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 TQString& s = text(curpara);
            int i = s.find( " " );
            while( (i >= 0) && (i < (int)s.length()-1) ) // -1 because text will end by EOLN
            {
                TQPixmap* pm = ( s.at(i).unicode() == 0x00A0U ) ? wsnb : ws;
                TQRect 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(), TQt::CopyROP);
                i = s.find( " ", i+1 );
            }
            ++curpara;
        }
    }

    if( _quotes )
    {
        TQFontMetrics fm( font());
        TQRect qs = fm.boundingRect("\"");

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

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

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

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

                        TQColor* 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( TQRect( beg.topLeft(),
                                    TQPoint( end.right(), end.bottom())), *c );
                            }
                            else
                            {
                                painter.fillRect( TQRect(
                                    beg.topLeft(),
                                    TQPoint( r.right(), beg.bottom())), *c );
				if( end.top()-beg.bottom() > 2 ) {
				    // there is a line, not only thin space
                            	    painter.fillRect( TQRect(
                                	TQPoint( r.left(), beg.bottom()),
                                	TQPoint( r.right(), end.top())), *c );
				}
                                painter.fillRect( TQRect(
                                    TQPoint( r.left(), end.top()),
                                    TQPoint( 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;

                TQValueList<DiffInfo> *list = diffPos[curpara];
                if(list)
                {
                    TQPen addPen(_diffAddColor,2);
                    TQPen delPen(_diffDelColor,2);
                    TQValueList<DiffInfo>::ConstIterator it;
                    for(it = list->begin(); it != list->end(); ++it)
                    {
                        TQRect beg = mapToView( curpara, (*it).begin );
                        TQRect end = mapToView( curpara, (*it).end );

                        TQPen* 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() + TQPoint(0, fm.ascent()-dy),
                                    end.topRight()+ TQPoint(0, fm.ascent()-dy));
                            else
                            {
                                int y = beg.top() + fm.ascent();
                                painter.drawLine(
                                    TQPoint(beg.left(), y),
                                    TQPoint(r.right(), y));
                                y += fm.lineSpacing();
                                while (y < end.top() + fm.ascent())
                                {
                                    painter.drawLine(
                                        TQPoint(r.left(), y),
                                        TQPoint(r.right(), y));
                                    y += fm.lineSpacing();
                                }
                                painter.drawLine(
                                    TQPoint(r.left(), end.top() + fm.ascent()),
                                    TQPoint(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;
    
    TQColor 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;
	
	TQRegExp markup("(\\\\)|(\")|(\\\\[abfnrtv'\"\?\\\\])|(\\\\\\d+)|(\\\\x[\\dabcdef]+)"
	    "|(%[\\ddioxXucsfeEgGphln]+)|(&[^\\s])|(&[\\w-]+;)");

	for( i = TQMAX(_firstChangedLine,0) ; i < TQMIN(_lastChangedLine+1,(uint)paragraphs()) ; i++ ) {
	
	TQString line=text(i);

	//remove old highlighting
	setSelection(i,0,i,line.length());
	setColor( _currentColor );
	removeSelection();
	
	TQColor 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;
	TQString t= text();
	
	if(_lastParagraph <= _firstChangedLine)
	{
	    index=_lastParagraph;
	    lineindex=_lastParagraphOffset;
	}
	
	for( ; index<_firstChangedLine ; index++)
	    lineindex+=paragraphLength(index)+1;
	
        TQRegExp re("<.*>");
	re.setMinimal(true);

	if( _firstChangedLine >0 )
	{
	    TQColor c;
	    TQFont f;
	   Qt::VerticalAlignment v;
	    getFormat(_firstChangedLine-1, paragraphLength(_firstChangedLine-1)-1, &f, &c, &v);
	    TQString l = text(_firstChangedLine-1);
	    if( c==_tagColor  && !l.endsWith(">") ) // hope _tagColor will be different than other colors
	    {
		TQRegExp 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( TQPainter *painter, int clipx, int clipy, int clipw, int cliph )
{
    MyMultiLineEdit::drawContents( painter, clipx, clipy, clipw, cliph );
    paintSpacePoints();
}

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

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

    const TQFontMetrics& fm = fontMetrics();
    const TQString& 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 {
		TQFont f( font() );
		f.setBold( true );
		TQFontMetrics 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
	    TQFont f( font() );
	    f.setBold( true );
	    TQFontMetrics 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 TQTextEdit:
    const int left_margin = 4;
    // const int top_margin = 4;
    
    TQPainter painter( viewport());
    const TQRect& linerect = paragraphRect(para);
    return TQRect(
        contentsToViewport( TQPoint(
            left_margin + linerect.left() + linewidth , 
            /*top_margin + */linerect.top() + indexline * fm.lineSpacing() + fm.leading())),
        TQSize(
            fm.charWidth( paratext, index ),
            fm.lineSpacing()
        ));
}

void MsgMultiLineEdit::keyPressEvent(TQKeyEvent *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);
        TQString 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 TQKeyEvent(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);
            TQString str=text(row);

            if(!str.isEmpty() && col < (int)str.length() && str.at(col) == '\\'
                        && !isMasked(&str,col))
            {
                TQString 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);
            TQString str=text(row);

            TQString 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);
        TQString 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( TQChar( 0x00a0U ) );
	e->accept();
	return;
    }
    // ALT+123 feature
    else if(( e->state() & AltButton ) && e->text()[0].isDigit() )
    {
	TQString text=e->text();
	while ( text[0].isDigit() ) {
	    _currentUnicodeNumber = 10*_currentUnicodeNumber+(text[0].digitValue());
	    text.remove( 0, 1 );
	}
    }
    else
    { 
        MyMultiLineEdit::keyPressEvent(e);
    }
}

void MsgMultiLineEdit::keyReleaseEvent(TQKeyEvent* e)
{
    if ( e->key() == Key_Alt && _currentUnicodeNumber >= 32 )
    {
        TQString text = TQChar( _currentUnicodeNumber );
	_currentUnicodeNumber=0;
        insert( text );
    }
}

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

bool MsgMultiLineEdit::isMasked(TQString *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"
