
///////////////////////////////////////////////
//											 //
// All files in X-framework are Copyright    //
// of Aleksandras Gluchovas 				 //
// 											 //
// e-mail: alex@soften.ktu.lt                //
//											 //
///////////////////////////////////////////////

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

/***** Implementation for class XRectEditor *****/

XRectEditor::XRectEditor( const wxColour& lineColor,
						  const wxColour& handleColor,
						  bool  useInvertedHints,
						  bool  useCursors ,
						  const wxColour& secondaryHandleColor
						  )

	: mMinDim( 1, 1 ),
	  mWidth ( 12 ),
	  mPen   ( lineColor, 1, wxSOLID ),
	  mBrush ( handleColor,  wxSOLID ),
	  mSecondaryBrush( secondaryHandleColor, wxSOLID ),

	  mUseInvertedHints( useInvertedHints ),
	  mUseCursors( useCursors ),
	  mCanMove( TRUE ),
   
	  mDragStarted ( 0 ),
	  mStartFired( 0 ),
	  mActiveHandle( NO_HANDLE ),
	  mHandlesVisible( 1 ),

	  mCursorIsSet( FALSE )



{}


void XRectEditor::EnableMove( bool canMove )
{
	mCanMove = canMove;
}

void XRectEditor::SubjectChanged(bool isInitiator)
{
	if ( !IsActive() ) return;

	XPoly poly;
	GetParent().GetShape( poly );
	SetShape( poly );

	mRect = mInitialRect;
}

void XRectEditor::SetMinimalDim( const XDim& minDim )
{
	mMinDim = minDim;
}

void XRectEditor::SetHandleWidth( PosT width )
{
	mWidth = width;
}

bool XRectEditor::IsSelected()
{
	return GetParent().
				IsSelected( &GetParent() );
}

XCollection& XRectEditor::GetScene()
{
	return *( (XCollection*)&( GetParent().GetParent() ) );
}

bool XRectEditor::IsInMultipleSelection()
{
	return ( GetScene().GetSelection().Count() > 1 );
}

void XRectEditor::SetShape( XPoly& shape )
{
	//mInitialRect  = shape.GetBoundingRect();

	//mRect = mInitialRect;

	mRect  = shape.GetBoundingRect();
}

void XRectEditor::GetShape( XPoly& shape )
{
	shape.CreateFromRect( mRect );
}

bool XRectEditor::OnMouseDown ( const XPoint& pos )
{
	// DBG::
	wxASSERT( !mDragStarted );

	XPoly poly;
	GetParent().GetShape( poly );
	SetShape( poly );

	mInitialRect = poly.GetBoundingRect();
	mRect = mInitialRect;

	mActiveHandle = NO_HANDLE;

	for( size_t i = TOP_LEFT; i != LEFT_BOTTOM + 1; ++i )

		if ( HandleRect( i ).Contains( pos ) )
		{
			mActiveHandle = i;
			break;
		}

	if ( mAutoRise )
	
		GetParent().
			MoveToFront( &GetParent() );

	bool justSelected = FALSE;

	if ( !IsSelected() )
	{
		GetParent().SetFocus( &GetParent(), FALSE );
		GetParent().SetSelection( GetParent() );
		justSelected = TRUE;
	}

	// first let the object itself try to handle the 
	// mouse click

	if ( mActiveHandle == NO_HANDLE )
	{
		if ( GetParent().OnMouseDown( pos ) )

			return TRUE;
		else
		if ( !mCanMove ) 
			return FALSE;
	}

	// now really start editing

	mStartFired = FALSE;

	mDragStarted = TRUE;
	mDragOrigin  = pos;

	if ( !mCursorIsSet )
	
		SetEdtCursor( wxCURSOR_CROSS  );

	SetMouseFocus( &GetParent() );
	GetWindow()->CaptureMouse();

	return TRUE; // left-down handled
}

bool XRectEditor::OnMouseUp   ( const XPoint& pos )
{
	if ( mDragStarted )
	{
		// simulate the last mouse movement

		OnMouseMove( pos );

		//mActiveHandle = NO_HANDLE;

		mDragStarted = 0;

		InvalidateRect( GetParent().GetInclusiveBounds() );

		if ( !mAutoRepaint )
		{
			XPoly poly;
			poly.CreateFromRect( mRect );
			GetParent().SetShape( poly );
		}

		bool finishedOk = true;

		GetWindow()->ReleaseMouse();
		ResetMouseFocus( &GetParent() );

		if ( mActiveHandle == NO_HANDLE )
		{
			if ( mStartFired )

				finishedOk = GetParent().OnFinishEdit();
		}
		else
			finishedOk = GetParent().OnFinishEdit();

		// finishedOk == FALSE may mean that editee has been
		// destroyed upon the OnFinishEdit(), and most probably
		// *this object does not exist anymore, thus we are careful
		// to exit nicely now
		 
		if ( finishedOk )
		{
			InvalidateRect( GetParent().GetInclusiveBounds() );

			RefreshNow();
		}

		return TRUE; // left-up handled
	}
	else
		return FALSE;
}

static inline void clip_to( PosT& value, PosT from, PosT till )
{
	if ( value < from ) 
		value = from;

	if ( value > till ) 
		value = till;
}

void XRectEditor::SetEdtCursor( int cursorType )
{
	GetWindow()->SetCursor( wxCursor( cursorType ) );
}

bool XRectEditor::OnMouseLeave()
{
	if ( !mDragStarted && mCursorIsSet )
	{
		SetEdtCursor( wxCURSOR_ARROW );

		mCursorIsSet = FALSE;

		return TRUE;
	}
	else 
		return FALSE;
}


bool XRectEditor::OnMouseMove ( const XPoint& pos )
{
	if ( mDragStarted == FALSE && 
		 mUseCursors           && 
		 !IsInMultipleSelection() 
	   )
	{
		bool handleHitted = FALSE;
		int  prevHandle = mActiveHandle;

		for( size_t i = TOP_LEFT; i != LEFT_BOTTOM + 1; ++i )
		{
			if ( HandleRect( i ).Contains( pos ) )
			{
				handleHitted = TRUE;

				if ( mActiveHandle != (int)i )
				{
					switch ( i )
					{
						case TOP_LEFT     : { SetEdtCursor( wxCURSOR_SIZENWSE );break; }
						case RIGHT_BOTTOM : { SetEdtCursor( wxCURSOR_SIZENWSE );break; }
						case TOP_RIGHT    : { SetEdtCursor( wxCURSOR_SIZENESW );break; }
						case LEFT_BOTTOM  : { SetEdtCursor( wxCURSOR_SIZENESW );break; }

						default: break;
					}

					mActiveHandle = i;
					mCursorIsSet  = TRUE;
				}

				handleHitted  = TRUE;

				break;
			}
		}

		if ( mCursorIsSet == FALSE || 
			 ( !handleHitted && mActiveHandle != NO_HANDLE && mCursorIsSet == TRUE )
		   )
		{
			mActiveHandle = NO_HANDLE;


			if ( mCanMove && 
				 GetEditee().GetBounds().Contains( pos ) )

				SetEdtCursor( wxCURSOR_CROSS  );
			else
			{
				mCursorIsSet = FALSE;

				SetEdtCursor( wxCURSOR_ARROW );

				return FALSE;
			}
		}

		return GetParent().OnMouseMove( pos );
	}
	else
	if ( mDragStarted == FALSE ) 

		return FALSE;

	//// otherwise dragging is in progress /////

	if ( !mStartFired && 
		 HasMovedEnough( pos, mDragOrigin ) )
	{
		mStartFired = TRUE;
		GetParent().OnStartEdit();
	}

	XPoint delta = pos.Sub( mDragOrigin );

	InvalidateRect( GetParent().GetInclusiveBounds() );

	XObject* pSurObj = this->GetParent().GetSurroundingObject();
	XRect surBounds;

	if ( pSurObj )
	
		surBounds = pSurObj->GetLimitingBounds();

	const int INFINITY = 32768;

	if ( mActiveHandle == NO_HANDLE )
	{
		// the object is being moved

		mRect.x = mInitialRect.x + delta.x;
		mRect.y = mInitialRect.y + delta.y;

		mRect.width  = mInitialRect.width;
		mRect.height = mInitialRect.height;

		if ( pSurObj )
		{
			if ( mRect.x + mRect.width > surBounds.x + surBounds.width )
				
				mRect.x = surBounds.x + surBounds.width - mRect.width;

			if ( mRect.y + mRect.height > surBounds.y + surBounds.height )
				
				mRect.y = surBounds.y + surBounds.height - mRect.height;

			if ( mRect.x < surBounds.x )
				
				mRect.x = surBounds.x;

			if ( mRect.y < surBounds.y )
				
				mRect.y = surBounds.y;
		}
	}
	else
	{
		// one of the corners of the object is being dragged

		// Microsoft's rect-coordinates are best suited
		// for the case of corner-clipping
		
		int left   = mInitialRect.x;
		int top    = mInitialRect.y;
		int right  = mInitialRect.x + mInitialRect.width;
		int bottom = mInitialRect.y + mInitialRect.height;

		switch ( mActiveHandle )
		{
			case TOP_LEFT     : 
			{
				left += delta.x;
				top  += delta.y;

				clip_to( left, -INFINITY, mInitialRect.x + mInitialRect.width  - mMinDim.width  );
				clip_to( top,  -INFINITY, mInitialRect.y + mInitialRect.height - mMinDim.height );

				if ( pSurObj )
				{
					clip_to( left, surBounds.x, INFINITY );
					clip_to( top,  surBounds.y, INFINITY );
				}

				break;
			}

			case LEFT_BOTTOM  : 
			{
				left   += delta.x;
				bottom += delta.y;

				clip_to( left,    -INFINITY, mInitialRect.x + mInitialRect.width  - mMinDim.width  );
				clip_to( bottom,  mInitialRect.y + mMinDim.height, INFINITY );

				if ( pSurObj )
				{
					clip_to( left,   surBounds.x, INFINITY );
					clip_to( bottom, -INFINITY, surBounds.y + surBounds.height );
				}

				break;
			}

			case TOP_RIGHT    : 
			{
				right += delta.x;
				top   += delta.y;

				clip_to( right, mInitialRect.x + mMinDim.width, INFINITY );
				clip_to( top,   -INFINITY, mInitialRect.y + mInitialRect.height - mMinDim.height );

				if ( pSurObj )
				{
					clip_to( right, -INFINITY, surBounds.x + surBounds.width );
					clip_to( top,   surBounds.y, INFINITY );
				}

				break;
			}

			case RIGHT_BOTTOM : 
			{
				right  += delta.x;
				bottom += delta.y;

				clip_to( right,  mInitialRect.x + mMinDim.width, INFINITY );
				clip_to( bottom, mInitialRect.y + mMinDim.height, INFINITY );

				if ( pSurObj )
				{
					clip_to( right,  -INFINITY, surBounds.x + surBounds.width );
					clip_to( bottom, -INFINITY, surBounds.y + surBounds.height );
				}

				break;
			}

					 // DBG:: bogus handle type
			default : 
				int avoidCompilerWarning = 0;
				wxASSERT(avoidCompilerWarning);
				break;
		}

		mRect.x = left;
		mRect.y = top;
		mRect.width  = right - left;
		mRect.height = bottom - top;
	}

	if ( !GetEditArea().IsEmpty() )
	{
		XRect inter = GetEditArea().InsersectionWith( mRect );

		if ( inter.IsEmpty() )
		{
			mRect.x = GetEditArea().x;
			mRect.y = GetEditArea().y;

			inter = GetEditArea().InsersectionWith( mRect );
		}

		mRect = inter;
	}

	if ( mAutoRepaint )
	{
		XPoly poly;
		poly.CreateFromRect( mRect );
		GetParent().SetShape( poly );
	}

	// fire off notifications to the derived classes
	OnDragging();

	// and to the edited object
	GetParent().OnEditorDragged();

	InvalidateRect( GetParent().GetInclusiveBounds() );

	mRect = GetParent().GetBounds(); // react how the editee shapes itself

	RefreshNow();

	return TRUE; // mouse motion handled
}

XRect XRectEditor::HandleRect( int handleType )
{

	switch ( handleType )
	{
		case TOP_LEFT     : return XRect( mRect.x - mWidth, mRect.y - mWidth,
								          mWidth, mWidth );

		case RIGHT_BOTTOM : return XRect( mRect.x + mRect.width, mRect.y + mRect.height,
								          mWidth, mWidth );

		case TOP_RIGHT    : return XRect( mRect.x + mRect.width, mRect.y - mWidth,
								          mWidth, mWidth );

		case LEFT_BOTTOM  : return XRect( mRect.x - mWidth, mRect.y + mRect.height,
								          mWidth, mWidth );

		default : 
			int avoidCompilerWarning = 0;
			wxASSERT(avoidCompilerWarning); // DBG:: bogus handle
			break;
	}

	return XRect();
}

#define TOLLERANCE 2

void XRectEditor::Draw( wxDC& canvas )
{
	GetParent().Draw( canvas );

	// handles are visible when item is in focus

	if ( !IsSelected() )

		return;

	if ( mUseInvertedHints )

		canvas.SetLogicalFunction( wxXOR );

	if ( GetScene().GetFocus() == &GetParent() )
   
		canvas.SetBrush( mBrush );
	else
		canvas.SetBrush( mSecondaryBrush );

	canvas.SetPen( mPen );

	// FOR NOW::
#if 0
	canvas.DrawLine( mRect.x,  mRect.y,
			         mRect.x + mRect.width , mRect.y );

	canvas.DrawLine( mRect.x + mRect.width , mRect.y,
			         mRect.x + mRect.width , mRect.y + mRect.height );

	canvas.DrawLine( mRect.x + mRect.width , mRect.y + mRect.height,
			         mRect.x,  mRect.y + mRect.height );

	canvas.DrawLine( mRect.x, mRect.y + mRect.height, 
			         mRect.x, mRect.y );
#endif

	canvas.SetPen( *wxTRANSPARENT_PEN );

	if ( mHandlesVisible )
	
		for( int i = TOP_LEFT; i <= LEFT_BOTTOM; ++i )
		{
			XRect r ( HandleRect(i ) );

			r.x += TOLLERANCE;
			r.y += TOLLERANCE;
			r.width  -= TOLLERANCE*2;
			r.height -= TOLLERANCE*2;

			canvas.DrawRectangle( r.x, r.y, r.width, r.height );
		}

	if ( mUseInvertedHints )

		canvas.SetLogicalFunction( wxCOPY );

}

XRect XRectEditor::GetBounds()
{
	if ( !IsSelected() 
		//&& GetParent().GetFocus() == &GetParent() 
		)

		return GetParent().GetBounds();

	if ( mRect.IsEmpty() )
	{
		XPoly shape;

		GetParent().GetShape( shape );

		mRect = shape.GetBoundingRect();
	}

	XRect rect = mRect;

	// append area occupied by handles
			    
	rect.x      -= mWidth + TOLLERANCE;
	rect.y      -= mWidth + TOLLERANCE;
	rect.width  += mWidth + mWidth + TOLLERANCE + TOLLERANCE;
	rect.height += mWidth + mWidth + TOLLERANCE + TOLLERANCE;

	return rect;
}

XObject* XRectEditor::CloneCopy( bool identical )
{
	XRectEditor* pCopy = new XRectEditor( mPen.GetColour(),
		                                  mBrush.GetColour(),
						                  mUseInvertedHints,
										  mUseCursors,
										  mSecondaryBrush.GetColour()
									    );

	pCopy->EnableMove( mCanMove );
	pCopy->SetAutoRepaint( mAutoRepaint );
	pCopy->SetAutoRise( mAutoRise );
	pCopy->SetMinimalDim( mMinDim );

	return pCopy;
}

bool XRectEditor::Contains( const XPoint& point )
{
	// FIXME:: this may be too CPU consuming for tests
	//		   during plain OnMouseMove()

	if ( IsSelected() )
	{
		if ( GetParent().Contains( point ) )

			return TRUE;

		for( size_t i = TOP_LEFT; i != LEFT_BOTTOM + 1; ++i )

			if ( HandleRect( i ).Contains( point ) )
			{
				return TRUE;
			}

		return FALSE;
	}
	
	return GetParent().Contains( point );
}

/*** protected methods ***/

bool XRectEditor::HasMovedEnough( const XPoint& pos1, const XPoint& pos2 )
{
	return abs( pos1.x - pos2.x ) >= 3 || 
		   abs( pos1.y - pos2.y ) >= 3;
}
