/////////////////////////////////////////////////////////////////////////////
// Name:        No names yet.
// Purpose:     Contrib. demo
// Author:      Aleksandras Gluchovas
// Modified by:
// Created:     2000/02/10
// RCS-ID:      $Id: bmpeditor.cpp,v 1.4 2001/04/04 12:42:36 aleksandras Exp $
// Copyright:   (c) Aleksandras Gluchovas
// Licence:     wxWindows licence
/////////////////////////////////////////////////////////////////////////////

#ifdef __GNUG__
    #pragma implementation "bmpeditor.cpp"
    #pragma interface "bmpeditor.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 "bmpeditor.h"

// X-Framework stuff
#include "XObject.h"
#include "XCellBackground.h"
#include "XSelectionEditor.h"
#include "XRectEditor.h"

/***** Implementation for class BmpEditorActionData *****/

BmpEditorActionData::BmpEditorActionData()

	: mXPositions( NULL ),
	  mYPositions( NULL ),
	  mSetPixels ( NULL ),
	  mOrigPixels( NULL ),
	  
	  mPixelCount( 0 ),
	  mNewSize( -1, -1 ),
	  mOrigSize( -1, -1 )
{
}

BmpEditorActionData::BmpEditorActionData( int nCols, int nRows, char* newPixels,
										  int nOrigCols, int nOrigRows, char* origPixels)

	: mXPositions( NULL ),
	  mYPositions( NULL )
{
	mNewSize.x = nCols;
	mNewSize.y = nRows;
	mOrigSize.x = nOrigCols;
	mOrigSize.y = nOrigRows;

	if ( nCols != nOrigCols || nRows != nOrigRows )
	{
		mSetPixels  = new char[ nCols * nRows ];
		mOrigPixels = new char[ nOrigCols * nOrigRows ];
		memcpy( mSetPixels,  newPixels,  nCols * nRows );
		memcpy( mOrigPixels, origPixels, nOrigCols * nOrigRows );

		return;
	}

	// save differences pixel-wise, if bitmap-size did not change

	int cnt = nCols * nRows;

	int i = 0, diffs = 0;

	for( ; i != cnt; ++i )

		if ( origPixels[i] != newPixels[i] ) ++diffs;

	mPixelCount = diffs;

	mSetPixels   = new char[ diffs ];
	mOrigPixels  = new char[ diffs ];
	mXPositions  = new int [ diffs ];
	mYPositions  = new int [ diffs ];

	int pos = 0;

	for( i = 0; i != cnt; ++i )

		if ( origPixels[i] != newPixels[i] )
		{
			mSetPixels[ pos ]  = newPixels[i];
			mOrigPixels[ pos ] = origPixels[i];
			mXPositions[ pos ] = i % nCols;
			mYPositions[ pos ] = i / nCols; 

			++pos;
		}
}

BmpEditorActionData::~BmpEditorActionData()
{
	if ( mXPositions  ) delete [] mXPositions;
	if ( mYPositions  ) delete [] mYPositions;
	if ( mSetPixels   ) delete [] mSetPixels;
	if ( mOrigPixels  ) delete [] mOrigPixels;
}

/***** Implementation for class BmpEditorData *****/

BmpEditorData::BmpEditorData()

	: mPixels( NULL ),
	  mColours( NULL ),
	  mBrushes( NULL ),
	  mColourCount( 0 ),
	  mRows( 0 ),
	  mCols( 0 ),
	  mPixelSize( 0 ),
	  mCmdHeadIndex( 0 )
{}

BmpEditorData::~BmpEditorData()
{
	if ( mPixels  ) delete [] mPixels;
	if ( mColours ) delete [] mColours;
	if ( mBrushes ) delete [] mBrushes;

	WX_CLEAR_ARRAY( mActions );
}

wxColour _gBmpColours[] = { wxColour( 255,0,0 ),
							wxColour( 0,255,0 ),
							wxColour( 0,0,255 ),
							wxColour( 0,0,0 ),
							wxColour( 255,255,255 ),
							wxColour( 255,255,0 ),
							wxColour( 0,255,255 ),
							wxColour( 255,0,255 ) };


void BmpEditorData::InitColours()
{
	if ( mColours ) delete [] mColours;
	if ( mBrushes ) delete [] mBrushes;

	mColourCount = 8;

	mColours = new wxColour[ mColourCount ];
	mBrushes = new wxBrush[ mColourCount ];

	for( int i = 0; i != mColourCount; ++i )
	{	
		mColours[ i ] = _gBmpColours[ i ];
		mBrushes[ i ] = wxBrush( _gBmpColours[ i ], wxSOLID );
	}
}

void BmpEditorData::InitPixels( int nCols, int nRows, char col, int pixelSize )
{
	if ( mPixels ) delete [] mPixels;

	mPixels = new char[ nCols * nRows ];

	memset( mPixels, (int)col, nCols * nRows );

	mCols	   = nCols;
	mRows	   = nRows;
	mPixelSize = pixelSize;
}

void BmpEditorData::SetPixelColour( int x, int y, char colour )
{
	mPixels[ x + y * mCols ] = colour;
}

void BmpEditorData::DrawLine( int x1, int y1, int x2, int y2, char colour )
{
	int dx = abs( x2 - x1 );
	int dy = abs( y2 - y1 );
	int sx = x2 >= x1 ? 1 : -1;
	int sy = y2 >= y1 ? 1 : -1;

	mPixels[ x1 + y1 * mCols ] = colour;

	if ( dy <= dx )
	{
		int d = ( dy << 1 ) - dx;
		int d1 = dy << 1;
		int d2 = ( dy - dx ) << 1;

		for( int x = x1 + sx, y = y1, i = 1; i <= dx; ++i, x += sx )
		{
			if ( d > 0 ) 
			{
				d += d2;
				y += sy;
			}
			else
				d += d1;

			mPixels[ x + y * mCols ] = colour;
		}
	}
	else
	{
		int d = ( dx << 1 ) - dy;
		int d1 = dx << 1;
		int d2 = ( dx - dy ) << 1;

		for( int x = x1, y = y1 + sy, i = 1; i <= dy; ++i, y += sy )
		{
			if ( d > 0 )
			{
				d += d2;
				x += sx;
			}
			else
				d += d1;

			mPixels[ x + y * mCols ] = colour;
		}
	}
}

void BmpEditorData::DoFloodFill( int xPos, int yPos, char colour )
{
	if ( xPos < 0 || yPos < 0 || xPos >= mCols || yPos >= mRows )

		return;

	int col = mPixels[ xPos + yPos * mCols ];

	if ( col != colour || col == (char)255 ) 

		return;

	mPixels[ xPos + yPos * mCols ] = (char)255;

	DoFloodFill( xPos+1, yPos, colour );
	DoFloodFill( xPos, yPos+1, colour );
	DoFloodFill( xPos-1, yPos, colour );
	DoFloodFill( xPos, yPos-1, colour );
}

void BmpEditorData::FloodFill( int xPos, int yPos, char fillColour )
{
	int floodCol = mPixels[ xPos + yPos * mCols ];
	
	DoFloodFill( xPos+1, yPos, floodCol );
	DoFloodFill( xPos, yPos+1, floodCol );
	DoFloodFill( xPos-1, yPos, floodCol );
	DoFloodFill( xPos, yPos-1, floodCol );

	mPixels[ xPos + yPos * mCols ] = (char)255;

	int cnt = mRows * mCols;

	for( int i = 0; i != cnt; ++i )

		if ( mPixels[i] == (char)255 )

			mPixels[i] = fillColour;
}

void BmpEditorData::FillRectangle( int xPos, int yPos, int width, int height, char fillColour )
{
	for( int x = xPos; x != xPos + width; ++x )

		for( int y = yPos; y != yPos + height; ++y )

			mPixels[ x + y * mCols ] = fillColour;
}

void BmpEditorData::DrawEllipse( int x1, int y1, int x2, int y2, char colour )
{
	int a = ( x2 - x1 ) / 2;
	int b = ( y2 - y1 ) / 2;
	int mx = x1 + a;
	int my = y1 + b;

int   x,  mx1,mx2,  my1,my2,
      aq,bq, dx,dy, r,rx,ry;

  SetPixelColour( mx + a, my, colour);
  SetPixelColour( mx - a, my, colour);

  mx1 = mx - a;   my1 = my;
  mx2 = mx + a;   my2 = my;

  aq = (a) * a;        // {calc sqr}
  bq = (b) * b;
  dx = aq << 1;       //        {dx := 2 * a * a}
  dy = bq << 1;       //        {dy := 2 * b * b}
  r  = a * bq;         //        {r  := a * b * b}
  rx = r << 1;        //        {rx := 2 * a * b * b}
  ry = 0;              //        {because y = 0}
  x = a;

  while ( x > 0 )
  {
    if ( r > 0 )
	{
      ++my1;   --my2;
      ry += dx;			     // {ry = dx * y}
      r  -= ry;		         //    {r = r - dx + y}
	}

    if ( r <= 0 )
	{
							  //    { x - 1 }
      --x;
      ++mx1;   
      --mx2;
      rx -= dy;			     //  {rx = dy * x}
      r  += rx;            //  {r = r + dy * x}
    }

    SetPixelColour(mx1, my1, colour);
    SetPixelColour(mx1, my2, colour);
    SetPixelColour(mx2, my1, colour);
    SetPixelColour(mx2, my2, colour);
  }
}

void BmpEditorData::AddAction( BmpEditorActionData* pAction )
{
	if ( mCmdHeadIndex < (signed)mActions.Count() )
	{
		// delete all the actions ahead of the added one

		unsigned int i = mCmdHeadIndex;

		while( i != mActions.Count() )
		{
			delete mActions[ i ];

			mActions.Remove( i );
		}
	}

	mActions.Add( pAction );
	++mCmdHeadIndex;
}

void BmpEditorData::Undo()
{
	if ( mCmdHeadIndex == 0 )
        return;

	--mCmdHeadIndex;

	DoApplyAction( mActions[ mCmdHeadIndex ], TRUE );
}

void BmpEditorData::Redo()
{
	if ( mCmdHeadIndex >= (signed)mActions.Count() ) 
        return;

	DoApplyAction( mActions[ mCmdHeadIndex ], FALSE );	

	++mCmdHeadIndex;
}

bool BmpEditorData::CanUndo()
{
	return mCmdHeadIndex != 0;
}

bool BmpEditorData::CanRedo()
{
	return mCmdHeadIndex < (signed)mActions.Count();
}

/*** protected methods ***/

void BmpEditorData::DoApplyAction( BmpEditorActionData* pAction, bool reverse )
{
	if ( pAction->mNewSize.x != pAction->mOrigSize.x ||
		 pAction->mNewSize.y != pAction->mOrigSize.y )
	{
		if ( reverse )
		{
			InitPixels( pAction->mOrigSize.x, pAction->mOrigSize.y, mPixelSize );
			memcpy( mPixels, pAction->mOrigPixels, mCols * mRows );
		}
		else
		{
			InitPixels( pAction->mNewSize.x, pAction->mNewSize.y, mPixelSize );
			memcpy( mPixels, pAction->mSetPixels, mCols * mRows );
		}
	}
	else
		for( int i = 0; i != pAction->mPixelCount; ++i )
		{
			int idx = pAction->mXPositions[ i ] + pAction->mYPositions[ i ] * mCols;

			if ( reverse )

				mPixels[ idx ] = pAction->mOrigPixels[ i ];
			else
				mPixels[ idx ] = pAction->mSetPixels[ i ];
		}
}

/***** Implementation for class BitmapEditorDoc *****/

IMPLEMENT_DYNAMIC_CLASS( BitmapEditorDoc, ResItemDocumentBase )

BitmapEditorDoc::BitmapEditorDoc()
{
}

BitmapEditorDoc::~BitmapEditorDoc()
{
}

bool BitmapEditorDoc::OnNewDocument()
{
	// FOR NOW::

	mpData = new BmpEditorData();

	mpData->InitColours();
	mpData->InitPixels( 16,16, 0, 10 );

	UpdateAllViews();

	return TRUE;
}

BmpEditorData* BitmapEditorDoc::GetData()
{
	return mpData;
}

void BitmapEditorDoc::SetRootNode( wxDMNode* pRoot )
{
	mpData = (BmpEditorData*)pRoot;
}

wxDMNode* BitmapEditorDoc::GetRootNode()
{
	return mpData;
}

WidgetResTemplate* BitmapEditorDoc::GetTemplateForNode( wxDMNode& node )
{
	return NULL;
}

bool BitmapEditorDoc::CanHaveFile()
{
	return TRUE;
}

bool BitmapEditorDoc::SavePMDocument()
{
	return TRUE;
}

bool BitmapEditorDoc::LoadPMDocument()
{
	mpData = new BmpEditorData();

	mpData->InitColours();
	mpData->InitPixels( 16,16, 0, 10 );

	UpdateAllViews();

	return TRUE;
}

bool BitmapEditorDoc::IsModified() const
{
	return TRUE;
}

void BitmapEditorDoc::Modify(bool modify)
{
}

/***** Implementation for class BitmapEditorView *****/

static wxCommandInfo _gBitmapEditorToolCommands[] = {

	wxCommandInfo( BitmapEditorView::ID_ZOOM_IN,				"Zoom &in", "", "" ), 
	wxCommandInfo( BitmapEditorView::ID_ZOOM_OUT,				"Zoom &out", "", "" ), 
	wxCommandInfo( -1, "SEPARATOR" ),

	wxCommandInfo( BitmapEditorView::ID_SET_SELECT_MODE,		"&Select region", "", "" ), 
	wxCommandInfo( BitmapEditorView::ID_SET_SELECT_COLOUR_MODE, "Pick &colour", "", "" ), 
	wxCommandInfo( BitmapEditorView::ID_SET_ERASE_MODE,			"&Erase", "", "" ), 
	wxCommandInfo( BitmapEditorView::ID_SET_FILL_MODE,			"&Fill", "", "" ), 
	wxCommandInfo( BitmapEditorView::ID_SET_PENCIL_MODE,		"&Pencil", "", "" ), 
	wxCommandInfo( BitmapEditorView::ID_SET_LINE_MODE,			"&Line", "", "" ), 
	wxCommandInfo( BitmapEditorView::ID_SET_RECT_MODE,			"&Rectangle", "", "" ), 
	wxCommandInfo( BitmapEditorView::ID_SET_SOLID_RECT_MODE,	"Solid Rectangle", "", "" ), 
	wxCommandInfo( BitmapEditorView::ID_SET_ELLIPSE_MODE,		"&Ellipse", "", "" ), 
	wxCommandInfo( BitmapEditorView::ID_SET_SOLID_ELLIPSE_MODE,	"Solid Ellipse", "", "" ), 

	wxCommandInfo( 0, "" ) // terminator
};

static wxCommandInfo _gBitmapEditorEditCommands[] = {

	wxCommandInfo( BitmapEditorView::ID_UNDO, "&Undo", "", "Ctrl+Z|Alt+BACK" ), 
	wxCommandInfo( BitmapEditorView::ID_REDO, "&Redo", "", "Ctrl+A|Ctrl+BACK" ), 

	wxCommandInfo( 0, "" ) // terminator
};

static wxPMMergableMenuChain _gBitmapEditorChains[] = {

	wxPMMergableMenuChain( "/&Edit/&Tool/", _gBitmapEditorToolCommands ),

	wxPMMergableMenuChain( "/&Edit/", _gBitmapEditorEditCommands ),

	wxPMMergableMenuChain( "", NULL ) // terminator
};

IMPLEMENT_DYNAMIC_CLASS( BitmapEditorView, wxPMView )

BEGIN_EVENT_TABLE( BitmapEditorView, wxPMView )

	EVT_MENU( ID_UNDO,						BitmapEditorView::OnUndo )
	EVT_MENU( ID_REDO,						BitmapEditorView::OnRedo )
	EVT_MENU( ID_ZOOM_IN,					BitmapEditorView::OnZoomIn )
	EVT_MENU( ID_ZOOM_OUT,					BitmapEditorView::OnZoomOut )
	EVT_MENU( ID_SET_CURSOR_MODE,			BitmapEditorView::OnSetCursorMode )
	EVT_MENU( ID_SET_SELECT_MODE,			BitmapEditorView::OnSetSelectMode )
	EVT_MENU( ID_SET_SELECT_COLOUR_MODE,	BitmapEditorView::OnSetSelectColourMode )
	EVT_MENU( ID_SET_ERASE_MODE,			BitmapEditorView::OnSetEraseMode )
	EVT_MENU( ID_SET_FILL_MODE,				BitmapEditorView::OnSetFillMode )
	EVT_MENU( ID_SET_PENCIL_MODE,			BitmapEditorView::OnSetPencilMode )
	EVT_MENU( ID_SET_LINE_MODE,				BitmapEditorView::OnSetLineMode )
	EVT_MENU( ID_SET_RECT_MODE,				BitmapEditorView::OnSetRectMode )
	EVT_MENU( ID_SET_SOLID_RECT_MODE,		BitmapEditorView::OnSetSolidRectMode )
	EVT_MENU( ID_SET_ELLIPSE_MODE,			BitmapEditorView::OnSetEllipseMode )
	EVT_MENU( ID_SET_SOLID_ELLIPSE_MODE,	BitmapEditorView::OnSetSolidEllipseMode )

END_EVENT_TABLE()

BitmapEditorView::BitmapEditorView()

	: mpData( NULL ),
	  mpScene( NULL ),
	  mpSurface( NULL ),
	  
	  mMode( ELLIPSE_MODE ),
	  mBgColour( 1 ),
	  mFgColour( 2 )
{
}

BitmapEditorView::~BitmapEditorView()
{
	if ( mpScene ) 
	{
		delete mpScene;
		//GetViewFrame()->PopEventHandler();
	}
}

wxPMMergableMenuChain* BitmapEditorView::GetMergableMenuChain()
{
	return _gBitmapEditorChains;
}

bool BitmapEditorView::OnCreate(wxDocument* doc, long flags)
{
	if ( !wxPMView::OnCreate( doc, flags ) )
        return FALSE;

	wxASSERT( doc->IsKindOf( CLASSINFO(BitmapEditorDoc) ) ); // DBG::

	mpData = GetEditorDoc().GetData();

	mpScene = new XCollection();

	mpScene->SetCanvasOrigin( XPoint(30,30) );

	XCellBackground* pBg = new XCellBackground( *wxWHITE );
	mpScene->Insert( pBg );

	//XSelectionEditor* pEditor = new XSelectionEditor();
	//mpScene->SetEditor( pEditor );

	mpScene->MarkBackgroundObj( *pBg );
	
	XwxWindowHook* pHook = new XwxWindowHook( GetViewFrame(), mpScene );

    return TRUE;
}

void BitmapEditorView::OnUpdate(wxView* sender, wxObject* hint)
{
	mpData = GetEditorDoc().GetData();

	RecreateScene();
}

void BitmapEditorView::OnUndo( wxCommandEvent& event )
{
	mpData->Undo();
	RefreshSurface();
}

void BitmapEditorView::OnRedo( wxCommandEvent& event )
{
	mpData->Redo();
	RefreshSurface();
}

void BitmapEditorView::OnSetCursorMode( wxCommandEvent& event )
{
}

void BitmapEditorView::OnSetSelectMode( wxCommandEvent& event )
{
	DoSetMode( SELECT_MODE );
}

void BitmapEditorView::OnSetSelectColourMode( wxCommandEvent& event )
{
	DoSetMode( SELECT_COLOUR_MODE );
}

void BitmapEditorView::OnSetEraseMode( wxCommandEvent& event )
{
	DoSetMode( ERASE_MODE );
}

void BitmapEditorView::OnSetFillMode( wxCommandEvent& event )
{
	DoSetMode( FILL_MODE );
}

void BitmapEditorView::OnZoomIn( wxCommandEvent& event )
{
}

void BitmapEditorView::OnZoomOut( wxCommandEvent& event )
{
}

void BitmapEditorView::OnSetPencilMode( wxCommandEvent& event )
{
	DoSetMode( PENCIL_MODE );
}

void BitmapEditorView::OnSetLineMode( wxCommandEvent& event )
{
	DoSetMode( LINE_MODE );
}

void BitmapEditorView::OnSetRectMode( wxCommandEvent& event )
{
	DoSetMode( RECT_MODE );
}

void BitmapEditorView::OnSetSolidRectMode( wxCommandEvent& event )
{
	DoSetMode( SOLID_RECT_MODE );
}

void BitmapEditorView::OnSetEllipseMode( wxCommandEvent& event )
{
	DoSetMode( ELLIPSE_MODE );
}

void BitmapEditorView::OnSetSolidEllipseMode( wxCommandEvent& event )
{
	DoSetMode( SOLID_ELLIPSE_MODE );
}

BmpEditorData& BitmapEditorView::GetData()
{
	return *mpData;
}

int BitmapEditorView::GetMode() { return mMode; }
int BitmapEditorView::GetBgColour() { return mBgColour; }
int BitmapEditorView::GetFgColour() { return mFgColour; }

void BitmapEditorView::SetMode( int mode ) { mMode = mode; }
void BitmapEditorView::SetBgColour( int colour ) { mBgColour = colour; }
void BitmapEditorView::SetFgColour( int colour ) { mFgColour = colour; }

/*** protected methods ***/

void BitmapEditorView::DoSetMode( int mode )
{
	mMode = mode;
	RefreshSurface();
}

void BitmapEditorView::RefreshSurface()
{
	mpScene->InvalidateRect( mpSurface->GetInclusiveBounds() );
	mpScene->RefreshNow();
}

void BitmapEditorView::RecreateScene()
{
	if ( mpSurface )

		mpScene->Remove( mpSurface );

	mpSurface = new XBitmapSurface( this );

	mpScene->Insert( mpSurface, FALSE );
	mpScene->SelectObject( *mpSurface, FALSE );

	XRect bounds( 0,0, mpData->mCols * mpData->mPixelSize, 
					   mpData->mRows * mpData->mPixelSize  );

	mpSurface->SetBounds( bounds );

	XRectEditor* pEditor = new XRectEditor();

	mpSurface->SetEditor( pEditor );

	pEditor->EnableMove( FALSE );

	RefreshSurface();
}

BitmapEditorDoc& BitmapEditorView::GetEditorDoc()
{
	return *(BitmapEditorDoc*)GetDocument();
}

/***** Impelemntation for class XBitmapSurface *****/

XBitmapSurface::XBitmapSurface( BitmapEditorView* pView )

	:  mpData( &pView->GetData() ),
	   mpView( pView ),
	   mBgBrush( wxColour( 128,128,128 ), wxSOLID ),
	   mDragStared( FALSE ),
	   mpOrigPixels( NULL ),
	   mEraseStarted( FALSE )
{}

XBitmapSurface::~XBitmapSurface()
{
	if ( mpOrigPixels ) delete mpOrigPixels;
}

bool XBitmapSurface::OnMouseDown( const XPoint& pos )
{
	mRightPressed = FALSE;

	return DoMouseDown( pos );
}

bool XBitmapSurface::OnMouseUp  ( const XPoint& pos )
{
	if ( !mDragStared ) return FALSE;

	int mode = mpView->GetMode();

	if ( mode == BitmapEditorView::FILL_MODE )
	{
		int x = 0, y = 0;
		GetPixelPos( pos, x, y );

		mpData->FloodFill( x,y, mRightPressed ? mpView->GetBgColour()
											  : mpView->GetFgColour() );
	}

	OnFinishEdit();

	mDragStared  = FALSE;

	DoUpdate();

	return TRUE;
}

bool XBitmapSurface::OnMouseMove( const XPoint& pos )
{
	int mode = mpView->GetMode();

	if ( mDragStared )
	{
		int x = 0, y = 0;
		GetPixelPos( pos, x, y );

		int originX = 0, originY = 0;
		GetPixelPos( mDragOrigin, originX, originY );

		int colour = mRightPressed ? mpView->GetBgColour()
								   : mpView->GetFgColour();

		if ( x < 0 || y < 0 || x >= mpData->mCols || y >= mpData->mRows )

			return TRUE;

		if ( mode == BitmapEditorView::PENCIL_MODE )
		
			mpData->SetPixelColour( x, y, colour );
		else
		if ( mode == BitmapEditorView::LINE_MODE )
		{
			// restore image which was before line was sketched
			memcpy( mpData->mPixels, mpOrigPixels, mOrigCols * mOrigRows );

			mpData->DrawLine( originX, originY, x, y, colour );
		}
		else
		if ( mode == BitmapEditorView::RECT_MODE || 
			 mode == BitmapEditorView::ELLIPSE_MODE )
		{
			XPoint p1 = mDragOrigin, p2 = pos;

			if ( p2.x < p1.x ) { int tmp = p2.x; p2.x = p1.x; p1.x = tmp; }
			if ( p2.y < p1.y ) { int tmp = p2.y; p2.y = p1.y; p1.y = tmp; }

			int x = 0, y = 0, x1 = 0, y1 = 0;
			GetPixelPos( p1, x, y );
			GetPixelPos( p2, x1, y1 );

			// restore image which was before line was sketched
			memcpy( mpData->mPixels, mpOrigPixels, mOrigCols * mOrigRows );

			if ( mode == BitmapEditorView::RECT_MODE )

				mpData->FillRectangle( x,y, x1 - x + 1, y1 - y + 1, colour );
			else
			if ( mode == BitmapEditorView::ELLIPSE_MODE )

				mpData->DrawEllipse( x,y, x1 - x + 1, y1 - y + 1, colour );
		}
		else
		if ( mode == BitmapEditorView::ERASE_MODE )
		{
			colour = mRightPressed ? mpView->GetFgColour()
								   : mpView->GetBgColour();

			int x0 = 0, y0 = 0;
			GetPixelPos( pos, x0, y0 );

			for( int x = x0; x != x0 + 8; ++x )

				for( int y = y0; y != y0 + 8; ++y )
				
					if ( x < mpData->mCols && y < mpData->mRows )
					
						mpData->SetPixelColour( x, y, colour );
		}
		else
		if ( mode == BitmapEditorView::ERASE_MODE )
		{
		}

		DoUpdate();
	}
	else // otherwise if edit-operation is not started
	{
		if ( mode == BitmapEditorView::ERASE_MODE )
		{
			int x0 = 0, y0 = 0;
			GetPixelPos( pos, x0, y0 );

			if ( !mpOrigPixels )

				SaveCheckPoint();
			
			char bg = mpView->GetBgColour();

			for( int x = x0; x != x0 + 8; ++x )

				for( int y = y0; y != y0 + 8; ++y )
				
					if ( x < mpData->mCols && y < mpData->mRows )
					
						mpData->SetPixelColour( x, y, bg );

			DoUpdate();

			// restore original image
			memcpy( mpData->mPixels, mpOrigPixels, mOrigCols * mOrigRows );
		}
	}

	return TRUE;
}

bool XBitmapSurface::OnRightClick( const XPoint& pos )
{
	mRightPressed = TRUE;

	return DoMouseDown( pos );
}

XRect XBitmapSurface::GetBounds()
{
	if ( mpData->mCols * mpData->mPixelSize == mBounds.width &&
		 mpData->mRows * mpData->mPixelSize == mBounds.height )

		return mBounds;
	else
	{
		mBounds.width  = mpData->mCols * mpData->mPixelSize;
		mBounds.height = mpData->mRows * mpData->mPixelSize;

		if ( HasEditor() )

			GetEditor().NotifyObjectChanged();

		return mBounds;
	}
}

void XBitmapSurface::SetBounds( const XRect& bounds )
{
	mBounds = bounds;

	mBounds.width  = ( mBounds.width  / mpData->mPixelSize ) * mpData->mPixelSize;
	mBounds.height = ( mBounds.height / mpData->mPixelSize ) * mpData->mPixelSize;

	int cols = mBounds.width  / mpData->mPixelSize;
	int rows = mBounds.height / mpData->mPixelSize;

	if ( mpOrigPixels )
	{
		mpData->InitPixels( cols, rows, mpView->GetBgColour(), mpData->mPixelSize );

		for( int x = 0; x != cols; ++x )
		
			for( int y = 0; y != rows; ++y )
			{
				if ( x < mOrigCols && y < mOrigRows )

					mpData->SetPixelColour( x,y, mpOrigPixels[ x + y * mOrigCols ] );
			}
	}
}

void XBitmapSurface::Draw( wxDC& canvas )
{
	canvas.SetPen( *wxTRANSPARENT_PEN );
	canvas.SetBrush( mBgBrush );
	canvas.DrawRectangle( mBounds.x, mBounds.y, mBounds.width, mBounds.height );

	bool erase = FALSE;

	for( int col = 0; col != mpData->mCols; ++col )
	{
		int x = mBounds.x + col * mpData->mPixelSize;

		for( int row = 0; row != mpData->mRows; ++row )
		{
			int y = mBounds.y + row * mpData->mPixelSize;
			int idx = col + row * mpData->mCols;
	
			canvas.SetBrush( mpData->mBrushes[ (int)mpData->mPixels[ idx ] ] );

			canvas.DrawRectangle( x + 1, y + 1, 
								  mpData->mPixelSize - 2, mpData->mPixelSize - 2 );
		}
	}
}

bool XBitmapSurface::OnStartEdit()
{
	SaveCheckPoint();

	return TRUE;
}

bool XBitmapSurface::OnFinishEdit()
{
	BmpEditorActionData* pAction = 
		new BmpEditorActionData( mpData->mCols, mpData->mRows, mpData->mPixels,
								 mOrigCols, mOrigRows, mpOrigPixels );

	mpData->AddAction( pAction );

	mpOrigPixels = NULL; // from now on, owned by action object

	return TRUE;
}

/*** protected methods ***/

void XBitmapSurface::GetPixelPos( const XPoint& canvasPos, int& x, int& y )
{
	x = ( canvasPos.x - mBounds.x ) / mpData->mPixelSize;
	y = ( canvasPos.y - mBounds.y ) / mpData->mPixelSize;
}

void XBitmapSurface::DoUpdate()
{
	InvalidateRect( GetBounds() );
	RefreshNow();
}

bool XBitmapSurface::DoMouseDown( const XPoint& pos )
{
	if ( mDragStared ) return FALSE;

	mDragStared = TRUE;
	mDragOrigin = pos;

	SaveCheckPoint();
	
	OnMouseMove( pos ); // simulate first mouse movement (some modes need this)

	return TRUE;
}

void XBitmapSurface::SaveCheckPoint()
{
	if ( mpOrigPixels ) delete [] mpOrigPixels;

	int cnt = mpData->mRows * mpData->mCols;
	mpOrigPixels = new char[ cnt ];

	memcpy( mpOrigPixels, mpData->mPixels, cnt );

	mOrigRows = mpData->mRows;
	mOrigCols = mpData->mCols;
}

