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

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

/***** Implementation for class XSelectionEditor *****/

XSelectionEditor::XSelectionEditor( const wxColour& color )
	: mDragStarted(0),
	  mSelectionStarted(0),

	  mSelectionFinished(0),
	  mpInitialFocus( NULL ),
	  mpSurroundingObj( NULL ),
	  mStickyMode(0),
	  mRMBScrollOn(1),

	  mScrollStarted(0),
	  mPen( color, 1, wxDOT )
{}

XSelectionEditor::~XSelectionEditor()
{}

void XSelectionEditor::SetStickyMode( bool stickyModeOn )
{
	mStickyMode = stickyModeOn;
}

XCollection& XSelectionEditor::GetCont()
{
	return *( (XCollection*)&GetParent() );
}

void XSelectionEditor::SaveOriginalShapes()
{
	WX_CLEAR_ARRAY( mShapes )

	XObjListT& lst = GetCont().GetSelection();

	StartBatch();

	XRect objectsRect; 
	for( size_t i = 0; i != lst.Count(); ++i )
	{
		mShapes.Add( new XPoly() );

		lst[i]->GetShape( *mShapes[ mShapes.Count()-1 ] );

		objectsRect.Accumulate( lst[i]->GetInclusiveBounds() );

		// now actually transaction starts
		lst[i]->OnStartEdit();
	}

	FinishBatch();

	mBounds         = objectsRect;
	mBoundsOriginal = mBounds;
}

void XSelectionEditor::ResetSelector()
{
	WX_CLEAR_ARRAY( mShapes );

	GetCont().InvalidateRect( GetBounds() );
}

void XSelectionEditor::FinishTransaction()
{
	XObjListT lst = GetCont().GetSelection();
	for( size_t i = 0; i != lst.Count(); ++i )

		lst[i]->OnFinishEdit();
}

void XSelectionEditor::NotifyObjectChanged()
{
	if ( mSelectionFinished )
	{
		InvalidateRect( GetBounds() );

		ResetSelector();
		//FinishTransaction();

		InvalidateRect( GetBounds() );

		mSelectionFinished = FALSE;
	}

	if ( GetCont().HasMultipleSelection() )
	{
		mSelectionFinished = TRUE; // continue operations on multiple selction

		InvalidateRect( GetBounds() );

		XObjListT& lst = GetCont().GetSelection();

		mBounds = XRect(); 

		for( size_t i = 0; i != lst.Count(); ++i )
		
			mBounds.Accumulate( lst[i]->GetInclusiveBounds() );

		InvalidateRect( GetBounds() );

		RefreshNow();
	}
}

bool XSelectionEditor::OnMouseDown( const XPoint& pos )
{
	if ( mScrollStarted )

		return TRUE;

	XObject* pObj = GetCont().FindObjectAt( pos, 
//		                                    FALSE, // not ignore mouse focus
											FALSE,
											TRUE  // ignore background
										  );

	if ( mSelectionFinished )
	{
		wxMouseEvent evt;
		GetMouseEventInfo( evt );

		if ( evt.ControlDown() || evt.ShiftDown() && pObj )
		{
			GetCont().SetFocus( pObj, FALSE );

			XObjListT sel = GetCont().GetSelection();

			GetCont().SetSelection( sel );

			return TRUE;
		}

		// selection was finished, after that user clicked
		// outside the rectangle of selected objects, then stop
		// everything and start over again
		if ( !pObj )
		{
			DrawTrackerRect( GetCanvas() ); // drawing twice erases tracker
			GetCont().InvalidateRect( GetBounds() );
			mSelectionFinished = FALSE;
			ResetSelector();
			FinishTransaction();

			mBounds = XRect();
			GetCont().ResetSelection();
		}

		else 
		// the same as above
		if ( !GetCont().IsSelected( pObj ) )
		{
			DrawTrackerRect( GetCanvas() ); // drawing twice erases tracker
			GetCont().InvalidateRect( GetBounds() );
			mSelectionFinished = FALSE;
			ResetSelector();
			FinishTransaction();

			mBounds = XRect();
			GetCont().ResetSelection();
		}
			
		else
		{
			int avoidCompilerWarning = 1;
			if ( avoidCompilerWarning ) // FOR NOW::
			{
				// selection finished, user clicked inside the
				// selection rectangle and starts dragging it

				SaveOriginalShapes();

				mDragOrigin = pos;

				mSelectionFinished = 0;
				mDragStarted       = 1;

				GetWindow()->CaptureMouse();

				// simulate mouse-move (updates new selection rectangle)
				OnMouseMove( pos );

				return TRUE;
			}
		}
	}

	//wxASSERT( !mSelectionStarted );
	// FOR NOW::
	if ( mSelectionStarted )

		return TRUE;

	// user clicked on some object (while selection neither started
	// nor finished), then propagate event to that object

	/* OLD VERSION:::
	if ( pObj && pObj->Contains( pos ) )

		return GetParent().OnMouseDown( pos );
	*/
	if ( pObj )
	{
		mpSurroundingObj = NULL;

		bool wasSelected = pObj->IsSelected();

		if ( pObj->HasEditor() && 
			 pObj->GetEditor().OnMouseDown( pos ) )

			 return TRUE;
		else
		if ( pObj->OnMouseDown( pos ) )

			return TRUE;
		else
			mpSurroundingObj = pObj;
	}

	// user clicked somewher in the empty place, probably trying
	// to select multiple objects, then entering "selectionStarted"
	// state

	mDragOrigin = pos;

	mSelectionStarted = 1;
	mBounds = XRect( pos, XDim(0,0) );

//	GetCont().SetFocus( NULL );

//	SetMouseFocus( &GetParent() );

	GetWindow()->CaptureMouse();
	
	return TRUE; // mouse-donw event handled
}

bool XSelectionEditor::OnMouseUp( const XPoint& pos )
{
	if ( mScrollStarted )
	{
		OnMouseMove( pos ); // simulate the last mouse movement

		ResetMouseFocus( &GetParent() );

		GetWindow()->ReleaseMouse();

		if ( mpInitialFocus )

			GetParent().SetMouseFocus( mpInitialFocus ); // restore

		mScrollStarted = 0;

		return 1;
	}

	if ( mDragStarted )
	{
		OnMouseMove( pos );

		FinishTransaction();

		// if user clicked to drag selection but,
		// actually havent dragged it anywhere, then
		// we've mistaken about his/her actions. To
		// correct it, we rest state, generate one mouse-down
		// and mouse-up event right after 

		if ( pos == mDragOrigin )
		{
			mDragStarted = 0;
			ResetSelector();
			GetCont().ResetSelection();

			GetCont().OnMouseDown( pos );

			GetCont().OnMouseUp( pos );
			return 1;
		}

		// objects dragged a while, and drapped, getting
		// back to "selectionFinished" state

		mSelectionFinished = 1;
		mDragStarted       = 0;

		GetWindow()->ReleaseMouse();

		return TRUE;
	}
	else

	if ( mSelectionStarted )
	{
		// if user haven't selected anything or selected
		// only one object, then clear
		// selection rectangle and reset state

		if ( GetCont().GetSelection().Count() == 0 ||
			 GetCont().GetSelection().Count() == 1 
		   )
		{
			GetCont().InvalidateRect( GetBounds() );
			mSelectionStarted = 0;
			GetCont().RefreshNow();

			ResetMouseFocus(&GetParent());

			GetWindow()->ReleaseMouse();

			return TRUE;
		}

		OnMouseMove( pos );

		// shrink selection rectangle down to fit
		// the selected object exactly

		GetCont().InvalidateRect( GetBounds() );

		/* relaclulate the selection rectangel to fit around
		 * the newlly selected objects 
	     */

		XObjListT& lst = GetCont().GetSelection();
		XRect objectsRect; 

		for( size_t i = 0; i != lst.Count(); ++i )
		
			objectsRect.Accumulate( lst[i]->GetInclusiveBounds() );

		mBounds         = objectsRect;
		mBoundsOriginal = mBounds;
		
		GetCont().InvalidateRect( GetBounds() );
		GetCont().RefreshNow();

		mSelectionStarted = 0;
		mSelectionFinished = 1;

		ResetMouseFocus(&GetParent());

		GetWindow()->ReleaseMouse();

		return TRUE;
	}

	// in case someone will make a refresh later
	GetCont().InvalidateRect( GetBounds() );

	GetCont().OnMouseUp( pos );
	return TRUE;
}

bool XSelectionEditor::OnMouseMove( const XPoint& pos )
{
	wxDC& canvas = GetCanvas();

	int cw, ch;
	GetWindow()->GetClientSize( &cw, &ch );

	XDim canvasDim( cw, ch );

	XPoint scrPos = pos.Add( _gCurCanvasOrigin );

	if ( mScrollStarted )
	{
		XPoint delta = scrPos.Sub( mDragOrigin );

		//delta.x *=2;
		//delta.y *=2;

		XPoint newOrigin = mCanvasOrigin.Add( delta );

		_gCurCanvasOrigin = newOrigin;

		canvas.SetDeviceOrigin( newOrigin.x, newOrigin.y );

		InvalidateRect( XRect( XPoint(-newOrigin.x,
			                          -newOrigin.y ) , canvasDim ) );

		RefreshNow();

		return TRUE;
	}

	int avoidCompilerWarning = 1;
	if ( avoidCompilerWarning ) // TBD:: if( mousefocus ) ...
	{
		// automatically scroll the canvas when the
		// mouse pointer leaves the bounds of canvas
		// and the mouse is focused

		XRect bounds = XRect( XPoint(0,0), canvasDim );

		if ( !bounds.Contains( scrPos ) )
		{
			XPoint delta;
			
			// determine X scroll units

			if ( scrPos.x < 0 )

				delta.x = -scrPos.x;
			else
			if ( scrPos.x > canvasDim.width )
				
				delta.x = canvasDim.width - scrPos.x;

			// determine Y scroll units

			if ( scrPos.y < 0 )

				delta.y = -scrPos.y;
			else
			if ( scrPos.y > canvasDim.height )
				
				delta.y = canvasDim.height - scrPos.y;

			delta.x *=1;
			delta.y *=1;

			XPoint newOrigin = _gCurCanvasOrigin.Add( delta );

			_gCurCanvasOrigin = newOrigin;

			canvas.SetDeviceOrigin( newOrigin.x, newOrigin.y );

			InvalidateRect( XRect( XPoint(-newOrigin.x,
				                          -newOrigin.y ) , canvasDim ) );

			RefreshNow();
		}
	}

	if ( mSelectionStarted )
	{
		GetCont().InvalidateRect( GetBounds() );

		mBounds = XRect( pos, mDragOrigin );
		mBounds.Normalize();

		GetCont().InvalidateRect( GetBounds() );

		XObjListT lst;

		GetCont().FindObjectsInRect( mBounds, lst, 1 );

		if ( !mStickyMode )
		{
			// if not in sticky mode, then filter out objects
			// which are not entirely covered by current
			// selection rectangle

			XObjListT list1;
			
			for( size_t i = 0; i != lst.Count(); ++i )

				if ( mBounds.Contains( lst[i]->GetBounds() ) &&
					 lst[i]->IsSelectable()
				   )
				{
					XObject* pSurObj = lst[i]->GetSurroundingObject();
					
					if ( mpSurroundingObj == pSurObj )

						list1.Add( lst[i] );
				}

			// allow the derived classes to do their specific filtering
			// of the selection
			FilterSelection( list1 );

			GetCont().SetSelection( list1, FALSE );	

			if ( GetCont().GetFocus() == NULL && list1.Count() )

				GetCont().SetFocus( list1[0] );
		}
		else
		{
			// allow the derived classes to do their specific filtering
			// of the selection
			FilterSelection( lst );

			GetParent().SetSelection( lst, FALSE );
		}

		RefreshNow();

		return TRUE;
	}
	else
	if ( mDragStarted )
	{
		XPoint delta = pos.Sub( mDragOrigin );

		XPoly shape;

		XObjListT& lst = GetCont().GetSelection();

		GetCont().InvalidateGroup( lst );
		GetCont().InvalidateRect( mBounds );

		for( size_t i = 0; i != lst.Count(); ++i )
		{
			shape = *(mShapes)[i];

			shape.Translate( delta );

			lst[i]->SetShape( shape );

			if ( lst[i]->HasEditor() )

				lst[i]->GetEditor().SetShape( shape );
		}

		// send notification to the derived classes
		OnSelectionDragged( lst );

		mBounds.x = mBoundsOriginal.x + delta.x;
		mBounds.y = mBoundsOriginal.y + delta.y;

		GetCont().InvalidateGroup( lst );
		GetCont().InvalidateRect( mBounds );

		RefreshNow();

		return TRUE;
	}
	else 
	{
		GetParent().OnMouseMove( pos );
		return TRUE;
	}
}

bool XSelectionEditor::OnRightClick( const XPoint& pos )
{
	mScrollStarted = 1;

	wxDC& canvas = GetCanvas();

	mDragOrigin = pos.Add( _gCurCanvasOrigin );

	mCanvasOrigin = _gCurCanvasOrigin;

	// FIMXE:: clean this up!

	mpInitialFocus = GetMouseFocus();

	if ( mpInitialFocus ) ResetMouseFocus( mpInitialFocus );

	SetMouseFocus( &GetParent() );

	GetWindow()->CaptureMouse();

	return TRUE;
}

void XSelectionEditor::DrawTrackerRect( wxDC& dc )
{
	if ( mBounds.width == 0 ||
		 mBounds.height == 0 ) return;

	dc.SetPen( mPen );
	dc.SetLogicalFunction( wxINVERT );

	int u = 0;

	dc.DrawLine( mBounds.x, mBounds.y-u,
			         mBounds.x + mBounds.width , mBounds.y-u );

	dc.DrawLine( mBounds.x + mBounds.width , mBounds.y,
			         mBounds.x + mBounds.width , mBounds.y + mBounds.height );

	dc.DrawLine( mBounds.x + mBounds.width , mBounds.y + mBounds.height,
			         mBounds.x , mBounds.y + mBounds.height );

	dc.DrawLine( mBounds.x, mBounds.y + mBounds.height, 
			         mBounds.x , mBounds.y );

	dc.SetLogicalFunction( wxCOPY );
}

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

	if ( mSelectionStarted  || 
		 mSelectionFinished ||
		 mDragStarted
	   )
	{
		if ( GetCont().GetSelection().Count() == 0 && mSelectionFinished )
		{
			mDragStarted = 0;
			mSelectionStarted = 0;
			mSelectionFinished = 0;
			return;
		}

		DrawTrackerRect( canvas );
	}
}

XRect XSelectionEditor::GetBounds()
{
	if ( mSelectionStarted  || 
		 mSelectionFinished ||
		 mDragStarted
	   )
	{
		// FIMXE:: +/- 1pix problmes

		XRect bigger = mBounds;
		int u = 2;
		bigger.x -=u;
		bigger.y -=u;
		bigger.width +=u*2;
		bigger.height +=u*2;
	
		return bigger;
	}
	else
		return XRect();	// empty rect
}
