/* ==========================================================================
	File :			RRECRuler.cpp

	Class :			CRRECRuler

	Author :		Johan Rosengren, Abstrakt Mekanik AB
					Iain Clarke

	Date :			2004-04-19

	Purpose :		This class encapsulates a ruler that can be used with 
					"CRulerRichEditCtrl". The class is derived from "CWnd", and 
					draws a ruler. 

	Description :	A plain "CWnd"-derived class. The mouse messages 
					"WM_LBUTTONDOWN", "WM_MOUSEMOVE" and "WM_LBUTTONUP" are handled, 
					and a registered message is sent to the control parent.	

	Usage :			Only tested with "CRulerRichEditCtrl". Add a member to the 
					parent class, and create it with "Create". Handle the 
					registered message "urm_RULERACTION" in the parent class. 
					The parent class is also expected to handle 
					"urm_GETSCROLLPOS", which should return the current 
					horisontal scrollbar position.

   ========================================================================*/

#include "stdafx.h"
#include "StdGrfx.h"
#include "RRECRuler.h"
#include "ids.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

CRRECRuler::CRRECRuler()
/* ============================================================
	Function :		CRRECRuler::CRRECRuler
	Description :	constructor
	Access :		Public
					
	Return :		void
	Parameters :	none

	Usage :			

   ============================================================*/
{

	m_physicalInch = 0;
	m_mode = MODE_METRIC;
	m_margin = 0;

}

CRRECRuler::~CRRECRuler()
/* ============================================================
	Function :		CRRECRuler::~CRRECRuler
	Description :	destructor
	Access :		Public
					
	Return :		void
	Parameters :	none

	Usage :			

   ============================================================*/
{
}

BEGIN_MESSAGE_MAP(CRRECRuler, CWnd)
	ON_WM_PAINT()
	ON_WM_ERASEBKGND()
	ON_WM_LBUTTONDOWN()
	ON_WM_LBUTTONUP()
	ON_WM_MOUSEMOVE()
END_MESSAGE_MAP()


BOOL CRRECRuler::Create( const CRect& rect, CWnd* parent, UINT id )
/* ============================================================
	Function :		CRRECRuler::Create
	Description :	Creates the ruler control
	Access :		Public
					
	Return :		BOOL				-	Result, "TRUE" if ok.
	Parameters :	const CRect& rect	-	Coordinate rect
					CWnd* parent		-	Parent
					UINT id				-	Child window id.
					
	Usage :			Call to create the ruler control

   ============================================================*/
{
	BOOL result = CWnd::Create( NULL, NULL, WS_CHILD | WS_VISIBLE, rect, parent, id );
	if( result )
	{
		CClientDC dc( this );
		m_physicalInch = dc.GetDeviceCaps( LOGPIXELSX );
	}

	return result;

}

void CRRECRuler::OnPaint()
/* ============================================================
	Function :		CRRECRuler::OnPaint
	Description :	Handles the "WM_PAINT" message from Windows.
					Draws the ruler to a memory "CDC" that is 
					blitted to the screen (double buffering)
	Access :		Protected
					
	Return :		void
	Parameters :	none

	Usage :			Called from MFC

   ============================================================*/
{
	CPaintDC mainDC(this);

	// Set up data
	int pos = (int)GetParent()->SendMessage( urm_GETSCROLLPOS );

	CRect rect;
	GetClientRect( rect );

	// Create off-screen canvas
	CDC dc;
	CBitmap bmp;
	bmp.CreateCompatibleBitmap( &mainDC, rect.Width(), rect.Height() );
	dc.CreateCompatibleDC( &mainDC );
	CBitmap* oldbmp = dc.SelectObject( &bmp );

	// Set up canvas
	dc.FillSolidRect( rect, RGB( 255, 255, 255 ) );
	dc.SelectStockObject( BLACK_PEN );
	dc.SelectStockObject( DEFAULT_GUI_FONT );
	dc.SetBkColor( RGB( 255, 255, 255 ) );

	// Set up data for the inner ruler window
	CRect winRect( rect );
	winRect.top += 3;
	winRect.bottom -= 5;
	winRect.right -= 3;
	winRect.left += m_margin - 2;

	int midpoint = winRect.top + ( winRect.Height() / 2 );
	int leftmarg = winRect.left + 2 - pos;
	int width = winRect.Height();
	int t;

	// Print the values in the ruler scale
	if( m_mode == MODE_INCH )
	{
		int inch4 = ( int ) ( ( double ) m_physicalInch / 4.0 +.5 );
		int inch8 = ( int ) ( ( double ) m_physicalInch / 8.0 +.5 );

		// Drawing scale markers
		for( t = ( leftmarg + ( int ) ( inch8 +.5 ) ) ; t < rect.right - m_margin ; t += ( int ) ( inch8 + .5 ) )
		{
			dc.MoveTo( t, midpoint - 1 );
			dc.LineTo( t, midpoint + 1 );
		}

		for( t = leftmarg + inch4 ; t < rect.right - m_margin ; t += inch4 )
		{
			dc.MoveTo( t, midpoint - 3 );
			dc.LineTo( t, midpoint + 3 );
		}

		CRect rectInch;
		CString counter;
		int count = 1;

		// Drawing numbers
		for( t = leftmarg + m_physicalInch ; t < rect.right - m_margin ; t += m_physicalInch )
		{

			rectInch.SetRect( t - width / 2, winRect.top + 2, t + width / 2, winRect.bottom - 2 );
			counter.Format( _T( "%i" ), count );
			dc.DrawText( counter, rectInch, DT_SINGLELINE | DT_CENTER | DT_VCENTER );
			count++;

		}
	}
	else
	{
		int cm = ( int ) ( ( double ) m_physicalInch / 2.54 + .5 );
		int cm2 = ( int ) ( ( double ) cm / 2.0 );

		// Drawing scale markers
		for( t = leftmarg + cm2 ; t < rect.right - m_margin ; t += cm2 )
		{
			dc.MoveTo( t, midpoint - 1 );
			dc.LineTo( t, midpoint + 2 );
		}

		CRect rectNum;
		CString counter;
		int count = 1;

		// Drawing numbers
		for( t = leftmarg + cm ; t < rect.right - m_margin ; t += cm )
		{
			rectNum.SetRect( t - width / 2, winRect.top + 2, t + width / 2, winRect.bottom - 2 );
			counter.Format( _T( "%i" ), count );
			dc.DrawText( counter, rectNum, DT_SINGLELINE | DT_CENTER | DT_VCENTER );
			count++;

		}
	}

	// Mask the surroundings
	CRect left( rect );
	left.right = winRect.left;
	CRect right( rect );
	right.left = rect.right - 3;
	CRect top( rect );
	top.bottom = winRect.top;
	CRect bottom( rect );
	bottom.top = winRect.bottom;

	dc.FillRect( left, CStdGrfx::dialogBrush() );
	dc.FillRect( right, CStdGrfx::dialogBrush() );
	dc.FillRect( top, CStdGrfx::dialogBrush() );
	dc.FillRect( bottom, CStdGrfx::dialogBrush() );

	// Frame the inside
	CStdGrfx::drawdoublesunken3dFrame( &dc, winRect );

	// Draw tab markers
	int max = (int)m_tabs.GetSize();
	for( t = 0 ; t < max ; t++ )
	{

		int x = ( leftmarg + m_tabs[ t ] - 2 );
		if( x > winRect.left && x + 3 < winRect.right )
		{
			dc.MoveTo( x, midpoint + 5 );
			dc.LineTo( x + 6, midpoint + 5 );
			dc.MoveTo( x, midpoint + 6 );
			dc.LineTo( x + 6, midpoint + 6 );

			dc.MoveTo( x + 2, midpoint + 7 );
			dc.LineTo( x + 2, midpoint + 10 );
			dc.MoveTo( x + 3, midpoint + 7 );
			dc.LineTo( x + 3, midpoint + 10 );
		}
	}

	// Frame the outside
	dc.SelectObject( CStdGrfx::highlightPen() );
	dc.MoveTo( rect.left + 1, rect.top );
	dc.LineTo( rect.right - 1, rect.top );

	// Restore...
	dc.SelectStockObject( BLACK_PEN );

	//... and out to screen
	mainDC.BitBlt( 0, 0, rect.Width(), rect.Height(), &dc, 0, 0, SRCCOPY );
	dc.SelectObject( oldbmp );
}

BOOL CRRECRuler::OnEraseBkgnd( CDC* /*pDC*/ ) 
/* ============================================================
	Function :		CRRECRuler::OnEraseBkgnd
	Description :	Returns "TRUE" to avoid flicker.
	Access :		Protected
					
	Return :		BOOL		-	Always "TRUE"
	Parameters :	CDC* pDC	-	Not used
					
	Usage :			Called from MFC. 

   ============================================================*/
{
	
	return TRUE;

}

void CRRECRuler::SetMode( int mode )
/* ============================================================
	Function :		CRRECRuler::SetMode
	Description :	Sets the internal mode, that is, if the 
					ruler should display inches or centimeters.
	Access :		Public
					
	Return :		void
	Parameters :	int mode	-	Mode to use, "MODE_INCH" or 
									"MODE_METRIC" (default)
					
	Usage :			Call to change the mode.

   ============================================================*/
{

	m_mode = mode;

	if( m_hWnd )
	{

		RedrawWindow();

	}

}

void CRRECRuler::SetMargin( int margin )
/* ============================================================
	Function :		CRRECRuler::SetMargin
	Description :	Sets the margin, in pixels, of the ruler.
	Access :		Public
					
	Return :		void
	Parameters :	int margin	-	The margin to set
					
	Usage :			Call to set the margin of the ruler scale.

   ============================================================*/
{

	m_margin = margin;

	if( m_hWnd )
	{

		RedrawWindow();

	}

}

int CRRECRuler::GetMode() const
/* ============================================================
	Function :		CRRECRuler::GetMode
	Description :	Gets the mode, that is, either "MODE_INCH" or 
					"MODE_METRIC", that is used to draw the ruler.
	Access :		Public
					
	Return :		int		-	The mode, either "MODE_INCH" or 
								"MODE_METRIC"
	Parameters :	none

	Usage :			Call to get the current mode.

   ============================================================*/
{

	return m_mode;

}

void CRRECRuler::SetTabStops( const CDWordArray& arr )
/* ============================================================
	Function :		CRRECRuler::SetTabStops
	Description :	Sets the tab stops in the internal tab stop 
					array, in device pixels
	Access :		Public
					
	Return :		void
	Parameters :	const CDWordArray& arr	-	The array to 
												copy from
					
	Usage :			Call to set the tab stops of the ruler.

   ============================================================*/
{

	m_tabs.RemoveAll();
	int max = (int)arr.GetSize();
	for (int t = 0 ; t < max ; t++)
	{
		m_tabs.Add(arr[ t ]);
	}

}

void CRRECRuler::OnLButtonDown(UINT nFlags, CPoint point)
/* ============================================================
	Function :		CRRECRuler::OnLButtonDown
	Description :	Handles the "WM_LBUTTONDOWN" message. We send 
					a message with the current cursor position 
					to the parent control.
	Access :		Protected
					
	Return :		void
	Parameters :	UINT nFlags		-	Not used
					CPoint point	-	Cursor position
					
	Usage :			Called from MFC

   ============================================================*/
{

	SetCapture();
	GetParent()->SendMessage( urm_RULERACTION, DOWN, ( LPARAM ) &point );
	CWnd::OnLButtonDown( nFlags,  point );

}

void CRRECRuler::OnLButtonUp(UINT nFlags, CPoint point)
/* ============================================================
	Function :		CRRECRuler::OnLButtonUp
	Description :	Handles the "WM_LBUTTONUP" message. We send 
					a message with the current cursor position 
					to the parent control.
	Access :		Protected
					
	Return :		void
	Parameters :	UINT nFlags		-	Not used
					CPoint point	-	Cursor position
					
	Usage :			Called from MFC

   ============================================================*/
{

	ReleaseCapture();
	GetParent()->SendMessage( urm_RULERACTION, UP, ( LPARAM ) &point );
	CWnd::OnLButtonUp( nFlags,  point );

}

void CRRECRuler::OnMouseMove(UINT nFlags, CPoint point)
/* ============================================================
	Function :		CRRECRuler::OnMouseMove
	Description :	Handles the "WM_MOUSEMOVE" message. We send 
					a message with the current cursor position 
					to the parent control.
	Access :		Protected
					
	Return :		void
	Parameters :	UINT nFlags		-	Not used
					CPoint point	-	Cursor position
					
	Usage :			Called from MFC

   ============================================================*/
{

	GetParent()->SendMessage( urm_RULERACTION, MOVE, ( LPARAM ) &point );
	CWnd::OnMouseMove( nFlags,  point );

}

