/////////////////////////////////////////////////////////////////////////////
// Name:        No names yet.
// Purpose:     Contrib. demo
// Author:      Aleksandras Gluchovas
// Modified by:
// Created:     03/04/1999
// RCS-ID:      $Id: lseditor.cpp,v 1.17 2001/04/23 19:51:11 hansvl Exp $
// Copyright:   (c) Aleksandars Gluchovas
// Licence:   	wxWindows licence
/////////////////////////////////////////////////////////////////////////////

#ifdef __GNUG__
    #pragma implementation "lseditor.cpp"
    #pragma interface "lseditor.h"
#endif

// For compilers that support precompilation, includes "wx/wx.h".
#include "wx/wxprec.h"

#ifdef __BORLANDC__
#pragma hdrstop
#endif

#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif

#include "wx/file.h"
#include "wx/textdlg.h"
#include "wx/clipbrd.h"
#include "wx/dataobj.h"
#include "wx/dcmemory.h"

#include "wx/wspp/rescrolw.h"

#include <stdio.h>

#include "wx/pmf/lseditor.h"
#include "wx/pmf/finddlg.h"

#include <memory.h>

/***** Implementation for class TBlock *****/

void TBlock::RecalcBlockProperties()
{
	char* cur = mBuf;
	char* end = mBuf + mTextLen;
	mRowCount = 0;

	while( cur < end )
	{
		if ( is_eol_char( *cur ) )
            ++mRowCount;

		++cur;
	}
}

/***** Implementation for class TTextIterator *****/

wxString TTextIterator::mSeparators = ",.()[]\t\\+-*/|=<>:;\t\n~?!%";

bool TTextIterator::IsSeparator( char ch )
{
	size_t sz = mSeparators.Length();

    size_t i;
	for( i = 0; i != sz; ++i )
    {
		if ( mSeparators[i] == ch )
            return TRUE;
    }
	return FALSE;
}

char* TTextIterator::GetClosestPos()
{
	char*  end = GetBlockEnd();
	char*  cur = mpCurRowStart;
	size_t col = 0;

	while( cur < end && col < mPos.mCol && !is_eol_char(*cur) )
	{
		if ( !is_DOS_eol_char( *cur ) )
        {
            ++col;
        }
		++cur;
	}

	if ( is_DOS_eol_char( *cur ) )
        ++cur;

	return cur;
}

char* TTextIterator::GotoClosestPos()
{
	char*  end = GetBlockEnd();
	char*  cur = mpCurRowStart;
	size_t col = 0;

	while( cur < end && col < mPos.mCol && !is_eol_char(*cur) )
	{
		if ( !is_DOS_eol_char( *cur ) )
        {
            ++col;
        }
		++cur;
	}

	mPos.mCol = col;

	if ( is_DOS_eol_char( *cur ) )
        ++cur;

	return cur;
}

TTextIterator::TTextIterator()

	: mIsEof( FALSE )
{}

bool TTextIterator::IsLastLine()
{
	TBlockIteratorT nextBlk = mBlockIter;
	++nextBlk;

	if ( nextBlk != mEndOfListIter )
        return FALSE;

	char* cur = mpCurRowStart;
	char* end = GetBlockEnd();

	while( cur < end && !is_eol_char( *cur ) )
    {
        ++cur;
    }

	if ( cur == end )
        return TRUE;

	++cur;

	return ( cur == end );
}

char TTextIterator::GetChar()
{
	char* cur = GetClosestPos();

	if ( is_DOS_eol_char( *cur ) ) 
		return *(cur+1);
	else
		return *cur;
}

bool TTextIterator::IsEol()
{
	return is_eol_char( GetChar() ) || mIsEof;
}

bool TTextIterator::IsEof()
{
	return mIsEof;
}

int TTextIterator::GetDistFromEol()
{
	return 0; // TBD::
}

void TTextIterator::NextChar()
{
	char* cur = GotoClosestPos();

	if ( cur + 1 >= GetBlockEnd() )
	{
		TBlockIteratorT nextBlk = mBlockIter;
		++nextBlk;

		if ( nextBlk == mEndOfListIter )
		{
			if ( cur != GetBlockEnd() ) 
				++mPos.mCol;	

			mIsEof = TRUE;
			return;
		}

		++mPos.mRow ;
		mPos.mCol = 0;

		mBlockIter = nextBlk;

		mFirstRowInBlock = mPos.mRow;
		mActualRow       = mPos.mRow;
		mpCurRowStart    = (*mBlockIter)->mBuf;

		mIsEof = ( (*mBlockIter)->mTextLen == 0 );
	}
	else
	{
		if ( is_eol_char( *cur ) ) 
		{
			++mPos.mRow;
			mPos.mCol = 0;
			
			mActualRow    = mPos.mRow;
			mpCurRowStart = cur + 1;
		}
		else
			++mPos.mCol;
	}

	mIsEof = (mpCurRowStart + mPos.mCol) == GetBlockEnd();
}

void TTextIterator::PreviousChar()
{
	char* cur = GotoClosestPos();

	if ( cur == (*mBlockIter)->mBuf )
	{
		TBlockIteratorT prevBlk = mBlockIter;
		--prevBlk;

		if ( prevBlk == mEndOfListIterInverse )
		{
			mIsEof = TRUE;
			return;
		}

		--mPos.mRow;

		mBlockIter = prevBlk;

		cur = GetBlockEnd() - 1;

		char* eolPos = cur;
		--cur; // skip EOL
		char* start = (*mBlockIter)->mBuf;

		while( cur != start && !is_eol_char( *cur ) )
        {
            --cur; // goto start of line
        }

		if ( is_eol_char( *cur ) )
            ++cur;

		mPos.mCol     = (size_t)(eolPos - cur);
		mpCurRowStart = cur;

		mFirstRowInBlock = mPos.mRow;
		mActualRow       = mPos.mRow;
	}
	else
	{
		do
		{
			// FIXME FIXME:: this is more than messy .... !

			if ( is_eol_char( *(cur-1) ) ) 
			{
				--cur; // goto EOL

				--mPos.mRow;

				char* eolPos = cur;
				--cur; // skip EOL
				char* start = (*mBlockIter)->mBuf;

				while( cur != start && !is_eol_char( *cur ) )
                {
                    --cur; // goto start of line
                }

				if ( is_eol_char( *cur ) )
                    ++cur;

				mPos.mCol     = (size_t)(eolPos - cur);
				mpCurRowStart = cur;

				if ( eolPos != cur && is_DOS_eol_char( *(eolPos-1) ) )
                    --mPos.mCol;
				
				mActualRow    = mPos.mRow;

				break;
			}
			else
			if ( is_DOS_eol_char( *(cur-1) ) )
			{
				--cur;

				if ( cur != (*mBlockIter)->mBuf && is_eol_char( *(cur-1) ) )
					continue;
				else
				{
					--mPos.mCol;
					--cur;
				}
			}
			else
			{
				--mPos.mCol;
				break;
			}

		} while( 1 );
	}

	mIsEof = (mpCurRowStart + mPos.mCol) == GetBlockEnd();
}

void TTextIterator::NextWord()
{
	GotoClosestPos();

	// skip non-white space ahead

	bool wasSeparator = IsSeparator( GetChar() );

	while( !IsEof() )
	{
		char ch = GetChar();

		if ( ch == ' '  || 
			 ch == '\t' || 
			 is_eol_char(ch) ||
			 wasSeparator != IsSeparator(ch) )

			break;

		NextChar();
	}

	// skip all white stpace if any
	while( !IsEof() )
	{
		char ch = GetChar();

		if ( ch != ' ' && ch != '\t' && !is_eol_char(ch) )

			break;

		NextChar();
	}
}

void TTextIterator::PreviousWord()
{
	GotoClosestPos();

	PreviousChar();

	// skip all white stpace if any
	while( !IsEof() )
	{
		char ch = GetChar();

		if ( ch != ' ' && ch != '\t' && !is_eol_char(ch) )

			break;

		PreviousChar();
	}

	bool wasSeparator = IsSeparator( GetChar() );

	// skip word;
	while( !IsEof() )
	{
		char ch = GetChar();

		if ( ch == ' '       || 
			 ch == '\t'      || 
			 is_eol_char(ch) ||
			 wasSeparator != IsSeparator(ch) 
		   )
		{
			NextChar();
			break;
		}

		PreviousChar();
	}
}

void TTextIterator::ToEndOfLine()
{
	GotoClosestPos();

	while( !IsEof() )
	{
		char ch = GetChar();

		if ( is_eol_char( ch ) )
            break;

		NextChar();
	}
}

void TTextIterator::ToStartOfLine()
{
	GotoClosestPos();

	mPos.mCol = 0;
	mPos.mRow = mActualRow;
}

size_t TTextIterator::GetLineLen()
{
	char* cur = mpCurRowStart;
	char* end = GetBlockEnd();

	size_t len = 0;

	while( cur < end && !is_eol_char( *cur ) )
	{
		if ( !is_DOS_eol_char( *cur ) )
            ++len;
		++cur;
	}

	return len;
}

TPosition TTextIterator::GetPosition()
{
	return mPos;
}

bool TTextIterator::IsInLastBlock()
{
	TBlockIteratorT next = mBlockIter;
	++next;

	return next == mEndOfListIter;
}

bool TTextIterator::DetectUnixText()
{
	char* cur = GetBlockStart();
	char* end = GetBlockEnd();

	bool isUnixText = IS_UNIX_TEXT_BY_DEFAULT;

	while( cur < end ) 
	{
		if ( is_DOS_eol_char( *cur ) )
            return FALSE;

		if ( is_eol_char( *cur ) )
            return TRUE;

		++cur;
	}

	return isUnixText;
}

/***** Implementation for class TCppJavaHighlightListener *****/

void TCppJavaHighlightListener::OnTextChanged( wxTextEditorModel* pModel, 
											   size_t atRow, size_t nRows, 
											   TEXT_CHANGE_TYPE ct ) 
{
	mpModel = pModel;
	
	/*

	int state = GetStateAtRow( atRow );

	if ( ct == CT_INSERTED )
	{
		RemoveCommentTags( atRow, atRow + nRows + 1 );
		GenerateTagsForRange( atRows, atRows + nRows + 1 );
	}
	else
	if ( ct == CT_DELETED )
	{
		RemoveCommentTags( atRow, atRow + 1 );
		GenerateTagsForRange( atRows, atRows + 1 );
	}
	*/
}

/***** Implementation for class wxTextEditorModel *****/

/*** protected methods ***/

size_t wxTextEditorModel::GetLineCountInRange( char* from, char* till )
{
	size_t nLines = 0;

	while( from != till )
	{
		if ( is_eol_char( *from ) )
            ++nLines;

		++from;
	}

	return nLines;
}

void wxTextEditorModel::DoInsertText( const TPosition& pos, 
									  char* text, size_t len,
									  TRange& actualRange )
{
	// FOR NOW:: very dummy imp.

	char* end = text + len;

	TTextIterator iter = CreateIterator( pos );

	TBlock& blk = *(*iter.mBlockIter);

	char* cur = text;

	char*  insertPos = iter.GotoClosestPos();
	actualRange.mFrom = iter.GetPosition();

	if ( is_eol_char( *insertPos ) &&
		 insertPos != iter.GetBlockStart() &&
		 is_DOS_eol_char( *(insertPos-1) )
	   )
	   --insertPos;

	size_t sizeAfter = (size_t)(iter.GetBlockEnd() - insertPos);

	size_t nLines = GetLineCountInRange( text, text + len );

	if ( blk.mTextLen + len < FILLED_BLOCK_LEN )
	{
		memmove( insertPos + len, insertPos, sizeAfter );

		memcpy( insertPos, text, len );

		blk.mTextLen += len;
		

		blk.RecalcBlockProperties();

		if ( iter.IsInLastBlock() )

			++blk.mRowCount; // last block have always the-last-row-to-spare -
		                     // the "nature" of most text editors

		char* endPos = insertPos + len;

		bool found = FALSE;

		/*
		// OLD STUFF:: slow & buggy

		while( !iter.IsEof() )
		{
			if ( iter.GetClosestPos() == endPos )
			{
				actualRange.mTill = iter.GetPosition();
				found = TRUE;
				break;
			}

			iter.NextChar();
		}

		if ( !found )
		{
			actualRange.mTill = iter.GetPosition();
			++actualRange.mTill.mCol;
			
			wxASSERT( found ); // DBG::
		}
		*/

		actualRange.mTill       = actualRange.mFrom;
		actualRange.mTill.mRow += nLines;

		if ( nLines == 0 )
			actualRange.mTill.mCol = actualRange.mFrom.mCol + (len);
		else
		{
			cur = end;

			while( cur != insertPos && !is_eol_char( *cur ) )
            {
				--cur;
            }

			if ( is_eol_char( *cur ) )
                ++cur;

			actualRange.mTill.mCol = (int)(end - cur);
		}

		NotifyTextChanged( pos.mRow, nLines, CT_INSERTED );
	}
	else
	{
		// TBD:::

		char buf[16];
		sprintf( buf, "%d", FILLED_BLOCK_LEN );
		wxString msg = "Sorry!!! Currently editor is limited to files less than ";
		msg += buf;
		msg += " bytes\n(the requested text length is " + 
		sprintf( buf, "%d", blk.mTextLen + len );
		msg += buf; 
		msg += " bytes)\n Please, close this file without making any changes.";

		wxMessageBox( msg );

		GetActiveView()->SetFocus();

		int avoidCompilerWarning = 0;
		wxASSERT(avoidCompilerWarning); // DBG:: for now
	}
}

void wxTextEditorModel::DoDeleteRange( const TPosition& from, const TPosition& till,
									   TRange& actualRange
									 )
{
	// FOR NOW:: very dummy imp.

	TTextIterator iterFrom = CreateIterator( from );
	TTextIterator iterTill = CreateIterator( till );

	if ( iterFrom.mBlockIter == iterTill.mBlockIter )
	{
		char* fromPos = iterFrom.GotoClosestPos();
		char* tillPos = iterTill.GotoClosestPos();
		char* blockStart = (*iterFrom.mBlockIter)->mBuf;
		
		if ( is_eol_char( *fromPos ) &&
			 fromPos != blockStart   &&
			 is_DOS_eol_char( *(fromPos-1) )
		   )
		   --fromPos;

		if ( is_eol_char( *tillPos ) &&
			 tillPos != blockStart   &&
			 is_DOS_eol_char( *(tillPos-1) )
		   )
		   --tillPos;

		size_t len = (size_t)( tillPos -fromPos );
		
		size_t nLines = GetLineCountInRange( fromPos, fromPos + len );

		size_t sizeAfter = (size_t)(iterFrom.GetBlockEnd() - tillPos);

		memmove( fromPos, tillPos, sizeAfter );

		(*iterFrom.mBlockIter)->mTextLen -= len;

		(*iterFrom.mBlockIter)->RecalcBlockProperties();

		if ( iterFrom.IsInLastBlock() )

			++(*iterFrom.mBlockIter)->mRowCount; // last block have always the-last-row-to-spare -
												// the "nature" of most text editors

		actualRange.mFrom = iterFrom.GetPosition();
		actualRange.mTill = iterTill.GetPosition();

		NotifyTextChanged( from.mRow, nLines, CT_DELETED );
	}
	else
	{
		int avoidCompilerWarning = 0;
		wxASSERT(avoidCompilerWarning); // DBG:: for now
	}
}

void wxTextEditorModel::GetTextFromRange( const TPosition& from, const TPosition& till, 
										  char** text, size_t& textLen 
										)
{
	TTextIterator iterFrom = CreateIterator( from );
	TTextIterator iterTill = CreateIterator( till );

	if ( iterFrom.mBlockIter == iterTill.mBlockIter )
	{
		char* blockStart = (*iterFrom.mBlockIter)->mBuf;

		char* fromPos = iterFrom.GetClosestPos();
		char* tillPos = iterTill.GetClosestPos();

		if ( is_eol_char( *fromPos ) &&
			 fromPos != blockStart   &&
			 is_DOS_eol_char( *(fromPos-1) )
		   )
		   --fromPos;

		if ( is_eol_char( *tillPos ) &&
			 tillPos != blockStart   &&
			 is_DOS_eol_char( *(tillPos-1) )
		   )
		   --tillPos;

		textLen = (size_t)( tillPos -fromPos );

		*text = AllocCharacters( textLen );

		memcpy( *text, fromPos, textLen );
	}
	else
	{
		int avoidCompilerWarning = 0;
		wxASSERT(avoidCompilerWarning); // DBG:: for now
	}
}

void wxTextEditorModel::LoadTextFromFile( const wxString& fname )
{
	wxASSERT( wxFile::Exists( fname ) );

	DeleteAllText();

	wxFile fl( fname );

	char* buf = AllocCharacters( fl.Length() );

	fl.Read( buf, fl.Length() );

	TRange result;
	DoInsertText( TPosition( 0,0 ), buf, fl.Length(), result );

	FreeCharacters( buf );

	TTextIterator iter = CreateIterator( TPosition( 0,0 ) );

	mIsUnixText = iter.DetectUnixText();

	ClearUndoBuffer();

	NotifyAllViews();
}

void wxTextEditorModel::SaveTextToFile( const wxString& fname )
{
	wxFile fl( fname, wxFile::write );

	char* text = 0;
	size_t len = 0;

	GetTextFromRange( TPosition(0,0), TPosition( GetTotalRowCount()+1,0 ), &text, len );

	fl.Write( text, len );
	fl.Close();

	FreeCharacters( text );
}

void wxTextEditorModel::NotifyTextChanged( size_t atRow, size_t nRows, TEXT_CHANGE_TYPE ct )
{
	if ( nRows > 0  )
		MergeChange( atRow, mRowsPerPage );
	else
		MergeChange( atRow, 1 );

	// reposition bookmarks

	if ( nRows > 0 )
	{
		if ( ct == CT_INSERTED )
		{
			size_t curPin = FindNextPinFrom( atRow + 1 );

			while( curPin != NPOS )
			{
				mPins[curPin]->mRow += nRows;

				++curPin;

				if ( curPin == mPins.Count() )
                    break;
			}
		}
		else
		if ( ct == CT_DELETED )
		{
			size_t curPin  = FindNextPinFrom( atRow + 1 );
			size_t fromPin = curPin;
			size_t tillRow = atRow + nRows;

			while( curPin != NPOS && mPins[curPin]->mRow < tillRow )
			{
				++curPin;

				if ( curPin == mPins.Count() )
                    break;
			}

			if ( fromPin != NPOS && nRows != 0 )
			{
                size_t i;
				for( i = 0; i != curPin - fromPin; ++i )
                {
					mPins.Remove( fromPin );
					//mPins.erase( &mPins[fromPin], &mPins[curPin] );
                }

				while( curPin < mPins.Count() )
				{
					mPins[curPin]->mRow -= nRows;
					++curPin;
				}
			}
		}
	}


	// send notifications

    size_t i;
	for( i = 0; i != mChangeListeners.Count(); ++i )
    {
		mChangeListeners[i]->OnTextChanged( this, atRow, nRows, ct );
    }
}

void wxTextEditorModel::NotifyTextChanged( TPosition from, TPosition till, TEXT_CHANGE_TYPE ct )
{
	ArrangePositions( from, till );

	NotifyTextChanged( from.mRow, till.mRow - from.mRow + 1, ct );
}

void wxTextEditorModel::DoExecuteNewCommand( TCommand& cmd )
{
	if ( cmd.mType == TCMD_INSERT )
	{
		cmd.mPrePos = mCursorPos;
		DoInsertText( cmd.mRange.mFrom, cmd.mData, cmd.mDataLen, cmd.mRange );
	}
	else
	if ( cmd.mType == TCMD_DELETE )
	{
		cmd.mPrePos = mCursorPos;
		DoDeleteRange( cmd.mRange.mFrom, cmd.mRange.mTill, cmd.mRange );
	}
}

void wxTextEditorModel::DoReexecuteCommand( TCommand& cmd )
{
	NotifyTextChanged( mCursorPos.mRow, 1, CT_MODIFIED ); // indicate update of current cursor position

	if ( cmd.mType == TCMD_INSERT )
	{
		DoInsertText( cmd.mRange.mFrom, cmd.mData, cmd.mDataLen, cmd.mRange );
		mCursorPos = cmd.mPostPos;
	}
	else
	if ( cmd.mType == TCMD_DELETE )
	{
		DoDeleteRange( cmd.mRange.mFrom, cmd.mRange.mTill, cmd.mRange );
		mCursorPos = cmd.mPostPos;
	}

	NotifyTextChanged( mCursorPos.mRow, 1, CT_MODIFIED ); // indicate update of current cursor position
}

void wxTextEditorModel::DoUnexecuteCommand( TCommand& cmd )
{
	NotifyTextChanged( mCursorPos.mRow, 1, CT_MODIFIED ); // indicate update of current cursor position
	
	if ( cmd.mType == TCMD_INSERT )
	{
		DoDeleteRange( cmd.mRange.mFrom, cmd.mRange.mTill, cmd.mRange );
		mCursorPos = cmd.mPrePos;
	}
	else					
	if ( cmd.mType == TCMD_DELETE )
	{
		DoInsertText( cmd.mRange.mFrom, cmd.mData, cmd.mDataLen, cmd.mRange );
		mCursorPos = cmd.mPrePos;
	}

	NotifyTextChanged( mCursorPos.mRow, 1, CT_MODIFIED ); // indicate update of current cursor position
}

void wxTextEditorModel::UndoImpl()
{
	--mCurCommand;

	DoUnexecuteCommand( *mCommands[mCurCommand] );
}

void wxTextEditorModel::RedoImpl()
{
	DoReexecuteCommand( *mCommands[mCurCommand] );

	++mCurCommand;
}

void wxTextEditorModel::ExecuteCommand( TCommand* pCmd )
{
	if ( mCurCommand < mCheckPointCmdNo )

		// new command is executed before the checkpoint,
		// and every thing is sliced - invalidate it

		mCheckPointDestroyed = TRUE;

	// slice undo-able commands ahead in the queue,
	// they wont ever be reexecuted

	while( mCommands.Count() > mCurCommand )
	{
		delete mCommands[ mCommands.Count() - 1 ];

		mCommands.Remove( mCommands.Count() - 1 );
	}

	mCommands.Add( pCmd );

	DoExecuteNewCommand( *pCmd );
	++mCurCommand;
}

bool wxTextEditorModel::CanPrependCommand( TCommand* pCmd )
{
	if ( mCommands.Count() != mCurCommand ||
		 mCommands.Count() == 0 ) 
		 
		 return FALSE;

	TCommand& prevCmd = *mCommands[ mCommands.Count() - 1 ];

	if ( !(prevCmd.mRange.mTill == pCmd->mRange.mFrom) ) 

		return FALSE;

	char prevCh = prevCmd.mData[ prevCmd.mDataLen - 1];
	char curCh  = pCmd->mData[0];

	if ( prevCh == curCh )
        return TRUE;

	if ( prevCh == ' ' || curCh == ' ')
        return FALSE;

	if ( TTextIterator::IsSeparator(prevCh) !=
		 TTextIterator::IsSeparator(curCh) )

		 return FALSE;
	
	return TRUE;
}

void wxTextEditorModel::PrependCommand( TCommand* pCmd )
{
	if ( mCheckPointCmdNo == mCurCommand )

		mCheckPointDestroyed = TRUE;

	TCommand& prevCmd = *mCommands[ mCommands.Count() - 1 ];

	DoExecuteNewCommand( *pCmd );

	TCommand* pComb = new TCommand();

	pComb->mType    = TCMD_INSERT;
	pComb->mDataLen = prevCmd.mDataLen + pCmd->mDataLen;

	pComb->mData        = AllocCharacters( pComb->mDataLen );
	pComb->mRange.mFrom = prevCmd.mRange.mFrom;
	pComb->mRange.mTill = pCmd->mRange.mTill;
	pComb->mPrePos      = prevCmd.mPrePos;
	pComb->mPostPos     = pCmd->mPostPos;

	memcpy( pComb->mData, prevCmd.mData, prevCmd.mDataLen );
	memcpy( pComb->mData + prevCmd.mDataLen, pCmd->mData, pCmd->mDataLen );

	FreeCharacters( prevCmd.mData );
	FreeCharacters( pCmd->mData );

	delete &prevCmd;
	delete pCmd;

	mCommands[ mCommands.Count() - 1 ] = pComb;
}

void wxTextEditorModel::SetPostPos( const TPosition& pos )
{
	mCommands[mCurCommand-1]->mPostPos = pos;
}

bool wxTextEditorModel::SelectionIsEmpty()
{
	return mSelectionStart == mSelectionEnd;
}

void wxTextEditorModel::StartBatch()
{
	// TBD::
}

void wxTextEditorModel::FinishBatch()
{
	// TBD::
}

void wxTextEditorModel::DeleteRange( const TPosition& from, const TPosition& till )
{
	TCommand* pCmd = new TCommand();

	pCmd->mType     = TCMD_DELETE;

	pCmd->mRange.mFrom = from;
	pCmd->mRange.mTill = till;
	pCmd->mPrePos      = mCursorPos;

	GetTextFromRange( from, till, &pCmd->mData, pCmd->mDataLen );

	ExecuteCommand( pCmd );
}

void wxTextEditorModel::InsertText( const TPosition& pos, const char* text, size_t len )
{
	TCommand* pCmd = new TCommand();

	pCmd->mType     = TCMD_INSERT;

	pCmd->mRange.mFrom = pos;

	pCmd->mData     = AllocCharacters( len, text ), 
	pCmd->mDataLen  = len;
	pCmd->mPrePos   = mCursorPos;

	ExecuteCommand( pCmd );

	NotifyAllViews();
}

void wxTextEditorModel::InsertText( const TPosition& pos, const wxString& text )
{
	InsertText( pos, (const char*)text, text.Length() );
}

void wxTextEditorModel::AppendText( const wxString& text )
{
	InsertText( TPosition( GetTotalRowCount(), 0 ) , 
				(const char*)text, text.Length() );
}

void wxTextEditorModel::WriteText( const wxString& text )
{
	InsertText( GetCursor(), text.c_str(), text.Length() );

	NotifyAllViews();
}

void wxTextEditorModel::DeleteSelection()
{
	DeleteRange( mSelectionStart, mSelectionEnd );

	ResetSelection();	
}

bool wxTextEditorModel::IsLastLine( const TPosition& pos )
{
	return FALSE;
}

TTextIterator wxTextEditorModel::CreateIterator( const TPosition& pos )
{
	size_t curRow = 0;

	TBlockIteratorT bIter = &mBlocks[0];

	TTextIterator tIter;

	TBlockIteratorT endIter = &mBlocks[ mBlocks.Count()-1 ] + 1;

	while( bIter != endIter )
	{
		TBlockIteratorT nextBlk = bIter;
		++nextBlk;

		if ( nextBlk == endIter ||
			 ( pos.mRow >= curRow && 
			   pos.mRow <= curRow + (*bIter)->mRowCount )
		   )
		{
			tIter.mFirstRowInBlock = curRow;

			char* cur = (*bIter)->mBuf;
			char* end = cur + (*bIter)->mTextLen;

			// slightly optimized

			if ( curRow < pos.mRow )
			{
				while( cur < end )
				{
					if ( is_eol_char( *cur ) ) 
					{
						++curRow;

						if ( !(curRow < pos.mRow) ) 
						{
							++cur;
							break;
						}
					}

					++cur;
				}
			}

			tIter.mActualRow    = curRow;
			tIter.mpCurRowStart = cur;
			tIter.mPos          = pos;

			// FOR NOW:: positioning past the end of file is not supported
			tIter.mPos.mRow     = curRow;

			tIter.mBlockIter     = bIter;
			tIter.mEndOfListIter = &mBlocks[ mBlocks.Count()-1 ] + 1;
			tIter.mEndOfListIterInverse = &mBlocks[ 0 ] - 1;

			break;
		}
		else
		{
			curRow += (*bIter)->mRowCount;
			++bIter;
		}
	}

	return tIter;
}

void wxTextEditorModel::ArrangePositions( TPosition& upper, TPosition& lower )
{
	if ( upper > lower )
	{
		TPosition tmp( lower );
		lower = upper;
		upper = tmp;
	}
}

void wxTextEditorModel::ArrangePositions( size_t& upper, size_t& lower )
{
	if ( upper > lower )
	{
		size_t tmp = lower;
		lower = upper;
		upper = tmp;
	}
}

void wxTextEditorModel::MergeChange( size_t fromRow, size_t nRows )
{
	if ( mTextChanged == FALSE )
	{
		mChangedFromRow = fromRow;
		mChangedTillRow = fromRow + nRows;
		mTextChanged    = TRUE;
	}
	else
	{
		if ( mChangedFromRow > fromRow )

			mChangedFromRow = fromRow;

		if ( mChangedTillRow < fromRow + nRows )

			mChangedTillRow = fromRow + nRows;
	}
}

void wxTextEditorModel::TrackSelection()
{
	if ( !mIsSelectionEditMode )
        return;

	if ( mPrevCursorPos == mSelectionStart )
		mSelectionStart = mCursorPos;
	else
		mSelectionEnd   = mCursorPos;
		
	ArrangePositions( mSelectionStart, mSelectionEnd );

	NotifyTextChanged( mSelectionStart, mPrevSelectionStart, CT_MODIFIED );
	NotifyTextChanged( mSelectionEnd,   mPrevSelectionEnd,   CT_MODIFIED );
}

void wxTextEditorModel::CheckSelection()
{
	ArrangePositions( mSelectionStart, mSelectionEnd );

	if ( mIsSelectionEditMode && SelectionIsEmpty() )
	{
		mSelectionStart = mCursorPos;
		mSelectionEnd   = mCursorPos;
	}

	if ( !mIsSelectionEditMode && !SelectionIsEmpty() )
	{
		ResetSelection();
	}

	mPrevSelectionStart = mSelectionStart;
	mPrevSelectionEnd   = mSelectionEnd;
	mPrevCursorPos      = mCursorPos;
}

void wxTextEditorModel::ResetSelection()
{
	if ( SelectionIsEmpty() )
        return;

	MergeChange( mSelectionStart.mRow, 
				 mSelectionEnd.mRow - mSelectionStart.mRow + 1 );

	NotifyTextChanged( mSelectionStart, mSelectionEnd, CT_MODIFIED );

	mSelectionStart = TPosition(0,0);
	mSelectionEnd   = TPosition(0,0);

}

void wxTextEditorModel::ClearUndoBuffer()
{
    size_t i;
	for( i = 0; i != mCommands.Count(); ++i )
	{
		TCommand& cmd = *mCommands[i];

		if ( cmd.mData )
            delete [] cmd.mData;

		delete &cmd;
	}

	mCommands.Clear();

	mCurCommand = 0;
}

void wxTextEditorModel::GetAllText( char** text, size_t& textLen )
{
	GetTextFromRange( TPosition(0,0), TPosition( GetTotalRowCount()+1, 0 ),
		              text, textLen
		            );
}

void wxTextEditorModel::GetTextFromRange( const TPosition& from, 
										  const TPosition& till, wxString& toStr )
{
	char* text = NULL; size_t len;
	GetTextFromRange( TPosition(0,0), TPosition( GetTotalRowCount()+1, 0 ),
		              &text, len
		            );

	char tmp = text[len];
	text[len] = '\0';

	toStr = "";
	toStr.append( text, len );

	text[len] = tmp;

	FreeCharacters( text );
}

wxString wxTextEditorModel::GetAllText()
{
	char* text = NULL; size_t len;
	GetTextFromRange( TPosition(0,0), TPosition( GetTotalRowCount()+1, 0 ),
		              &text, len
		            );

	char tmp = text[len];
	text[len] = '\0';
	wxString str( text, 0, len );
	text[len] = tmp;

	FreeCharacters( text );

	return str;
}

void wxTextEditorModel::DeleteAllText()
{
	ResetSelection();

	DeleteRange( TPosition(0,0), TPosition( GetTotalRowCount()+1, 0 ) );
}

void wxTextEditorModel::SetSelectionEditMode( bool editIsOn )
{
	mIsSelectionEditMode = editIsOn;
}

size_t wxTextEditorModel::GetTotalRowCount()
{
	size_t nRows = 0;

    size_t i;
	for( i = 0; i != mBlocks.Count(); ++i )

		nRows += mBlocks[i]->mRowCount;

	return nRows;
}

void wxTextEditorModel::GetSelection( char** text, size_t& textLen )
{
	GetTextFromRange( GetStartOfSelection(), GetEndOfSelection(), text, textLen );
}

void wxTextEditorModel::NotifyView()
{
	mpActiveView->OnModelChanged();
}

void wxTextEditorModel::NotifyAllViews()
{
    size_t i;
	for( i = 0; i != mViews.Count(); ++i )

		mViews[i]->OnModelChanged();
}

void wxTextEditorModel::PrepreForCommand()
{
	mTextChanged    = 0;
	mChangedFromRow = 0;
	mChangedTillRow = 0;
}

size_t wxTextEditorModel::TextToScrColumn( const TPosition& pos )
{
	TPosition spos;

	mpActiveView->TextPosToScreenPos( pos, spos );
	
	return spos.mCol + mpActiveView->GetPagePos().mCol;
}

size_t wxTextEditorModel::ScrToTextColumn( TPosition pos )
{
	TPosition tpos;

	pos.mCol -= mpActiveView->GetPagePos().mCol;
	pos.mRow -= mpActiveView->GetPagePos().mRow;

	mpActiveView->ScreenPosToTextPos( pos, tpos );

	return tpos.mCol;
}

void wxTextEditorModel::DoMoveCursor( int rows, int cols )
{
	mCursorPos.mCol = TextToScrColumn( mCursorPos );

	mCursorPos.mRow += rows;
	mCursorPos.mCol += cols;

	mCursorPos.mCol = ScrToTextColumn( mCursorPos );
}

/*** public interface ***/

wxTextEditorModel::wxTextEditorModel()

	: 
          mCurCommand     ( 0 ),
          mpActiveView    ( NULL ),
          mIsUnixText     ( IS_UNIX_TEXT_BY_DEFAULT ),
          mTextChanged    ( FALSE ),
          mChangedFromRow ( 0 ),
          mChangedTillRow ( 0 ),
          mWasChanged     ( FALSE ),
          mRowsPerPage    ( 0 ),
          mIsReadOnly     ( FALSE ),
          mIsModified     ( FALSE ),
          mInsertMode     ( TRUE  ),
		  mAutoIndentMode ( TRUE  ),
		  mSmartIndentMode( TRUE  ),
          mIsSelectionEditMode( FALSE ),
          mTabSize        ( 4 ),
          mCheckPointDestroyed( FALSE ),
          mCheckPointCmdNo( 0 )
{
	// at least one block should be present
	// (otherwise text-iterators wont work)

	mBlocks.Add( new TBlock() );
}

wxTextEditorModel::~wxTextEditorModel()
{
	ClearUndoBuffer();

	WX_CLEAR_ARRAY( mBlocks );
	WX_CLEAR_ARRAY( mPins );
}

char* wxTextEditorModel::AllocCharacters( size_t n )
{
	return new char[n];
}

char* wxTextEditorModel::AllocCharacters( size_t n, const char* srcBuf )
{
	char* destBuf = AllocCharacters( n );

	memcpy( destBuf, srcBuf, n );

	return destBuf;
}

void wxTextEditorModel::FreeCharacters( char* buf )
{
	delete [] buf;
}

void wxTextEditorModel::OnInsertChar( char ch )
{
	if ( ch == 27 )
        return; // hack

	if ( is_DOS_eol_char( ch ) )
        ch = '\n';

	PrepreForCommand();
	StartBatch();

	TCommand* pCmd = new TCommand();

	pCmd->mType    = TCMD_INSERT;

	if ( ch == '\n' && !mIsUnixText ) 
	{
		// DOS text with CR-LF pair
		pCmd->mData    = AllocCharacters( 2 );
		pCmd->mDataLen = 2;
		pCmd->mData[0] = (char)13;
		pCmd->mData[1] = (char)10;
	}
	else
	{
		pCmd->mData    = AllocCharacters( 1 );
		pCmd->mDataLen = 1;
		pCmd->mData[0] = ch;
	}


	if ( !SelectionIsEmpty() )
	{
		mCursorPos = mSelectionStart;
		DeleteSelection();
	}

	pCmd->mRange.mFrom = mCursorPos;

	if ( mInsertMode == FALSE )
	{
		TPosition nextPos( mCursorPos.mRow, mCursorPos.mCol + 1 );
		DeleteRange( mCursorPos, nextPos );

		SetPostPos( mCursorPos );
	}

	TTextIterator iter = CreateIterator( mCursorPos );

	size_t lineLen = iter.GetLineLen();

	bool indentAdded = FALSE;

	if ( mCursorPos.mCol > lineLen )
	{

		wxString s( ' ', mCursorPos.mCol - lineLen );
		InsertText( TPosition( mCursorPos.mRow, lineLen ), s.c_str(), s.length() );

		SetPostPos( mCursorPos );

		indentAdded = TRUE;
	}

	if ( CanPrependCommand( pCmd ) || indentAdded )

		PrependCommand( pCmd );
	else
		ExecuteCommand( pCmd );

	++mCursorPos.mCol;

	if ( is_eol_char( ch ) )
	{
		mCursorPos.mCol = 0;
		++mCursorPos.mRow;

		SetPostPos( mCursorPos );

		if ( mAutoIndentMode )
		{
			iter.ToStartOfLine();
			wxString indent;

			while( !iter.IsEol() )
			{
				char ch = iter.GetChar();

				if ( ch == '\t' || ch == ' ' )

					indent += ch;
				else
					break;

				iter.NextChar();
			}

			if ( indent.length() )
			{
				// auto-indent is always prepended to the command which
				// caused it

				mCursorPos = TPosition( mCursorPos.mRow, 0 );

				TCommand* pICmd = new TCommand();
				pICmd->mType    = TCMD_INSERT;
				pICmd->mData    = AllocCharacters( indent.length() );
				pICmd->mDataLen = indent.length();
				memcpy( pICmd->mData, indent, indent.length() );

				pICmd->mRange.mFrom = TPosition( mCursorPos.mRow, 0 );

				PrependCommand( pICmd );

				SetPostPos( mCursorPos );

				mCursorPos.mCol = indent.length();
			}
		}
	}
	else
		SetPostPos( mCursorPos );

	FinishBatch();

	NotifyAllViews();
}

void wxTextEditorModel::OnDelete()
{
	PrepreForCommand();
	StartBatch();

	if ( !SelectionIsEmpty() )
	{
		TPosition startPos = mSelectionStart;
		DeleteSelection();
		mCursorPos = startPos;
	}
	else
	{
		TTextIterator iter = CreateIterator( mCursorPos );
		
		if ( iter.GetLineLen() == mCursorPos.mCol && !iter.IsLastLine() )
		{
			TPosition nextPos( mCursorPos.mRow+1, 0 );
			DeleteRange( mCursorPos, nextPos );
			NotifyTextChanged( mCursorPos.mRow, 2, CT_DELETED );
		}
		else
		{
			TPosition nextPos( mCursorPos.mRow, mCursorPos.mCol + 1 );
			DeleteRange( mCursorPos, nextPos );
		}
	}

	SetPostPos( mCursorPos );

	FinishBatch();

	NotifyAllViews();
}

void wxTextEditorModel::OnDeleteBack()
{
	PrepreForCommand();
	StartBatch();

	if ( !SelectionIsEmpty() )
	{
		mCursorPos = mSelectionStart;
		DeleteSelection();
	}
	else
	if ( !(mCursorPos == TPosition(0,0)) )
	{
		TPosition prevPos;

		if ( mCursorPos.mCol == 0 )
		{
			TTextIterator iter = CreateIterator( mCursorPos );
			iter.PreviousChar();

			prevPos = iter.GetPosition();
		}
		else
			prevPos = TPosition( mCursorPos.mRow, mCursorPos.mCol - 1 );

		DeleteRange( prevPos, mCursorPos );

		mCursorPos = prevPos;
	}

	SetPostPos( mCursorPos );

	FinishBatch();

	NotifyAllViews();

}

void wxTextEditorModel::OnDeleteLine()
{
	PrepreForCommand();
	StartBatch();

	DeleteSelection();

	TTextIterator iter = CreateIterator( mCursorPos );

	iter.ToStartOfLine();

	TPosition from = iter.GetPosition();

	iter.ToEndOfLine();

	if ( iter.IsLastLine() == FALSE )

		iter.NextChar(); // delete eol-char also, if it's not the last line

	TPosition till = iter.GetPosition();

	DeleteRange( from, till );
	SetPostPos( mCursorPos );

	FinishBatch();

	NotifyAllViews();
}

void wxTextEditorModel::OnShiftSelectionIndent( bool left )
{
	if ( SelectionIsEmpty() )
        return;

	PrepreForCommand();
	StartBatch();

    size_t row;
	for( row = mSelectionStart.mRow; row != mSelectionEnd.mRow; ++row )
	{
		TTextIterator iter = CreateIterator( TPosition( row, 0 ) );

		if ( left )
		{
			unsigned int n = 0, pos = 0;

			while( !iter.IsEol() && !iter.IsEof() )
			{
				char ch = iter.GetChar();

				if ( pos == mTabSize )
                    break;

				if ( ch != ' ' && ch != '\t' )
                    break;

				++n;

				if ( ch == '\t' )
                    break;

				++pos;

				iter.NextChar();
			}

			if ( n )
                DeleteRange( TPosition( row,0 ), TPosition( row, n ) );
		}
		else
		{
			char txt = '\t';

			InsertText( TPosition( row, 0 ), &txt, sizeof(char) );
		}
	}

	FinishBatch();
	NotifyAllViews();
}

void wxTextEditorModel::OnPaste()
{
	// FIXME:: "wxLogQueryInterface(..)" linking problems with MSDev4.0

	
#ifdef __HACK_MY_MSDEV40__

	bool alreadyOpen=wxClipboardOpen();
	if (!alreadyOpen)
	{
		wxOpenClipboard();
	}

	char* data = (char*)::wxGetClipboardData( wxDF_TEXT );

	wxCloseClipboard();

	if ( data == NULL )
        return;

	PrepreForCommand();
	StartBatch();

	if ( !SelectionIsEmpty() )
	{
		mCursorPos = GetStartOfSelection();
		DeleteSelection();
	}

	InsertText( mCursorPos, data, strlen( data ) );

	delete [] data;
#else

	if ( !wxTheClipboard->Open() )
        return;

	wxTextDataObject data;
	if ( !wxTheClipboard->IsSupported(wxDF_TEXT) ) 
	{
		wxTheClipboard->Close();
		return;
	}

	wxTheClipboard->GetData( data );

	wxString txt = data.GetText();

	wxTheClipboard->Close();

	PrepreForCommand();
	StartBatch();

	DeleteSelection();

	InsertText( mCursorPos, txt.c_str(), txt.length() );
#endif


	mCursorPos = mCommands[ mCommands.Count()-1 ]->mRange.mTill;
	SetPostPos( mCursorPos );

	FinishBatch();
	NotifyAllViews();
}

void wxTextEditorModel::OnCut()
{
	OnCopy();

	PrepreForCommand();
	StartBatch();

	DeleteSelection();
	SetPostPos( mCursorPos );


	FinishBatch();
	NotifyAllViews();
}

void wxTextEditorModel::OnCopy()
{
	if ( !SelectionIsEmpty() )
	{
		size_t len  = 0;
		char*  text = NULL;

#ifndef __HACK_MY_MSDEV40__

		if ( !wxTheClipboard->Open() )
            return;

		GetTextFromRange( mSelectionStart, mSelectionEnd, &text, len );

		wxString s( text, len );

	    wxTheClipboard->AddData( new wxTextDataObject(s) );
		wxTheClipboard->Close();

		FreeCharacters( text );
#else
		bool alreadyOpen=wxClipboardOpen();
		if (!alreadyOpen)
		{
			wxOpenClipboard();
			if (!wxEmptyClipboard()) 
			{
				wxCloseClipboard();
				return;
			}
		}

		GetTextFromRange( mSelectionStart, mSelectionEnd, &text, len );

		wxString s( text, len );

		bool success = ::wxEmptyClipboard();

		success = wxSetClipboardData( wxDF_TEXT, (wxObject*)s.c_str(), 0,0 );

		FreeCharacters( text );

		wxCloseClipboard();

#endif
	}
}

bool wxTextEditorModel::CanCopy()
{
	return !SelectionIsEmpty();
}

bool wxTextEditorModel::CanPaste()
{
	if ( mIsReadOnly )
        return FALSE;

#ifndef __HACK_MY_MSDEV40__

	if ( !wxTheClipboard->Open() )
        return FALSE;

	if ( !wxTheClipboard->IsSupported(wxDF_TEXT) )
		return FALSE;

	wxTheClipboard->Close();

	return TRUE;

#else

	bool success = ::wxClipboardOpen();

	bool alreadyOpen=wxClipboardOpen();
	if (!alreadyOpen)
	{
		wxOpenClipboard();
	}

	char* data = (char*)::wxGetClipboardData( wxDF_TEXT );

	wxCloseClipboard();

	if ( data != NULL && strlen(data) != 0 )
	{
		delete [] data;
		return TRUE;
	}
	else
	{
		delete [] data;
		return FALSE;
	}

#endif

}

bool wxTextEditorModel::CanUndo()
{
	return !( mCommands.Count() == 0 ||
			  mCurCommand == 0 );
}

bool wxTextEditorModel::CanRedo()
{
	return mCurCommand != mCommands.Count();
}

void wxTextEditorModel::OnUndo()
{
	if ( !CanUndo() )
        return;

	PrepreForCommand();
	StartBatch();

	ResetSelection();

	UndoImpl();

	FinishBatch();
	NotifyAllViews();
}

void wxTextEditorModel::OnRedo()
{
	if ( !CanRedo() )
        return;

	PrepreForCommand();
	StartBatch();

	ResetSelection();

	RedoImpl();

	FinishBatch();
	NotifyAllViews();
}

void wxTextEditorModel::OnMoveLeft()
{
	PrepreForCommand();
	CheckSelection();

	if ( mCursorPos.mCol == 0 )
	{
		if ( mCursorPos.mRow != 0 )
		{
			--mCursorPos.mRow;

			TTextIterator iter = CreateIterator( mCursorPos );

			iter.ToEndOfLine();

			mCursorPos.mCol = iter.GetPosition().mCol;
		}
	}
	else
		--mCursorPos.mCol;

	TrackSelection();
	NotifyView();
}

void wxTextEditorModel::OnMoveRight()
{
	PrepreForCommand();
	CheckSelection();

	++mCursorPos.mCol;

	TrackSelection();
	NotifyView();
}

void wxTextEditorModel::OnMoveUp()
{
	PrepreForCommand();
	CheckSelection();

	if ( mCursorPos.mRow != 0 )
	
		DoMoveCursor( -1,0 );

	TrackSelection();
	NotifyView();
}

void wxTextEditorModel::OnMoveDown()
{
	PrepreForCommand();
	CheckSelection();

	if ( mCursorPos.mRow + 1 < GetTotalRowCount() )

		DoMoveCursor( 1,0 );

	TrackSelection();
	NotifyView();
}

void wxTextEditorModel::OnWordRight()
{
	PrepreForCommand();
	CheckSelection();

	TTextIterator iter = CreateIterator( mCursorPos );

	iter.NextWord();

	mCursorPos = iter.GetPosition();

	TrackSelection();
	NotifyView();
}

void wxTextEditorModel::OnWordLeft()
{
	PrepreForCommand();
	CheckSelection();

	TTextIterator iter = CreateIterator( mCursorPos );

	iter.PreviousWord();

	mCursorPos = iter.GetPosition();

	TrackSelection();
	NotifyView();
}

void wxTextEditorModel::OnMoveToPosition( const TPosition& pos )
{
	PrepreForCommand();
	CheckSelection();

	mCursorPos = pos;

	TrackSelection();
	NotifyView();
}

void wxTextEditorModel::OnEndOfLine()
{
	PrepreForCommand();
	CheckSelection();

	TTextIterator iter = CreateIterator( mCursorPos );
	iter.ToEndOfLine();

	mCursorPos = iter.GetPosition();

	TrackSelection();
	NotifyView();
}

void wxTextEditorModel::OnStartOfLine()
{
	PrepreForCommand();
	CheckSelection();

	unsigned int prevCol = mCursorPos.mCol;

	TTextIterator iter = CreateIterator( mCursorPos );
	iter.ToStartOfLine();

	// bypass leading white-space at the beginning of the line

	while( !iter.IsEol() )
	{
		char ch = iter.GetChar();

		if ( ch != ' ' && ch != '\t' )
            break;

		++mCursorPos.mCol;

		iter.NextChar();
	}

	mCursorPos = iter.GetPosition();

	if ( mCursorPos.mCol == prevCol )

		mCursorPos.mCol = 0;

	TrackSelection();
	NotifyView();
}

void wxTextEditorModel::OnPageUp()
{
	PrepreForCommand();
	CheckSelection();

	if ( mCursorPos.mRow < mRowsPerPage )
		mCursorPos.mRow = 0;
	else
		DoMoveCursor( -1*(mRowsPerPage),0 );

	mpActiveView->ScrollView( -(int)mRowsPerPage, 0 );

	TrackSelection();
	NotifyView();
}

void wxTextEditorModel::OnPageDown()
{
	PrepreForCommand();
	CheckSelection();

	if ( mCursorPos.mRow + mRowsPerPage >= GetTotalRowCount() )
	{
		if ( GetTotalRowCount() != 0 )
			mCursorPos.mRow = GetTotalRowCount() - 1;
		else
			mCursorPos.mRow = 0;
	}
	else
		DoMoveCursor( mRowsPerPage,0 );

	mpActiveView->ScrollView( mRowsPerPage, 0 );

	TrackSelection();
	NotifyView();
}

void wxTextEditorModel::OnSlideUp()
{
	PrepreForCommand();
	
	if ( mpActiveView->GetPagePos().mRow + mRowsPerPage - 1 == mCursorPos.mRow  )
	{
		if ( mCursorPos.mRow == 0 )
			return;

		DoMoveCursor( -1,0 );
	}

	mpActiveView->ScrollView( -1, 0 );

	NotifyView();
}

void wxTextEditorModel::OnSlideDown()
{
	PrepreForCommand();
	
	if ( mCursorPos.mRow == mpActiveView->GetPagePos().mRow )
	{
		if ( mCursorPos.mRow + 1 >= GetTotalRowCount() )

			return;

		DoMoveCursor( 1,0 );
	}

	mpActiveView->ScrollView( 1, 0 );

	NotifyView();
}

void wxTextEditorModel::OnStartOfText()
{
	PrepreForCommand();
	CheckSelection();

	mCursorPos.mRow = mCursorPos.mCol = 0;

	TrackSelection();
	NotifyView();
}

void wxTextEditorModel::OnEndOfText()
{
	PrepreForCommand();
	CheckSelection();

	mCursorPos.mRow = GetTotalRowCount() - 1;

	TTextIterator iter = CreateIterator( mCursorPos );

	iter.ToEndOfLine();

	mCursorPos = iter.GetPosition();

	TrackSelection();
	NotifyView();
}

void wxTextEditorModel::OnSelectWord()
{
	PrepreForCommand();

	TTextIterator iter1 = CreateIterator( mCursorPos );
	iter1.GotoClosestPos();

	if ( mCursorPos == iter1.GetPosition() )
	{
		TTextIterator iter2 = iter1;

		// find the left-edge of the word

		bool wasSeparator = TTextIterator::IsSeparator( iter1.GetChar() );

		while( !iter1.IsEol() )
		{
			char ch = iter1.GetChar();

			if ( ch == '\t' || 
				 ch == ' '  ||
				 wasSeparator != TTextIterator::IsSeparator( iter1.GetChar() )
			   ) 
			{
				iter1.NextChar();
				break;
			}

			iter1.PreviousChar();
		}

		// find the left-edge of the word

		while( !iter2.IsEol() )
		{
			char ch = iter2.GetChar();

			if ( ch == '\t' || 
				 ch == ' '  ||
				 wasSeparator != TTextIterator::IsSeparator( iter2.GetChar() ) 
			)
				break;

			iter2.NextChar();
		}

		if ( !(iter1.GetPosition() == iter2.GetPosition()) )
		{
			mSelectionStart = iter1.GetPosition();
			mSelectionEnd   = iter2.GetPosition();
			mCursorPos      = iter2.GetPosition();

			NotifyTextChanged( mSelectionStart.mRow, 1, CT_MODIFIED );
		}
	}

	NotifyView();
}

void wxTextEditorModel::OnSelectAll()
{
	PrepreForCommand();

	ResetSelection();

	mSelectionStart = TPosition(0,0);
	mSelectionEnd   = TPosition( GetTotalRowCount(), 1024 ); // FOR NOW:: hack

	mCursorPos = mSelectionStart;

	NotifyTextChanged( mSelectionStart.mRow, mSelectionEnd.mRow, CT_MODIFIED );

	NotifyView();
}

void wxTextEditorModel::OnToggleBookmark()
{
	size_t curRow = GetCursor().mRow;

	if ( GetPinAt( curRow, TBookmarkPin::GetPinTypeCode() ) != NULL )
		RemovePinAt( curRow, TBookmarkPin::GetPinTypeCode() );
	else
		AddPin( new TBookmarkPin( curRow ) );

	MergeChange( curRow, 1 );

	NotifyAllViews();
}

void wxTextEditorModel::OnNextBookmark()
{
	size_t pinNo = FindNextPinFrom( mCursorPos.mRow + 1 );

	while( pinNo != NPOS )
	{
		TPinBase& pin = *mPins[pinNo];

		if ( pin.mTypeCode == BOOKMARK_PIN_TC )
		{
			OnGotoLine( pin.mRow, 0 );
			break;
		}

		if ( pinNo == mPins.Count() )
            break;

		++pinNo;
	}
}

void wxTextEditorModel::OnPreviousBookmark()
{
	if ( mCursorPos.mRow == 0 )
        return;

	size_t pinNo = FindPreviousPinFrom( mCursorPos.mRow - 1 );

	while( pinNo != NPOS )
	{
		TPinBase& pin = *mPins[pinNo];

		if ( pin.mTypeCode == BOOKMARK_PIN_TC )
		{
			OnGotoLine( pin.mRow, 0 );
			break;
		}

		if ( pinNo == 0 )
            break;

		--pinNo;
	}
}

bool wxTextEditorModel::OnFind()
{
	if ( !SelectionIsEmpty() )
	{
		if ( GetStartOfSelection().mRow == GetEndOfSelection().mRow )
		{
			char* buf = NULL; size_t len = 0;

			GetSelection( &buf, len );

			mLastFindExpr = wxString( buf, 0, len );

			delete [] buf;
		}
	}

	wxFindTextDialog dlg( GetActiveView(), mLastFindExpr );
	//dlg.SetExpr( mLastFindExpr );

	if ( dlg.ShowModal() == wxID_OK )
	{
		mLastFindExpr = dlg.GetExpr();

		GetActiveView()->SetFocus();

		return OnFindNext();
	}

	GetActiveView()->SetFocus();

	return FALSE;
}

bool wxTextEditorModel::OnFindNext( bool quiet )
{
	PrepreForCommand();

	wxString& val = mLastFindExpr;
	size_t  len = val.Length();

	if ( len == 0 ) 
	{
		NotifyView();
		wxMessageBox( "Search string not found!" );

		GetActiveView()->SetFocus();

		return FALSE;
	}

	char ch1 = val[0u];

	TTextIterator iter = CreateIterator( mCursorPos );

	while( !iter.IsEof() )
	{
		char ch = iter.GetChar();

		if ( ch == ch1 )
		{
			size_t startCol = iter.mPos.mCol;
			iter.NextChar();
			ch = iter.GetChar();

			size_t i = 1;
			while( i < len && !iter.IsEof() && ch == val[i] )
			{
				++i;
				iter.NextChar();
				ch = iter.GetChar();
			}

			if ( i == len )
			{
				if ( !SelectionIsEmpty() )

					ResetSelection();

				SetStartOfSelection( TPosition( iter.mPos.mRow, startCol ) );
				SetEndOfSelection( iter.mPos );

				MergeChange( iter.mPos.mRow, 1 );

				mCursorPos = iter.mPos;

				OnGotoLine( iter.mPos.mRow, iter.mPos.mCol );
				return TRUE;
			}
		}
		else
			iter.NextChar();
	}

	NotifyView();
	MergeChange( mCursorPos.mRow, 2 );
	
	if ( !quiet )
        wxMessageBox( "Search string not found!" );

	GetActiveView()->SetFocus();

	return FALSE;
}

bool wxTextEditorModel::OnFindPrevious( bool quiet )
{
	PrepreForCommand();

	wxString& val = mLastFindExpr;
	size_t  len = val.Length();

	if ( len == 0 ) 
	{
		NotifyView();
		wxMessageBox( "Search string not found!" );

		GetActiveView()->SetFocus();

		return FALSE;
	}

	char ch1 = val[0u];

	TTextIterator iter;
	if ( !(mSelectionStart == mSelectionEnd) )
	{
		iter = CreateIterator( mSelectionStart );
		iter.PreviousChar();
	}
	else
	{
		iter = CreateIterator( mCursorPos );
		iter.PreviousChar();
	}

	while( !iter.IsEof() )
	{
		char ch = iter.GetChar();

		if ( ch == ch1 )
		{
			TTextIterator tmp = iter;

			size_t startCol = iter.mPos.mCol;
			iter.NextChar();
			ch = iter.GetChar();

			size_t i = 1;
			while( i < len && !iter.IsEof() && ch == val[i] )
			{
				++i;
				iter.NextChar();
				ch = iter.GetChar();
			}

			if ( i == len )
			{
				if ( !SelectionIsEmpty() )

					ResetSelection();

				SetStartOfSelection( TPosition( iter.mPos.mRow, startCol ) );
				SetEndOfSelection( iter.mPos );

				MergeChange( iter.mPos.mRow, 1 );

				mCursorPos = iter.mPos;

				OnGotoLine( iter.mPos.mRow, iter.mPos.mCol );
				return TRUE;
			}
			else
			{
				iter = tmp;
				iter.PreviousChar();
			}
		}
		else
			iter.PreviousChar();
	}

	NotifyView();
	MergeChange( mCursorPos.mRow, 2 );
	
	if ( !quiet )
        wxMessageBox( "Search string not found!" );

	GetActiveView()->SetFocus();

	return FALSE;
}

void wxTextEditorModel::OnGotoLine( unsigned int line, unsigned int col )
{
	if ( mpActiveView == NULL ) 
        return;

	TPosition pagePos = mpActiveView->GetPagePos();

	mpActiveView->GetTimer()->HideCursor();

	if ( line >= pagePos.mRow &&
		 line <  pagePos.mRow + mRowsPerPage )
	{
		mCursorPos.mRow = (size_t)line;
		mCursorPos.mCol = (size_t)col;

        if ( (int)col == - 1)
		{
			mCursorPos.mCol = 0;
			OnStartOfLine();
		}
		else
			NotifyView();

		return;
	}

	size_t third = mRowsPerPage / 3;
	size_t newTop = 0;

	if ( line < third )
		newTop = 0;
	else
		newTop = line - third;


	mpActiveView->ScrollView( (int)newTop - (int)pagePos.mRow, -(int)pagePos.mCol );

	mCursorPos.mRow = line;
	mCursorPos.mCol = col;

	if ( (int)col == - 1)
	{
		mCursorPos.mCol = 0;
		OnStartOfLine();
	}
	else
		NotifyView();
}

void wxTextEditorModel::OnGotoLine()
{
	wxTextEntryDialog* dlg = 
		new wxTextEntryDialog( mpActiveView, "Line number:", "Goto line", "" );

	int nTries = 3;

	while( dlg->ShowModal() == wxID_OK && nTries )
	{
		ResetSelection();

		int i = -1;
		sscanf( dlg->GetValue(), "%d", &i );

		if ( i == -1  )
		{
			wxMessageBox( "Please enter a number" );
			--nTries;
			continue;
		}

		if ( i == 0 )
            ++i;

		OnGotoLine( (size_t)(i-1), 0 );
		break;
	}

	GetActiveView()->SetFocus();
}


bool wxTextEditorModel::IsReadOnly()
{
	return mIsReadOnly;
}

bool wxTextEditorModel::IsModified()
{
	return mCurCommand != 0;
}

bool wxTextEditorModel::IsInsertMode()
{
	return mInsertMode;
}

void wxTextEditorModel::SetReadOnly( bool on )
{
	mIsReadOnly = on;
}

void wxTextEditorModel::SetCheckpoint()
{
	mCheckPointDestroyed = FALSE;
	mCheckPointCmdNo     = mCurCommand;
}

bool wxTextEditorModel::CheckpointModified()
{
	if ( mCheckPointDestroyed )
        return TRUE;

	return mCheckPointCmdNo != mCurCommand;
}

TPosition wxTextEditorModel::GetStartOfSelection()
{
	ArrangePositions( mSelectionStart, mSelectionEnd );

	return mSelectionStart;
}

TPosition wxTextEditorModel::GetEndOfSelection()
{
	ArrangePositions( mSelectionStart, mSelectionEnd );

	return mSelectionEnd;
}

TPosition wxTextEditorModel::GetCursor()
{
	return mCursorPos;
}

void wxTextEditorModel::SetStartOfSelection( const TPosition& pos )
{
	mSelectionStart = pos;
}

void wxTextEditorModel::SetEndOfSelection( const TPosition& pos )
{
	mSelectionEnd = pos;
}

void wxTextEditorModel::SetCursor( const TPosition& pos )
{
	mCursorPos = pos;

	NotifyAllViews();
}

void wxTextEditorModel::AddView( wxTextEditorView* pView )
{
	mViews.Add( pView );
	pView->SetModel( this );
	SetActiveView( pView );
}

void wxTextEditorModel::RemoveView( wxTextEditorView* pView )
{
	mViews.Remove( pView );
}

void wxTextEditorModel::SetActiveView( wxTextEditorView* pView )
{
	mpActiveView = pView;
}

wxTextEditorView* wxTextEditorModel::GetActiveView()
{
	return mpActiveView;
}

void wxTextEditorModel::SetRowsPerPage( size_t n )
{
	mRowsPerPage = n;
}

long wxTextEditorModel::mPinIdCounter = 1;

long wxTextEditorModel::AddPin( TPinBase* pPin )
{
	// FIXME:: binary search should be used

	size_t beforePin = FindNextPinFrom( pPin->mRow );

	if ( beforePin != NPOS )
	{
		// pins in the same row are ordered in the
		// descending order of their type-codes

		while( beforePin < mPins.Count() &&
			   mPins[beforePin]->mRow == pPin->mRow &&
			   mPins[beforePin]->mTypeCode < pPin->mTypeCode )
        {
		   ++beforePin;
        }

		if ( beforePin < mPins.Count() )
			
			mPins.Insert( pPin, beforePin );
		else
			mPins.Add( pPin );
	}
	else
		mPins.Add( pPin );

	pPin->mPinId = mPinIdCounter++;

	return pPin->mPinId;
}

TPinBase* wxTextEditorModel::FindPinById( long pinId )
{
    size_t i;
	for( i = 0; i != mPins.Count(); ++i )
    {
		if ( mPins[i]->mPinId == pinId )
			return mPins[i];
    }
	return NULL;
}

PinListT& wxTextEditorModel::GetPins()
{
	return mPins;
}

size_t wxTextEditorModel::FindFirstPinInRange( size_t fromRow, size_t tillRow )
{
	// FIXME:: perform binary search instead

    size_t i;
	for( i = 0; i != mPins.Count(); ++i )
	{
		TPinBase& pin = *mPins[i];

		if ( pin.mRow >= tillRow )
            return NPOS;

		if ( pin.mRow >= fromRow )
			return i;
	}

	return NPOS;
}

size_t wxTextEditorModel::FindNextPinFrom( size_t fromRow )
{
	// FIXME:: perform binary search instead

    size_t i;
	for( i = 0; i != mPins.Count(); ++i )
	{
		TPinBase& pin = *mPins[i];

		if ( pin.mRow >= fromRow )
			return i;
	}

	return NPOS;
}

size_t wxTextEditorModel::FindPreviousPinFrom( size_t fromRow )
{
	// FIXME:: perform binary search instead

	if ( mPins.Count() == 0 )
        return NPOS;

	size_t i = mPins.Count() - 1;

	for(;;)
	{
		TPinBase& pin = *mPins[i];

		if ( pin.mRow <= fromRow )
			return i;

		if ( i == 0 )
            break;

		--i;
	}

	return NPOS;
}

size_t wxTextEditorModel::GetPinNoAt( size_t row, int pinTypeCode )
{
	size_t curPin = FindNextPinFrom( row );

	while( curPin != NPOS )
	{
		TPinBase& pin = *mPins[curPin];

		if ( pin.mRow > row )
            return NPOS;

		if ( pin.mTypeCode == pinTypeCode )
            return curPin;

		++curPin;

		if ( curPin == mPins.Count() )
            return NPOS;
	}

	return NPOS;
}

TPinBase* wxTextEditorModel::GetPinAt( size_t row, int pinTypeCode )
{
	size_t pinNo = GetPinNoAt( row, pinTypeCode );

	return ( pinNo == NPOS ) ? NULL : mPins[pinNo];
}

bool wxTextEditorModel::RemovePinAt( size_t row, int pinTypeCode )
{
	size_t pinNo = GetPinNoAt( row, pinTypeCode );

	if ( pinNo != NPOS )
	{
		mPins.Remove( pinNo );
		return TRUE;
	}

	return FALSE;
}

void wxTextEditorModel::AddChangeListener( TTextChangeListenerBase* pListener )
{
	mChangeListeners.Add( pListener );
}

/***** Implementation for class wxTextEditorView *****/

BEGIN_EVENT_TABLE( wxTextEditorView, wxReDirectScrolledWindow )

	EVT_SIZE  ( wxTextEditorView::OnSize   )
	EVT_SCROLLWIN( wxTextEditorView::OnScroll )
	EVT_PAINT ( wxTextEditorView::OnPaint  )

	EVT_LEFT_DOWN  ( wxTextEditorView::OnLButtonDown )
	EVT_LEFT_UP    ( wxTextEditorView::OnLButtonUp   )
	EVT_MOTION     ( wxTextEditorView::OnMotion      )
	EVT_LEFT_DCLICK( wxTextEditorView::OnDblClick    )

	EVT_SET_FOCUS  ( wxTextEditorView::OnSetFocus  )
	EVT_KILL_FOCUS ( wxTextEditorView::OnKillFocus )

	EVT_CHAR( wxTextEditorView::OnChar )
	EVT_KEY_DOWN( wxTextEditorView::OnKeyDown )

	EVT_ERASE_BACKGROUND( wxTextEditorView::OnEraseBackground )


END_EVENT_TABLE()

TCursorTimer* wxTextEditorView::mpTimer = new TCursorTimer();

wxTextEditorView::wxTextEditorView( wxWindow* parent, 
									wxWindowID id, 
									wxTextEditorModel* pModel,
									int wndStyle,
									bool ownsModel )

	: wxReDirectScrolledWindow( parent, id, wxPoint(32768,32768), wxSize(0,0),
						wxHSCROLL | wxVSCROLL | wndStyle ),
          mpNextCharReceiver( NULL ),                    
          mLastRowsTotal( (size_t)(-1) ),
          mAdjustScrollPending( FALSE ),
          mScrollingOn( TRUE ),
          mCursorOn  ( TRUE ),
          mLTMode( FALSE ),
		  mDragStarted( FALSE ),
		  mpDraggedText( NULL ),          
          mOwnsModel ( ownsModel ),
          mRowsPerPage ( 0 ),
          mColsPerPage ( 0 ),
          mFullRefreshPending( TRUE ),
          mMaxColumns( 500 ),
		  mHasBgImage( FALSE ),
		  mpBgImageDc( NULL ),
          mPagePos( 0,0 )

{
	if ( ownsModel && !pModel )
		pModel = new wxTextEditorModel();

	SetModel( pModel );

	pModel->AddView( this );

	SetTextDefaults();

	SetSourcePainter( new SourcePainter() );

	mCashedIter.mPos = TPosition( (size_t)(-1), 0 );

	// default
	AddPinPainter( new TBookmarkPainter() );
}


wxTextEditorView::~wxTextEditorView()
{
	if ( mpTimer && mpTimer->GetView() == this && 
		 mCursorOn && !mLTMode )
	{
		mpTimer->SetView( NULL );
		mpTimer->HideCursor( TRUE );
	}

	if ( mOwnsModel && mpModel )
		delete mpModel;

	WX_CLEAR_ARRAY( mPinPainters );

    if ( mpPainter )
        delete mpPainter;
}

void wxTextEditorView::SetNextCharReceiver( wxEvtHandler* pReceiver )
{
	mpNextCharReceiver = pReceiver;
}

void wxTextEditorView::SetTextDefaults()
{
	mLeftMargin   = 22;
	mRightMargin  = 0;
	mTopMargin    = 0;
	mBottomMargin = 0;

	mCharDim.x = -1; // not detected yet
	mCharDim.y = -1;

	mNormalTextCol       = *wxBLACK;
	mIndentifierTextCol  = *wxBLUE;
	mReservedWordTextCol = *wxRED;
	mCommentTextCol      = wxColour( 0,128,128 );

	mNormalBkCol    = 
		wxSystemSettings::GetSystemColour( wxSYS_COLOUR_WINDOW );

	mSelectionFgCol = wxColour(255,255,255);//*wxWHITE;
	mSelectionBkCol = wxColour( 0,0,128 );

	mNormalBkBrush   = wxBrush( mNormalBkCol,    wxSOLID );
	mSelectedBkBrush = wxBrush( mSelectionBkCol, wxSOLID );

#if defined(__WXMSW__) || defined(__WINDOWS__) 
    mFont.SetFaceName("Courier");
    //mFont.SetStyle(40);
    //mFont.SetWeight(40);
    mFont.SetPointSize( 10 );
#else
    //mFont.SetFamily( wxSWISS );
	mFont = wxSystemSettings::GetSystemFont(wxSYS_OEM_FIXED_FONT);

    mFont.SetStyle(wxNORMAL);
    mFont.SetWeight(wxNORMAL);
    mFont.SetPointSize( 9);
#endif


#if defined(__WXMSW__) || defined(__WINDOWS__)
    mFont.RealizeResource();
#endif

	// reduce flicker un wxGtk
	SetBackgroundColour( mNormalBkCol );
}

void wxTextEditorView::SetColours( const wxColour& normalBkCol,
								   const wxColour& selectedBkCol,
								   const wxColour& selectedTextCol )
{
	mNormalBkCol    = normalBkCol;
	mSelectionFgCol = selectedTextCol;
	mSelectionBkCol = selectedBkCol;

	mNormalBkBrush   = wxBrush( mNormalBkCol,    wxSOLID );
	mSelectedBkBrush = wxBrush( mSelectionBkCol, wxSOLID );
}

void wxTextEditorView::SetHighlightingColours( const wxColour& normalTextCol,
											    const wxColour& identifierTextCol,
											    const wxColour& reservedWordTextCol,
											    const wxColour& commentTextCol )
{
	mNormalTextCol       = normalTextCol;
	mIndentifierTextCol  = identifierTextCol;
	mReservedWordTextCol = reservedWordTextCol;
	mCommentTextCol      = commentTextCol;
}

void wxTextEditorView::SetMargins( int top, int left, int bottom, int right )
{
	mLeftMargin   = left;
	mRightMargin  = right;
	mTopMargin    = top;
	mBottomMargin = bottom;
}

void wxTextEditorView::RecalcPagingInfo()
{
	bool firstRefresh = mCharDim.x == -1;

	if ( firstRefresh )

		ObtainFontProperties();

	int w = 0, h = 0;
	GetClientSize( &w, &h );

	w -= mLeftMargin + mRightMargin;
	h -= mTopMargin  + mBottomMargin;

	mColsPerPage = ( ( w / mCharDim.x ) +
				   ( ( w % mCharDim.x ) ? 0 : 0 ) );


	mRowsPerPage = ( ( h / mCharDim.y ) +
				   ( ( h % mCharDim.y ) ? 0 : 0 ) );

	if ( mpModel->GetActiveView() == this )

		mpModel->SetRowsPerPage( mRowsPerPage );

	if ( firstRefresh ) 
	{
		// scrolling should not happen at DC-level
		EnableScrolling( FALSE, FALSE );

		if ( mScrollingOn )

			SetScrollbars( mCharDim.x, mCharDim.y,
						   mMaxColumns,
						   mpModel->GetTotalRowCount(),
						   mPagePos.mCol,
						   mPagePos.mRow,
						   TRUE
						 );
	}

	PositionCursor();
}

void wxTextEditorView::OnScroll( wxScrollWinEvent& event )
{
	if ( !mScrollingOn )
        return;

	// overriden implementation of wxReDirectScrolledWindow::OnScroll,
	// to reduce flicker on wxGtk, by using wxClientDC 
	// instead of Refresh()

	int orient = event.GetOrientation();

	int nScrollInc = CalcScrollInc(event);
	if (nScrollInc == 0)
        return;

	if (orient == wxHORIZONTAL)
	{
		int newPos = m_xScrollPosition + nScrollInc;
		SetScrollPos(wxHORIZONTAL, newPos, TRUE );
	}
	else
	{
		int newPos = m_yScrollPosition + nScrollInc;
		SetScrollPos(wxVERTICAL, newPos, TRUE );
	}

	if (orient == wxHORIZONTAL)
	{
		m_xScrollPosition += nScrollInc;
	}
	else
	{
		m_yScrollPosition += nScrollInc;
	}

	int x,y;
	ViewStart( &x, &y );

	mPagePos.mRow = y;
	mPagePos.mCol = x;

	PositionCursor();

	if ( mAdjustScrollPending )
	{
		mLastRowsTotal = mpModel->GetTotalRowCount();
		SetScrollbars( mCharDim.x, mCharDim.y,
					   mMaxColumns,   // FOR NOW:: maximal line-length not calculated
					   mLastRowsTotal,
					   mPagePos.mCol,
					   mPagePos.mRow,
					   TRUE
					 );

		mLastViewStart = mPagePos;

		mAdjustScrollPending = FALSE;

		return;
	}

	wxClientDC dc( this );

	mFullRefreshPending = TRUE;

	PaintRows( mPagePos.mRow, mPagePos.mRow + mRowsPerPage, dc );
}

void wxTextEditorView::OnPaint( wxPaintEvent& event )
{
	wxPaintDC dc( this );

	if ( !mpModel )
        return;

	//wxReDirectScrolledWindow::OnPaint( event );
	if ( mCharDim.x == -1 )
        ObtainFontProperties();

	mFullRefreshPending = TRUE;

	if ( IsActiveView() && mCursorOn && !mLTMode ) 
	{
		mpTimer->Lock();
		mpTimer->SetIsShown( FALSE );
	}

	PaintRows( mPagePos.mRow, mPagePos.mRow + mRowsPerPage, dc );
}

void wxTextEditorView::OnSize( wxSizeEvent& event )
{
	if ( !mpModel )
        return;

	RecalcPagingInfo();

	SyncScrollbars();

	event.Skip();
}

void wxTextEditorView::OnEraseBackground( wxEraseEvent& event )
{
#if 0
	int w = 0, h = 0;

	GetClientSize( &w, &h );
	
	wxPaintDC dc( this );

	PaintBackgroundRegion( dc, 0,0, w,h );
#endif
}

void wxTextEditorView::OnLButtonDown( wxMouseEvent& event )
{
	if ( mDragStarted )
        return;

	mDragStarted = TRUE;

	TPosition textPos;
	PixelsToTextPos( event.m_x, event.m_y, textPos );

	mpModel->SetSelectionEditMode( FALSE );

	mpModel->OnMoveToPosition( textPos );

	mpModel->SetSelectionEditMode( TRUE );

	SetFocus();

	CaptureMouse();
}

void wxTextEditorView::OnLButtonUp( wxMouseEvent& event )
{
	if ( mDragStarted )
	{
		OnMotion( event ); // simulate last motion event

		mpModel->SetSelectionEditMode( FALSE );

		ReleaseMouse();
		mDragStarted = FALSE;
	}
}

void wxTextEditorView::OnMotion( wxMouseEvent& event )
{
	if ( mDragStarted )
	{
		TPosition textPos;

		if ( event.m_y < 0 && mpModel->GetCursor().mRow == 0 )
			event.m_y = 0;

		PixelsToTextPos( event.m_x, event.m_y, textPos );

		mpModel->OnMoveToPosition( textPos );
	}
}

void wxTextEditorView::OnDblClick( wxMouseEvent& event )
{
	event.Skip();
	mpModel->OnSelectWord();
}

void wxTextEditorView::OnSetFocus( wxFocusEvent& event )
{
	if ( !mLTMode && mCursorOn )
	{
		mpTimer->SetView( this );
		mpTimer->ShowCursor( TRUE );
	}
}

void wxTextEditorView::OnKillFocus( wxFocusEvent& event )
{
	if ( !mLTMode && mCursorOn )
	{
		mpTimer->HideCursor( TRUE );
		mpTimer->SetView( NULL );
	}
}

void wxTextEditorView::HoldCursor( bool hold )
{
	if ( mLTMode || !mCursorOn )
        return;

	if ( !hold )
	{
		if ( wxWindow::FindFocus() != this )
		{	
			mpTimer->HideCursor();
			mpTimer->SetView( NULL );
		}
	}
	else
	{
		mpTimer->SetView( this );
		mpTimer->ShowCursor();
	}
}

void wxTextEditorView::OnKeyDown( wxKeyEvent& event )
{
	// FOR NOW:: hard-coded key-bindings

	mpModel->SetSelectionEditMode( event.ShiftDown() );

	if ( event.ControlDown() )
	{
		if ( event.m_keyCode == WXK_LEFT ) 
			mpModel->OnWordLeft();
		else
		if ( event.m_keyCode == WXK_RIGHT ) 
			mpModel->OnWordRight();
		else
		if ( event.m_keyCode == WXK_UP ) 
			mpModel->OnSlideUp();
		else
		if ( event.m_keyCode == WXK_DOWN ) 
			mpModel->OnSlideDown();
		else
		if ( event.m_keyCode == WXK_HOME ) 
			mpModel->OnStartOfText();
		else
		if ( event.m_keyCode == WXK_END ) 
			mpModel->OnEndOfText();
		else
		if ( event.m_keyCode == WXK_INSERT )
			mpModel->OnCopy();
		else
		{
			if ( mpNextCharReceiver ) 
				mpNextCharReceiver->ProcessEvent( event );
			else
				event.Skip();
		}

		/*
		else
		if ( event.m_keyCode == WXK_NEXT ) 
		
			mpModel->();
		else
		if ( event.m_keyCode == WXK_PRIOR ) 
		
			mpModel->();
		*/
	}
	else
	{
		if ( event.m_keyCode == WXK_LEFT ) 
			mpModel->OnMoveLeft();
		else
		if ( event.m_keyCode == WXK_RIGHT ) 
			mpModel->OnMoveRight();
		else
		if ( event.m_keyCode == WXK_UP ) 
			mpModel->OnMoveUp();
		else
		if ( event.m_keyCode == WXK_DOWN ) 
			mpModel->OnMoveDown();
		else
		if ( event.m_keyCode == WXK_HOME ) 
			mpModel->OnStartOfLine();
		else
		if ( event.m_keyCode == WXK_END ) 
			mpModel->OnEndOfLine();
		else
		if ( event.m_keyCode == WXK_NEXT ) 
			mpModel->OnPageDown();
		else
		if ( event.m_keyCode == WXK_PRIOR ) 
			mpModel->OnPageUp();
		else
		if ( event.m_keyCode == WXK_DELETE )
			mpModel->OnDelete();
		else
		if ( event.m_keyCode ==  WXK_INSERT && event.ShiftDown() )
			mpModel->OnPaste();
		else
		{
			if ( mpNextCharReceiver ) 
				mpNextCharReceiver->ProcessEvent( event );
			else
				event.Skip();
		}
	}	
}

void wxTextEditorView::OnChar( wxKeyEvent& event )
{
	if ( event.ControlDown() )
	{
		if ( event.m_keyCode == 'v' )
			mpModel->OnPaste();
		else
		if ( event.m_keyCode == 'c' )
			mpModel->OnCopy();
		else
		if ( event.m_keyCode == 'z' )
			mpModel->OnUndo();
		else
		if ( event.m_keyCode == 'a' )
			mpModel->OnRedo();
		else
			event.Skip();
	}
	else
	if ( event.AltDown() )
	{
		if ( event.m_keyCode == WXK_BACK ) 
			mpModel->OnUndo();
		else
			event.Skip();
	}
	else
	if ( event.m_keyCode ==  WXK_BACK )
		mpModel->OnDeleteBack();
	else
	if ( event.m_keyCode == WXK_TAB && event.ShiftDown() )
		mpModel->OnShiftSelectionIndent( TRUE );
	else
	{
		if ( !mpModel->SelectionIsEmpty() && event.m_keyCode == WXK_TAB )
			mpModel->OnShiftSelectionIndent( FALSE );
		else
		if ( event.m_keyCode < WXK_START )
			mpModel->OnInsertChar( event.m_keyCode );
		else
		{
			if ( mpNextCharReceiver ) 
				mpNextCharReceiver->ProcessEvent( event );
			else
				event.Skip();
		}
	}
}

void wxTextEditorView::SetModel( wxTextEditorModel* pModel )
{
	mpModel = pModel;

	if ( pModel ) 
	{
		mSelectionStart = pModel->GetStartOfSelection();
		mSelectionEnd   = pModel->GetEndOfSelection();
		mCursorPos      = pModel->GetCursor();
	}
}

void wxTextEditorView::SetSourcePainter( SourcePainter* pPainter )
{
	mpPainter = pPainter;
}

void wxTextEditorView::AddPinPainter( TPinPainterBase* pPainter )
{
	mPinPainters.Add( pPainter );
}

void wxTextEditorView::SetDefaultFont( const wxFont& font )
{
	mFont = font;

#if defined(__WXMSW__) || defined(__WINDOWS__)
    mFont.RealizeResource();
#endif

	mCharDim.x = -1;
	mCharDim.y = -1;

	RecalcPagingInfo();
}

void wxTextEditorView::LoadBgImage( const wxString& fname, long type )
{
	if ( !wxFileExists( fname ) )
        return;

	if ( mBgImage.LoadFile( fname, type ) )
		mHasBgImage = TRUE;

	SetBgImage( mBgImage );
}

void wxTextEditorView::SetBgImage( const wxBitmap& image )
{
	if ( &image != &mBgImage ) 
		mBgImage = image;

	if ( mpBgImageDc ) 
	{
		((wxMemoryDC*)mpBgImageDc)->SelectObject( wxNullBitmap );
		delete mpBgImageDc;
	}

	mpBgImageDc = new wxMemoryDC();
	((wxMemoryDC*)mpBgImageDc)->SelectObject( mBgImage );
}

void wxTextEditorView::SetRowsPerPage( size_t n )
{
	mpModel->SetRowsPerPage( n );

	mRowsPerPage = n;
	SyncScrollbars();
	PositionCursor();
}

void wxTextEditorView::SetMaxColumns( size_t n )
{
	mMaxColumns = n;

	SyncScrollbars();
	PositionCursor();
}

wxFont& wxTextEditorView::GetDefaultFont()
{
	return mFont;
}

void wxTextEditorView::SetLineTrackingMode( bool on, const wxColour& col )
{
	mLTColour = col;
	mLTMode   = on;

	//if ( mpTimer->GetView() == this )
	//	mpTimer->HideCursor();
}

void wxTextEditorView::EnableCursor( bool enable )
{
	mCursorOn = enable;
}

void wxTextEditorView::EnableScrollbars( bool enable )
{
	mScrollingOn = enable;
}

bool wxTextEditorView::IsActiveView()
{
	return (this == mpModel->GetActiveView()) && (wxWindow::FindFocus() == this);
}

void wxTextEditorView::PositionCursor()
{
	if ( !IsActiveView() || !mCursorOn )
        return;

	mpTimer->HideCursor();

	TextPosToScreenPos( mpModel->GetCursor(), mCursorScrPos );

	mpTimer->SetView( this );
	mpTimer->ShowCursor();
}

void wxTextEditorView::PixelsToScrPos( int x, int y, int& scrRow, int& scrCol )
{
	x -= mLeftMargin;
	y -= mTopMargin;

	//if ( x < 0 ) x = 0; // FOR NOW:: horizontal auto-scroll disabled

	scrCol = x / mCharDim.x;
	scrRow = y / mCharDim.y;
}

void wxTextEditorView::PixelsToTextPos( int x, int y, TPosition& textPos )
{
	int scrRow = 0, scrCol = 0;
	PixelsToScrPos( x, y, scrRow, scrCol );

	if ( scrRow + (int)mPagePos.mRow < 0 )
		scrRow = -(int)mPagePos.mRow;

	if ( scrCol + (int)mPagePos.mCol < 0 )
		scrCol = -(int)mPagePos.mCol;

	ScreenPosToTextPos( TPosition( scrRow, scrCol ), textPos );
}

void wxTextEditorView::ScreenPosToPixels( const TPosition& scrPos, int& x, int& y )
{
	x = mLeftMargin + scrPos.mCol * mCharDim.x;
	y = mTopMargin  + scrPos.mRow * mCharDim.y;
}

void wxTextEditorView::TextPosToScreenPos( const TPosition& txtPos, TPosition& scrPos )
{
	TTextIterator iter;

	if ( txtPos.mRow != mCashedIter.mPos.mRow )
	{
		iter = mpModel->CreateIterator( txtPos );
		mCashedIter = iter;
	}
	else
	{
		iter = mCashedIter;
		iter.mPos.mCol = txtPos.mCol;
	}

	iter.ToStartOfLine();

	size_t scrCol = 0;
	size_t txtCol = 0;

	while( !iter.IsEol() && txtCol < txtPos.mCol )
	{
		if ( iter.GetChar() == '\t' )
		{
			size_t spacing = ( (scrCol / mpModel->mTabSize) + 1 ) * mpModel->mTabSize - scrCol;
			scrCol += spacing;
		}
		else
			++scrCol;

		++txtCol;
		iter.NextChar();
	}

	TPosition actualPos = iter.GetPosition();

	scrCol += txtPos.mCol - txtCol;

	scrPos.mRow = actualPos.mRow - mPagePos.mRow;
	scrPos.mCol = scrCol - mPagePos.mCol;
}

void wxTextEditorView::ScreenPosToTextPos( const TPosition& scrPos, TPosition& txtPos )
{
	TPosition absScrPos( scrPos.mRow + mPagePos.mRow, scrPos.mCol + mPagePos.mCol );

	TTextIterator iter = mpModel->CreateIterator( TPosition( absScrPos.mRow, 0 ) );

	size_t scrCol = 0;
	size_t txtCol = 0;

	// iterate over all possible on-screen positions, and find one which matches "absScrPos"

	while( !iter.IsEol() && scrCol < absScrPos.mCol )
	{
		if ( iter.GetChar() == '\t' )
		{
			size_t spacing = ( (scrCol / mpModel->mTabSize) + 1 ) * mpModel->mTabSize - scrCol;

			scrCol += spacing;
		}
		else
			++scrCol;

		++txtCol;
		iter.NextChar();
	}

	TPosition actualPos = iter.GetPosition();

	if ( scrCol == absScrPos.mCol )
	{
		txtPos = actualPos;
		return;
	}
	else
	if ( scrCol < absScrPos.mCol )
	{
		// the absScrPos points past the eol

		txtPos = actualPos;
		txtPos.mCol += absScrPos.mCol - scrCol;
	}
	else
	if ( scrCol > absScrPos.mCol )
	{
		// there should have been a '\t' char, which made us jump too far forward

		txtPos = actualPos;
		--txtPos.mCol;
	}
}

bool wxTextEditorView::IsClipboardCmd( wxKeyEvent& key )
{
	if ( key.ControlDown() && key.m_keyCode == WXK_CONTROL )
		return TRUE;

	if ( key.ShiftDown() && key.m_keyCode == WXK_SHIFT )
		return TRUE;

	if ( key.ControlDown() )
	{
		return ( key.m_keyCode == 'C' ||
				 key.m_keyCode == 'c' ||
				 key.m_keyCode == WXK_INSERT );
	}

	return FALSE;
}

void wxTextEditorView::ObtainFontProperties()
{
	wxClientDC dc(this);
	dc.SetFont( mFont );

	long w,h;

	dc.GetTextExtent( "X", &w, &h );

	mCharDim.x = w;
	mCharDim.y = h;
}

void wxTextEditorView::SyncViewPortPosition()
{

	TPosition pos = mpModel->GetCursor();

	TextPosToScreenPos( pos, pos );
	pos.mRow += mPagePos.mRow;
	pos.mCol += mPagePos.mCol;

	if ( pos.mRow < mPagePos.mRow )
	{
		mPagePos.mRow = pos.mRow;
		mFullRefreshPending = TRUE;
	}
	else
	if ( pos.mRow >= mPagePos.mRow + mRowsPerPage && mRowsPerPage != 0 )
	{
		mPagePos.mRow = pos.mRow - mRowsPerPage + 1;
		mFullRefreshPending = TRUE;
	}

	if ( pos.mCol < mPagePos.mCol )
	{
		mPagePos.mCol = pos.mCol;
		mFullRefreshPending = TRUE;
	}
	else
	if ( pos.mCol >= mPagePos.mCol + mColsPerPage )
	{
		mPagePos.mCol = pos.mCol - mColsPerPage + 1;
		mFullRefreshPending = TRUE;
	}
}

void wxTextEditorView::SyncScrollbars()
{
	if ( !mScrollingOn )
        return;

	size_t nRows = mpModel->GetTotalRowCount();

#if !defined(__WINDOWS__)

	if ( mLastViewStart == mPagePos )
	{
		if ( mLastRowsTotal != nRows )
        {
			mAdjustScrollPending = TRUE;
        }
		return;
	}
#else
	if ( mLastViewStart == mPagePos &&
		 mLastRowsTotal == nRows )

		 return;
#endif
	SetScrollbars( mCharDim.x, mCharDim.y,
				   mMaxColumns,
				   nRows,
				   mPagePos.mCol,
				   mPagePos.mRow,
				   TRUE
				 );

	mLastViewStart = mPagePos;
	mLastRowsTotal = nRows;
}

void wxTextEditorView::ScrollView( int rows, int cols )
{
	int pageRow = (int)mPagePos.mRow; 
	int pageCol = (int)mPagePos.mCol;

	if ( pageRow + rows < 0 ) 
		pageRow = 0;
	else
	if ( pageRow + rows > (int)mpModel->GetTotalRowCount() )
		pageRow = mpModel->GetTotalRowCount();
	else
		pageRow = pageRow + rows;

	mPagePos.mRow = (size_t)pageRow;

	if ( pageCol + cols < 0 )
		pageCol = 0;
	else
		pageCol = pageCol + cols;

	mPagePos.mCol = pageCol;

	mFullRefreshPending = TRUE;
}

void wxTextEditorView::OnModelChanged()
{
	if ( IsActiveView() )
	{	
		mpTimer->HideCursor();
	}

	// invalidate pre-cached iterator
	mCashedIter.mPos = TPosition( (size_t)(-1), 0 );

	SyncViewPortPosition();

	if ( IsActiveView() )
		mpTimer->HideCursor();

	if ( mLTMode )
        mFullRefreshPending = TRUE;

	if ( mpModel->mTextChanged && !mFullRefreshPending )
	{
		wxClientDC dc( this );
		PaintRows( mpModel->mChangedFromRow, mpModel->mChangedTillRow, dc );
	}
	else
	if ( mFullRefreshPending )
	{
		wxClientDC dc( this );
		PaintRows( mPagePos.mRow, mPagePos.mRow + mRowsPerPage, dc );
	}

	if ( IsActiveView() )
	{	
		PositionCursor();
		SyncScrollbars();
	}
}

void wxTextEditorView::Activate()
{
	mpModel->SetStartOfSelection( mSelectionStart );
	mpModel->SetEndOfSelection( mSelectionEnd );

	mpModel->SetRowsPerPage( mRowsPerPage );
	mpTimer->SetView( this );

	if ( mCursorOn )
	
		mpTimer->ShowCursor();

	mpModel->SetActiveView( this );

	mpModel->SetCursor( mCursorPos );
}

void wxTextEditorView::Deactivate()
{
	mSelectionStart = mpModel->GetStartOfSelection();
	mSelectionEnd   = mpModel->GetEndOfSelection();
	mCursorPos      = mpModel->GetCursor();

	if ( mpTimer->GetView() == this && 
		 mCursorOn )
		
		mpTimer->HideCursor( TRUE );
}

/*** protected methods ***/

char*  wxTextEditorView::mpLineBuffer    = NULL;
size_t wxTextEditorView::mpLineBufferLen = 0;

char* wxTextEditorView::GetLineBuffer( size_t len )
{
	if ( mpLineBuffer == NULL || mpLineBufferLen < len )
	{
		if ( !mpLineBuffer )
            mpModel->FreeCharacters( mpLineBuffer );

		mpLineBuffer = mpModel->AllocCharacters( len );

		mpLineBufferLen = len;
	}

	return mpLineBuffer;
}

TPinPainterBase* wxTextEditorView::FindPainterForPin( TPinBase& pin )
{
	int pinTc = pin.mTypeCode;

    size_t i;
	for( i = 0; i != mPinPainters.Count(); ++i )
    {
		if ( mPinPainters[i]->mPinTypeCode == pinTc )
			return mPinPainters[i];
    }

	return NULL;
}

void wxTextEditorView::PaintDecorations( size_t fromRow, 
										 size_t tillRow, 
										 wxDC& dc, TTextIterator& iter )
{
	int dcY = ( fromRow - mPagePos.mRow ) * mCharDim.y + mTopMargin;
		
	size_t curPin = mpModel->FindFirstPinInRange( fromRow, tillRow );

	PinListT& pins = mpModel->GetPins();
	TPinPainterBase* pPainter = NULL;

	size_t prevRow = fromRow;
	int    prevY   = dcY;

	wxPoint pos;
	wxSize  dim( mLeftMargin, mCharDim.y );

	while( curPin != NPOS )
	{
		TPinBase& pin = *pins[curPin];

		if ( pPainter == NULL ||
			 pPainter->mPinTypeCode != pin.mTypeCode )

			 pPainter = FindPainterForPin( pin );
	

		// only pins which have their painters can be "visualized"

		if ( pPainter )
		{
			pos.x = 0;
			pos.y = ( pin.mRow - mPagePos.mRow )* mCharDim.y + mTopMargin;

			if ( prevRow < pin.mRow )
			{
				// fill upper gap

				PaintBackgroundRegion( dc, 0, prevY, 
									   mLeftMargin, 
									   mCharDim.y * ( pin.mRow - prevRow ) + 1 );
			}
			else
				dc.SetBrush( mNormalBkBrush );

			pPainter->DrawPin( &pin, *this, dc, pos, dim );

			prevRow = pin.mRow + 1;
			prevY = pos.y + mCharDim.y;
		}

		++curPin;

		if ( curPin >= pins.Count() ||
			 pins[curPin]->mRow >= tillRow )
        {
			 break;
        }
	}

	// fill the reminder 

	if ( prevRow < tillRow )
    {	
		PaintBackgroundRegion( dc, 0, prevY, 
							   mLeftMargin, 
							   mCharDim.y * ( tillRow - prevRow ) + 1 );
    }
	dc.SetPen( *wxTRANSPARENT_PEN );
}

void wxTextEditorView::PaintRows( size_t fromRow, size_t tillRow, wxDC& dc )
{
	// NOTE:: raws are painted from "fromRow" but not including "tillRow" - [fromRow,tillRow)

	dc.SetPen( *wxTRANSPARENT_PEN );

	// how much on-screen columns are visible?

	size_t fromScrCol = mPagePos.mCol;
	size_t tillScrCol = fromScrCol + mColsPerPage;

	TPosition selStart = mpModel->GetStartOfSelection();
	TPosition selEnd   = mpModel->GetEndOfSelection();

	bool selectionIsEmpty = ( selStart == selEnd );

	wxColour curFgCol;
	wxColour curBkCol;

	wxBrush mLTBrush( mLTColour, wxSOLID );

	// clip given row-region to the current page

	if ( ( fromRow >= mPagePos.mRow + mRowsPerPage) || 
		 ( tillRow <= mPagePos.mRow ) )
		return;

	if ( fromRow < mPagePos.mRow )
        fromRow = mPagePos.mRow;
	if ( tillRow > mPagePos.mRow + mRowsPerPage )
        tillRow = mPagePos.mRow + mRowsPerPage;

	if ( fromRow >= tillRow )
        return;

	// now start the renderng

	dc.SetFont( mFont );

	TTextIterator iter = mpModel->CreateIterator( TPosition( fromRow, 0 ) );

	PaintDecorations( fromRow, tillRow, dc, iter );

	size_t cursorRow = mpModel->GetCursor().mRow;

	size_t curRow;
	for( curRow = fromRow; curRow != tillRow; ++curRow )
	{
		// place text into line-buffer

		iter.ToStartOfLine();
		size_t lineLen = iter.GetLineLen();

		char* lineBuf = GetLineBuffer( lineLen + 1 );

		size_t i = 0;

  		while( !iter.IsEof() && !iter.IsEol() )
  		{
  			lineBuf[i++] = iter.GetChar();
  			iter.NextChar();
		}

		iter.NextChar(); // skip eol

		// obtain "highlights"
  		
		mpPainter->SetState( FALSE, FALSE );
		mpPainter->Init( FALSE );
		mpPainter->ProcessSource( lineBuf, lineLen );
		wxArrayInt& blocks = mpPainter->GetBlocks();

		// setup state vars

		int dcY = ( curRow - mPagePos.mRow ) * mCharDim.y + mTopMargin;
		
		size_t scrCol = 0;
		size_t txtCol = 0;

		size_t curBlk = 0;
		size_t curBlkCol = 0;

		int    chunkLen      = -1;
		size_t chunkTxtStart = 0;
		size_t chunkScrStart = 0;

		// pre-detect occurance of selection

		bool lineHasSelection = ( selStart.mRow == curRow ) ||
			                    ( selEnd.mRow == curRow   );

		bool isInSelection    = ( selStart.mRow <= curRow ) &&
			                    ( selEnd.mRow >= curRow   );

		if ( isInSelection && selStart.mRow == curRow &&
			 selStart.mCol != 0 )
        {
			 isInSelection = FALSE;
        }

		if ( selStart == selEnd ) 
		{
			lineHasSelection = FALSE;
			isInSelection = FALSE;
		}
		
		char ch = '\0';

		if ( mHasBgImage )
		{
			int dcX = mLeftMargin;

#if 0
				wxBrush gr( wxColour( 128,128,255 ), wxSOLID );
				dc.SetBrush ( gr );
				dc.DrawRectangle( dcX, dcY, 
								   ( tillScrCol - fromScrCol ) * mCharDim.x + 1, 
								   mCharDim.y + 1 );
#else
			PaintBackgroundRegion( dc, dcX, dcY, 
								   ( tillScrCol - fromScrCol ) * mCharDim.x + 1, 
								   mCharDim.y + 1 );
#endif
		}

		// loop though the text in this row

		bool curCharInSel = FALSE;

		do
		{
			TPosition curPos( curRow, txtCol );

			// first check if we can finish the current chunk

			bool finishChunk = FALSE;

			if ( curBlk < blocks.Count() &&
				 curBlkCol + get_src_block_len( blocks[curBlk] ) == txtCol )
			{
				curBlkCol += get_src_block_len( blocks[curBlk] );
				++curBlk;
				finishChunk = TRUE;
			}
			else
			if ( ( !selectionIsEmpty && ( curPos == selStart || curPos == selEnd ) )
				 || lineBuf[txtCol] == '\t' 
				 || txtCol == lineLen )

				 finishChunk = TRUE;
			
			if ( finishChunk && chunkLen != -1 )
			{
				// is any part of the chunk visible?

				size_t chunkScrEnd = chunkScrStart + chunkLen;

				if ( ( // if hits from one side or is inside
					   ( chunkScrStart >= fromScrCol &&
					     chunkScrStart <  tillScrCol    ) ||
					   ( chunkScrEnd   >= fromScrCol &&
					     chunkScrEnd   <  tillScrCol    ) ) ||

					   // if overlaps the whole range
					 (	chunkScrStart <   fromScrCol &&
					    chunkScrEnd   >=  tillScrCol      )

				   )
				{
					// render chunk data to the given DC

					dc.SetTextForeground( curFgCol );
					dc.SetTextBackground( curBkCol );

					// clip left edge

					if ( chunkScrStart < fromScrCol )
					{
						size_t diff = fromScrCol - chunkScrStart;
						chunkLen      -= diff;
						chunkTxtStart += diff;
						chunkScrStart += diff;
					}

					// clip right edge

					if ( chunkScrEnd > tillScrCol )
					{
						size_t diff  = chunkScrEnd - tillScrCol;
						chunkLen    -= diff;
						chunkScrEnd -= diff;
					}

					// create string

					char tmp = lineBuf[chunkTxtStart + chunkLen];
					
					lineBuf[chunkTxtStart + chunkLen] = '\0';

					// use member-variable, reuse heap-buffer between outputs
					mFragment = lineBuf + chunkTxtStart;

					lineBuf[chunkTxtStart + chunkLen] = tmp;

					// draw it

					int dcX = (chunkScrStart - fromScrCol) * mCharDim.x + mLeftMargin;

					if ( !curCharInSel && mHasBgImage )
                        dc.SetBackgroundMode( wxTRANSPARENT );
                    else
                        dc.SetBackgroundMode( wxSOLID );

					dc.DrawText( mFragment, dcX, dcY );
				}

				chunkLen = -1;

			} // end of "if ( finishChunk )"

			if ( txtCol == lineLen ) 
				break;

			if ( chunkLen == -1 )
			{
				// prepare the new chunk

				if ( curBlk < blocks.Count() )
				{
						switch( get_src_block_rank( blocks[curBlk] ) )
						{
							case RANK_BLACK : 
                                curFgCol = mNormalTextCol; 
                                break;
							case RANK_BLUE  : 
                                curFgCol = mIndentifierTextCol;
                                break;
							case RANK_RED   :
                                curFgCol = mReservedWordTextCol;
                                break;
							case RANK_GREEN :
                                curFgCol = mCommentTextCol;
                                break;
							default : 
                                break;
						}
				}

				// track occurence of selection

				if ( lineHasSelection )
				{
					isInSelection = TRUE;

					if ( selEnd.mRow == curRow &&
						 selEnd.mCol <= txtCol )

						 isInSelection = FALSE;

					if ( selStart.mRow == curRow &&
						 selStart.mCol > txtCol )

						 isInSelection = FALSE;
				}

				if ( isInSelection )
				{
					curFgCol = mSelectionFgCol;
					curBkCol = mSelectionBkCol;
					curCharInSel = TRUE;
				}
				else
				{
					if ( mLTMode && curRow == cursorRow ) 
						curBkCol = mLTColour;
					else
						curBkCol = mNormalBkCol ;

					curCharInSel = FALSE;
				}

				chunkScrStart = scrCol;
				chunkTxtStart = txtCol;
				chunkLen      = 0;
			}

			
			ch = lineBuf[txtCol];

			if ( ch == '\t' )
			{
				// tab's are treated specially (for simplicity and speed)

				int dcX = (chunkScrStart - fromScrCol) * mCharDim.x + mLeftMargin;

				if ( !isInSelection ) 
				{
					if ( mLTMode && curRow == cursorRow ) 
						dc.SetBrush( mLTBrush );
					else 
						dc.SetBrush( mNormalBkBrush );
				}
				else dc.SetBrush( mSelectedBkBrush );

				// *** "the rule of TAB..." ***

				size_t spacing = ( (scrCol / mpModel->mTabSize) + 1 ) * mpModel->mTabSize - scrCol;

				int width = spacing * mCharDim.x + 1;

				if ( dcX < mLeftMargin )
				{
					width -= mLeftMargin - dcX;

					dcX = mLeftMargin;
				}

				if ( width > 0 && ( !mHasBgImage || curCharInSel ) )
				
					dc.DrawRectangle( dcX, dcY, width, mCharDim.y + 1 );

				scrCol += spacing;
				txtCol += 1;
				
				// move chunk-start forward, after the occurance of '\t'

				chunkLen = -1;
			}
			else
			{
				// increase on-screen/in-text positions

				++scrCol;
				++txtCol;
				++chunkLen;
			}
		
		} while( TRUE );

		// fill the remaining white-space after eol

		if ( scrCol < tillScrCol && 
			 ( !isInSelection ||
			   ( isInSelection && curRow == selEnd.mRow ) )
		   )
		{
			if ( scrCol < fromScrCol )
                scrCol = fromScrCol;

			int dcX = ( scrCol - fromScrCol ) * mCharDim.x + mLeftMargin;

			if ( mLTMode && curRow == cursorRow )
			{
				dc.SetBrush ( mLTBrush );
				dc.DrawRectangle( dcX, dcY, 
								  mCharDim.x * ( tillScrCol - scrCol ) + 1, 
								  mCharDim.y + 1 );
			}
			else
			{
#if 0
				wxBrush gr( wxColour( 0,255,0 ), wxSOLID );
				dc.SetBrush ( gr );
				dc.DrawRectangle( dcX, dcY, 
								  mCharDim.x * ( tillScrCol - scrCol ) + 1, 
								  mCharDim.y + 1 );
#else
				PaintBackgroundRegion( dc, dcX, dcY,
									   mCharDim.x * ( tillScrCol - scrCol ) + 1, 
									   mCharDim.y + 1 );
#endif
			}
		}

		// render selection which is located past the eol

		if ( ( lineHasSelection || isInSelection ) && 
			 !( selEnd.mRow == curRow && selEnd.mCol <= txtCol )
		   )
		{
			// determine start of selection on-screen

			size_t scrSelStart = scrCol + ( selStart.mCol - txtCol );

			if ( isInSelection )
				scrSelStart = scrCol;

			size_t scrSelEnd = tillScrCol;

			if ( selEnd.mRow == curRow )
				scrSelEnd = scrCol + ( selEnd.mCol - txtCol );

			// clipping 

			if ( scrSelStart < fromScrCol )
                scrSelStart = fromScrCol;
			if ( scrSelEnd   > tillScrCol )
                scrSelEnd = tillScrCol;

			// drawing 

			if ( scrSelEnd > scrSelStart )
			{
				int dcX = ( scrSelStart - fromScrCol ) * mCharDim.x + mLeftMargin;

				dc.SetBrush( mSelectedBkBrush );
				dc.DrawRectangle( dcX, dcY, 
								  mCharDim.x * ( scrSelEnd - scrSelStart ) + 1, 
								  mCharDim.y + 1 );
			}
		}

		if ( iter.IsEof() ) 
		{
			++curRow;
			break;
		}

	} // end of "for(...)"

	if ( curRow < tillRow )
	{
		// fill in the reminder of the rows-block (in case there's no more text left)
		
		int dcY = mTopMargin + (curRow - mPagePos.mRow)*mCharDim.y;
		int dcX = mLeftMargin;

		PaintBackgroundRegion( dc, dcX, dcY, mColsPerPage*mCharDim.x + 1, 
							   ( tillRow - curRow ) * mCharDim.y + 1
							 );
	}

	if ( mFullRefreshPending )
	{
		dc.SetBrush( mNormalBkBrush );

		// fill in "corners" which are never reached by characters
		
		int w,h;
		GetClientSize( &w, &h );

		int dcX = tillScrCol*mCharDim.x + mLeftMargin;

		// right "corner"

		PaintBackgroundRegion( dc, dcX, mTopMargin, w - dcX + 1, h );

		int dcY = mTopMargin + mRowsPerPage*mCharDim.y;

		// bottom "corner"
		PaintBackgroundRegion( dc, 0, dcY, w, h - dcY + 2 );

		++curRow;

		// any past-the-eof lines left at the bottom?
	}

	mFullRefreshPending = FALSE;

	if ( mpTimer->GetView() == this && mCursorOn && !mLTMode ) 

		mpTimer->Unlock();

} // end of PaintRows(..)

void wxTextEditorView::PaintBackgroundRegion( wxDC& dc, int x, int y, 
											  int width, int height )
{
	if ( !mHasBgImage )
	{
		dc.SetBrush( mNormalBkBrush );
		dc.SetPen( *wxTRANSPARENT_PEN );
		dc.DrawRectangle( x,y, width, height );
		return;
	}

	unsigned int iw = mBgImage.GetWidth(), ih = mBgImage.GetHeight();

	int dispX = -1 * ( ( mPagePos.mCol * mCharDim.x ) % iw );  //glt
	int dispY = -1 * ( ( mPagePos.mRow * mCharDim.y ) % ih );  //glt
	int x1 = ( x + dispX )  / iw;
	int y1 = ( y + dispY )  / ih;
	int x2 = ( x + width )  / iw;
	int y2 = ( y + height ) / ih;

	// FIXME:: hack?
	--y1; --x1;
	++y2; ++x2;

    int a;
	for( a = y1; a <= y2; ++a )
	{
        int b;
		for( b = x1; b <= x2; ++ b )
		{
			int rx = b * iw + dispX; // cell x 
			int ry = a * ih + dispY; // cell y

			// now get intersection of the given region with the current 
			// background "cell"

			int _x = ( x > rx ) ? x : rx;
			int _y = ( y > ry ) ? y : ry;

			unsigned int botX = ( x + width  < (signed)(rx + iw)  ) ? x + width  : rx + iw;
			unsigned int botY = ( y + height < (signed)(ry + ih) )  ? y + height : ry + ih;

			int _width  = botX - _x;
			int _height = botY - _y;

			// if upper-left and lower-right conners aren't correct -
			// intersection area is empty

			if ( _width  < 0 ||
				 _height < 0 ) 
				continue;

			dc.Blit( _x, _y, _width, _height, mpBgImageDc, 
					 _x - rx, _y - ry );
		}
	}
}

/***** Implementation for class TBookmarkPainter *****/

TBookmarkPainter::TBookmarkPainter() 

	: TPinPainterBase( BOOKMARK_PIN_TC ),
	  mBkBrush( wxColour( 0,255,255 ), wxSOLID )
{
}

void TBookmarkPainter::DrawPin( TPinBase* pPin, wxTextEditorView& view, wxDC& dc, 
							    const wxPoint& pos, const wxSize& dim )
{
	dc.SetPen( *wxBLACK_PEN );
	dc.SetBrush( mBkBrush );
	dc.DrawRoundedRectangle( pos.x+2, pos.y, dim.x-4, dim.y, 4 );
}

/***** Implementation for class TBreakpointPainter *****/

TBreakpointPainter::TBreakpointPainter() 

	: TPinPainterBase( BRKPOINT_PIN_TC ),
	  mBkBrush( wxColour( 196,0,0 ), wxSOLID )
{
}

void TBreakpointPainter::DrawPin( TPinBase* pPin, wxTextEditorView& view, wxDC& dc, 
								  const wxPoint& pos, const wxSize& dim )
{
	dc.SetPen( *wxBLACK_PEN );
	dc.SetBrush( mBkBrush );
	dc.DrawRoundedRectangle( pos.x+6, pos.y+2, dim.x-12, dim.y-4, 30 );
}

/***** Implementation for class TCursorTimer *****/

TCursorTimer::TCursorTimer()

	: mIsLocked( FALSE ),
	  mIsShown ( FALSE ),
	  mStarted ( FALSE ),
	  mBrush( wxColour(0,0,0), wxSOLID ),
	  mMissOneTick( FALSE ),
	  mBlinkInterval( 500 )
{
}

void TCursorTimer::Notify()
{
	if ( mIsLocked )
        return;

	if ( mMissOneTick )
	{
		// this trick is used because it's not
		// possible to restart the timer under wxGtk

		mMissOneTick = FALSE;
		return;
	}


	mIsLocked = TRUE;

	DrawCursor();

	mIsShown = !mIsShown;

	mIsLocked = FALSE;
}

void TCursorTimer::SetView( wxTextEditorView* pView )
{
	mpView = pView;
}

wxTextEditorView* TCursorTimer::GetView()
{
	return mpView;

}

void TCursorTimer::HideCursor( bool forceHide )
{
	Lock();

	if ( mIsShown  ) 
	{
		DrawCursor();
		mIsShown = FALSE;
	}

	Unlock();
}

void TCursorTimer::ShowCursor( bool forceShow )
{
	Lock();

	if ( !mIsShown )
	{
		DrawCursor();
		mIsShown = TRUE;
	}

	//if ( mStarted )
	//	mMissOneTick = TRUE;

	Unlock();

	if ( !mStarted )
	{
		Start( mBlinkInterval );
		mStarted = TRUE;
	}
	else
	{
		Stop();
		Start( mBlinkInterval );
	}
}

void TCursorTimer::Lock()
{
//	while( mIsLocked );

	mIsLocked = TRUE;
}

void TCursorTimer::Unlock()
{
	mIsLocked = FALSE;
}

void TCursorTimer::SetIsShown( bool isShown )
{
	mIsShown = isShown;
}

/*** protected methods ***/

void TCursorTimer::DrawCursor()
{
	if ( mpView == NULL )
        return;

	wxClientDC dc( mpView );

	int x = 0, y = 0;

	mpView->ScreenPosToPixels( mpView->mCursorScrPos, x, y );

	//dc.SetLogicalFunction( wxINVERT  );
	dc.SetLogicalFunction( wxOR_REVERSE  );
	dc.SetBrush( mBrush );

	dc.SetPen( *wxTRANSPARENT_PEN );
	dc.DrawRectangle( x,y, 2, mpView->mCharDim.y + 1 );
	dc.SetBackgroundMode( wxSOLID );
}

