You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdesdk/kbabel/kbabel/mymultilineedit.cpp

1669 lines
45 KiB

/* ****************************************************************************
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 <tdeglobal.h>
#include <tdeglobalsettings.h>
#include <tdemessagebox.h>
#include <tdestdaccel.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, TQ_SIGNAL(selectionChanged()), this, TQ_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(TDEGlobalSettings::textColor()),
_errorColor(TQt::red),
_currentColor(TDEGlobalSettings::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, TQ_SIGNAL( signalSyntaxHighlightingChanged( bool ) ), highlighter, TQ_SLOT( setSyntaxHighlighting( bool ) ) );
connect( this, TQ_SIGNAL( selectionChanged() ), this, TQ_SLOT( paintSpacePoints() ) );
connect( this, TQ_SIGNAL( cursorPositionChanged( int, int ) ), this, TQ_SLOT( paintSpacePoints(int, int) ) );
connect( this, TQ_SIGNAL( textChanged() ), this, TQ_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;
TQt::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"