Click here to Skip to main content
15,893,668 members
Articles / Desktop Programming / MFC

CFlowchartEditor - linking things in CDiagramEditor

Rate me:
Please Sign up or sign in to vote.
4.94/5 (136 votes)
5 Jul 2006Public Domain8 min read 366K   33.9K   278  
A flowchart editor with linked objects, based on CDiagramEditor.
/* ==========================================================================
	Class :			CDiagramEditor

	Author :		Johan Rosengren, Abstrakt Mekanik AB

	Date :			2004-03-29

	Purpose :		"CDiagramEditor" encapsulates a basic vector editor geared 
					towards diagrams rather than graphics. It supports 
					virtual screens both smaller and larger than the window, 
					snap to grid, copy/paste, unlimited undo, zoom etc. It 
					can be added to both dialogbox- and doc/view apps due to 
					the use of a separate container for data.

					Normally, this class need only to be inherited from when 
					the drawing functionality is insufficient - many of the 
					settings can be modified through member accessors.

	Description :	The class derives from "CWnd", and handles Windows messages 
					relevant to the operations. 

	Usage :			

   ========================================================================
	Changes :		8/4  2004	SendMessageToObjects sets TRUE flag
								sending message only to selected objects.
					9/4  2004	Made some message mapping functions
								virtual to allow enhancements in 
								derived classes.
					9/4  2004	Added accessors for states to allow
								access for derived classes
					28/4 2004	Set interact mode to MODE_NONE after
								ctrl+clicking item (to not move lines)
					28/4 2004	Setting scrollbar positions to zero
								in Clear
					29/4 2004	Sending this window as a parent to the 
								property dialog. This is to be able to 
								redraw the editor properly after changes 
								to the dialog.
					30/4 2004	Changed c-style casts to static_cast
					30/4 2004	Remove ShowProperties-const-ness.
   ========================================================================
					14/5 2004	Added access to m_subMode in 
								SetInteractMode
					14/5 2004	Made OnObjectCommand virtual to allow 
								interception in derived classes.
					15/5 2004	Made GetBackgroundColor const.
					20/5 2004	Made SelectAll virtual.
					24/5 2004	Added virtual size check against the client 
								rect in HScroll and VScroll (Graham).
   ========================================================================
					24/6 2004	Corrected bug in SetZoom, where the minimum
								zoom value was not honoured (pgrohs).
					24/6 2004	Zeroing m_internalData after delete to 
								avoid crash. (pgrohs).
					26/6 2004	Added group handling (Unruled Boy).
   ========================================================================
					26/6 2004	Corrected bug in alignment handling - all 
								objects, not only selected, where modified.
								Also made functions virtual.
					29/6 2004	Added ZoomToFitScreen and 
								mousewheel-handling (John A. Johnson).
					30/6 2004	Added panning (John A. Johnson).
					3/7	 2004	Made AddObject virtual and added the virtual 
								MoveObject function. This is to allow 
								trapping of added or moved objects in the 
								editor in derived classes. Also made 
								clipboard-functions and DeleteAllSelected 
								virtual.
					5/7	2004	Made Clear virtual.
					5/7	2004	Added virtual GetCursor function.
					6/7	2004	Correction in VirtualToScreen, not deducting 
								scrollbar positions any longer (Wolfgang Busch).
					6/7	2004	Coordinate conversion functions made public.
								(Wolfgang Busch).
   ========================================================================
					12/7 2004	Added scroll wheel mode. By calling 
								SetScrollWheelMode( WHEEL_SCROLL/WHEEL_ZOOM), 
								the scroll wheel will either scroll or zoom.
   ========================================================================
					16/7 2004	Added virtual to more message handlers, 
								among them the scrollbar ones.
					16/7 2004	Added virtual to SetZoom.
					16/7 2004	Added virtual functions to set the 
								scrollbar positions to get one single point 
								where this is made.
					25/7 2004	Checking if the cursor is outside of 
								restraint when placing an item in 
								OnLButtonDown.
   ========================================================================
					3/8	 2004	Added ScrollIntoView commands
					4/8	 2004	Fixed bug with selection markers - 
								virtualization of the mouse coordinates was 
								in discord with the selection marker 
								rectangles. (Marc G)
   ========================================================================
					9/8	 2004	Added new ScrollIntoView command taking 
								object as an inparam.
					19/8 2004	Fixed bug in hit testing objects when 
								zooming in OnLButtonDown (Marc G)
   ========================================================================
					28/8 2004	Added check for non-normalized rect when 
								hit-testing in OnLButtonDown, as lines 
								have them.
   ========================================================================
					11/12 2004	Added minimum step size one pixel in 
								OnKeyDown to avoid 0-pixel movement for
								arrow keys (Graham G Pearson)
					11/12 2004	Made UnselectAll virtual (Grisha Vinevich)
  ========================================================================*/

#include "stdafx.h"
#include "DiagramEditor.h"
#include <math.h>

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

#pragma warning( disable : 4706 )

/////////////////////////////////////////////////////////////////////////////
// CDiagramEditor construction/destruction/initialization

CDiagramEditor::CDiagramEditor()
/* ============================================================
	Function :		CDiagramEditor::CDiagramEditor
	Description :	Constructor
	Access :		Public

	Return :		void
	Parameters :	none

	Usage :			

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

	m_bkgndCol = ::GetSysColor( COLOR_WINDOW );
	m_nonClientBkgndCol = ::GetSysColor( COLOR_3DSHADOW );

	m_gridCol = RGB( 192, 192, 192 );
	m_grid = TRUE;
	m_gridStyle = PS_SOLID;
	m_gridSize = CSize( 8, 8 );

	m_snap = FALSE;
	m_bgResize = FALSE;
	m_bgResizeZone = 10;
	m_bgResizeSelected = FALSE;
	m_markerSize = CSize( 8, 8 );

	m_drawObj = NULL;
	m_objs = NULL;
	m_multiSelObj = NULL;
	m_internalData = NULL;

	m_restraint = RESTRAINT_NONE;

	m_leftMargin = 8;
	m_topMargin = 8;
	m_rightMargin = 8;
	m_bottomMargin = 16;
	m_margin = FALSE;
	m_marginColor = RGB( 128, 128, 255 );

	m_popupMenu = NULL;
	m_multiDraw = FALSE;

	m_zoomFactor = 0.01;
	m_zoomMax = 10.0;
	m_zoomMin = 0.0;

	m_panningTimer = 100;
	m_panning = FALSE;

	m_keyInterface = KEY_ALL;

	m_cursorNorth		= ::LoadCursor( NULL, IDC_SIZENS );
	m_cursorNorthEast	= ::LoadCursor( NULL, IDC_SIZENESW );
	m_cursorEast		= ::LoadCursor( NULL, IDC_SIZEWE );
	m_cursorSouthEast	= ::LoadCursor( NULL, IDC_SIZENWSE );
	m_cursorSouth		= ::LoadCursor( NULL, IDC_SIZENS );
	m_cursorSouthWest	= ::LoadCursor( NULL, IDC_SIZENESW );
	m_cursorWest		= ::LoadCursor( NULL, IDC_SIZEWE );
	m_cursorNorthWest	= ::LoadCursor( NULL, IDC_SIZENWSE );

	SetScrollWheelMode( WHEEL_SCROLL );

	Clear();

}

CDiagramEditor::~CDiagramEditor()
/* ============================================================
	Function :		CDiagramEditor::~CDiagramEditor
	Description :	Destructor
	Access :		Public

	Return :		void
	Parameters :	none

	Usage :			

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

	delete m_drawObj;
	delete m_internalData;
	delete m_popupMenu;

}

void CDiagramEditor::Clear()
/* ============================================================
	Function :		CDiagramEditor::Clear
	Description :	Clears internal run-time variables.
	Access :		Public

	Return :		void
	Parameters :	none

	Usage :			Call to reset internal states.

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

	// Clearing internal states and vars
	m_selectionRect.SetRectEmpty();
	m_interactMode = MODE_NONE;
	m_zoom = 1.0;
	m_bgResizeSelected = FALSE;
	m_deltaPoint = CSize( 0, 0 );
	m_multiSelObj = NULL;
	m_drawing = FALSE;

	SetPanning( FALSE );

	delete m_drawObj;
	m_drawObj = NULL;

	if( m_hWnd )
	{

		SetupScrollbars();
		SetHScroll( 0 );
		SetVScroll( 0 );

		RedrawWindow();

	}

}

BOOL CDiagramEditor::Create( DWORD dwStyle, const RECT &rect, CWnd *pParentWnd, CDiagramEntityContainer* data )
/* ============================================================
	Function :		CDiagramEditor::Create
	Description :	Creates a "CDiagramEditor" window.
	Access :		Public

	Return :		BOOL							-	"TRUE" if success
	Parameters :	DWORD dwStyle					-	Window styles for 
														the editor
					const RECT &rect				-	Window rectangle
					CWnd *pParentWnd				-	Parent of the 
														editor
					CDiagramEntityContainer* data	-	Pointer to data. 
														Might be "NULL".
					
	Usage :			If data is "NULL", a "CDiagramEntityContainer" 
					will be created internally, and the instance 
					of the editor will be responsible for the 
					container deletion.

   ============================================================*/
{
	if( data == NULL )
	{
		m_internalData = new CDiagramEntityContainer;
		SetInternalDiagramEntityContainer( m_internalData );
	}
	else
		SetDiagramEntityContainer( data );

	BOOL res = CWnd::Create( NULL, NULL, dwStyle, rect, pParentWnd, NULL );
	CSize virtualSize;
	if( GetVirtualSize().cx == 0 &&  GetVirtualSize().cy == 0 )
		virtualSize = CSize( rect.right - rect.left, rect.bottom - rect.top );
	else
		virtualSize = GetVirtualSize();

	SetInternalVirtualSize( virtualSize );

	return res;

}

void CDiagramEditor::New()
/* ============================================================
	Function :		CDiagramEditor::New
	Description :	Clears the current 'page' and creates a 
					new one.
	Access :		Public

	Return :		void
	Parameters :	none

	Usage :			Call to start a new drawing.

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

	SetRedraw( FALSE );
	Clear();
	m_objs->Clear();
	SetRedraw( TRUE );
	RedrawWindow();

}

void CDiagramEditor::SetDiagramEntityContainer( CDiagramEntityContainer* objs )
/* ============================================================
	Function :		CDiagramEditor::SetDiagramEntityContainer
	Description :	Sets the data container for the editor.
	Access :		Public
					
	Return :		void
	Parameters :	CDiagramEntityContainer* objs	-	the data 
														container
					
	Usage :			If this function is used, the caller is 
					responsible for deleting the container. Can 
					be called before or after "Create".

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

	if( m_internalData )
		delete m_internalData;

	m_internalData = NULL;

	SetInternalDiagramEntityContainer ( objs );

}

void CDiagramEditor::SetInternalDiagramEntityContainer( CDiagramEntityContainer* objs )
/* ============================================================
	Function :		CDiagramEditor::SetInternalDiagramEntityContainer
	Description :	Sets the internal data container pointer.
	Access :		Protected

	Return :		void
	Parameters :	CDiagramEntityContainer* objs	-	A pointer to the 
														container to set.
					
	Usage :			Internal function.

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

	m_objs = objs;

}

CDiagramEntityContainer* CDiagramEditor::GetDiagramEntityContainer() const
/* ============================================================
	Function :		CDiagramEditor::GetDiagramEntityContainer
	Description :	Returns a pointer to the data container.
	Access :		Public

	Return :		CDiagramEntityContainer*	-	The current data 
													container (might 
													be "NULL").
	Parameters :	none

	Usage :			If modifications are made to the contents of 
					the container, "SetModified" must be called as 
					appropriate. If visual changes are expected, 
					"RedrawWindow" must be called for the editor.

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

	return m_objs;

}

BEGIN_MESSAGE_MAP( CDiagramEditor, CWnd )
	//{{AFX_MSG_MAP( CDiagramEditor )
	ON_WM_PAINT()
	ON_WM_ERASEBKGND()
	ON_WM_SETCURSOR()
	ON_WM_SIZE()
	ON_WM_HSCROLL()
	ON_WM_VSCROLL()
	ON_WM_LBUTTONDOWN()
	ON_WM_LBUTTONUP()
	ON_WM_RBUTTONUP()
	ON_WM_RBUTTONDOWN()
	ON_WM_MOUSEMOVE()
	ON_WM_GETDLGCODE()
	ON_WM_KEYDOWN()
	ON_WM_LBUTTONDBLCLK()
	ON_WM_MBUTTONDOWN()
	//}}AFX_MSG_MAP

	ON_WM_KILLFOCUS()
	ON_WM_TIMER()
	ON_WM_MOUSEWHEEL()
	ON_COMMAND_RANGE( CMD_START, CMD_END, OnObjectCommand )

	ON_COMMAND( ID_EDIT_CUT, OnEditCut )
	ON_COMMAND( ID_EDIT_COPY, OnEditCopy )
	ON_COMMAND( ID_EDIT_PASTE, OnEditPaste )
	ON_COMMAND( ID_EDIT_GROUP, OnEditGroup )
	ON_COMMAND( ID_EDIT_UNGROUP, OnEditUngroup )

END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CDiagramEditor painting

void CDiagramEditor::OnPaint() 
/* ============================================================
	Function :		CDiagramEditor::OnPaint
	Description :	Draws the screen. A memory "CDC" is created 
					and drawn to.
	Access :		Protected

	Return :		void
	Parameters :	none

	Usage :			Called from MFC. Do not call from code.

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

	CPaintDC outputdc( this );

	// Getting coordinate data
	CRect rect;
	GetClientRect( &rect );

	SCROLLINFO sih;
	sih.cbSize = sizeof( SCROLLINFO );
	sih.fMask = SIF_POS;
	SCROLLINFO siv;
	siv.cbSize = sizeof( SCROLLINFO );
	siv.fMask = SIF_POS;
	if( !GetScrollInfo( SB_HORZ, &sih ) )
		sih.nPos = 0;
	if( !GetScrollInfo( SB_VERT, &siv ) )
		siv.nPos = 0;

	CRect totalRect;
	int virtwidth = round( static_cast< double >( GetVirtualSize().cx ) * GetZoom() ) + 1;
	int virtheight = round( static_cast< double >( GetVirtualSize().cy ) * GetZoom() ) + 1;
	totalRect.SetRect( 0, 0, virtwidth, virtheight );

	// Creating memory CDC
	CDC dc;
	dc.CreateCompatibleDC( &outputdc );
	CBitmap bmp;
	bmp.CreateCompatibleBitmap( &outputdc, rect.right, rect.bottom );
	CBitmap* oldbmp = dc.SelectObject( &bmp );

	// Painting
	EraseBackground( &dc, rect );

	dc.SetWindowOrg( sih.nPos, siv.nPos );

	Draw( &dc, totalRect );

	// Blit the memory CDC to screen
	outputdc.BitBlt( 0, 0, rect.right, rect.bottom, &dc, sih.nPos, siv.nPos, SRCCOPY );
	dc.SelectObject( oldbmp );

}

void CDiagramEditor::Draw( CDC* dc, CRect rect ) const
/* ============================================================
	Function :		CDiagramEditor::Draw
	Description :	Calls a series of (virtual) functions to 
					draw to "dc". "rect" is the total rectangle 
					to draw to.
	Access :		Public

	Return :		void
	Parameters :	CDC* dc		-	The "CDC" to draw to.
					CRect rect	-	The complete rectangle 
									(including non-visible areas)
					
	Usage :			Should not normally be called from user code. 
					Can be called to draw the complete window. 
					Can be overriden to change drawing order.

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

	double zoom = GetZoom();

	DrawBackground( dc, rect, zoom );

	if( m_grid )
		DrawGrid( dc, rect, zoom );

	if( m_margin )
		DrawMargins( dc, rect, zoom );

	DrawObjects( dc, zoom );

	if( m_bgResize && m_bgResizeSelected )
		DrawSelectionMarkers( dc );

	if( GetPanning() )
		DrawPanning( dc );

}

void CDiagramEditor::Print( CDC* dc, CRect rect, double zoom ) 
/* ============================================================
	Function :		CDiagramEditor::Print
	Description :	Prints the editor to printer or print a 
					preview.
	Access :		Public

	Return :		void
	Parameters :	CDC* dc		-	Printer- or preview "CDC" to 
									draw to.
					CRect rect	-	Total rect of editor.
					double zoom	-	Desired zoom value.
					
	Usage :			The function should be called to print the 
					editor to a printer or print preview. This 
					is because the zoom will most likely be set 
					to something different from the editor itself. 
					A good zoom factor can be calculated from 
					the difference between the screen- and 
					printer resolution:

					"printerDC.GetDeviceCaps( LOGPIXELSX ) / screenDC.GetDeviceCaps( LOGPIXELSX )"

					The grid, margin and selection markers are 
					not printed.
					Can be overriden to change drawing order, or 
					add drawing of the grid etc.
   ============================================================*/
{

	UnselectAll();
	DrawBackground( dc, rect, zoom );
	DrawObjects( dc, zoom );

}

BOOL CDiagramEditor::OnEraseBkgnd( CDC* ) 
/* ============================================================
	Function :		CDiagramEditor::OnEraseBkgnd
	Description :	Handles the "WM_ERASEBKGND" message. Handled 
					to avoid flicker - the editor is completely 
					redrawn in "OnPaint".
	Access :		Protected

	Return :		BOOL	-	
	Parameters :	CDC*	-	
					
	Usage :			Called from MFC. Do not call from code.

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

	return TRUE;

}

/////////////////////////////////////////////////////////////////////////////
// CDiagramEditor painting virtuals

void CDiagramEditor::EraseBackground( CDC* dc, CRect rect ) const
/* ============================================================
	Function :		CDiagramEditor::EraseBackground
	Description :	Draws the non-client background
	Access :		Protected

	Return :		void
	Parameters :	CDC* dc		-	"CDC" to draw to.
					CRect rect	-	Total rect to draw to.
					
	Usage :			Virtual. Can be overridden in a derived class 
					to erase the non-client area.

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

	dc->FillSolidRect( rect, m_nonClientBkgndCol );

}

void CDiagramEditor::DrawBackground( CDC* dc, CRect rect, double /*zoom*/) const
/* ============================================================
	Function :		CDiagramEditor::DrawBackground
	Description :	Draws the virtual background
	Access :		Protected

	Return :		void
	Parameters :	CDC* dc			-	"CDC" to draw to.
					CRect rect		-	Total rect to draw to.
					double (zoom)	-	Current zoom level. 
										Coordinates can be 
										multiplied with this 
										value to get scaled.
					
	Usage :			Virtual. Can be overridden in a derived 
					class to draw the virtual paper area.

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

	dc->FillSolidRect( &rect, m_bkgndCol );

}

void CDiagramEditor::DrawGrid( CDC* dc, CRect rect, double zoom ) const
/* ============================================================
	Function :		CDiagramEditor::DrawGrid
	Description :	Draws the grid
	Access :		Protected

	Return :		void
	Parameters :	CDC* dc		-	"CDC" to draw to.
					CRect rect	-	Total rect to draw to.
					double zoom	-	Current zoom level. 
									Coordinates can be 
									multiplied with this value 
									to get scaled.
					
	Usage :			Virtual. Can be overridden in a derived 
					class to draw the grid. Will not be called 
					if the grid is not visible.

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

	COLORREF gridcol = GetGridColor();
	int gridstyle = GetGridPenStyle();

	CPen pen;
	pen.CreatePen( gridstyle, 1, gridcol );

	dc->SelectObject( &pen );

	// To avoid accumulating rounding errors, we don't
	// precalculate the grid size for the given zoom...

	int width = rect.Width();
	int height = rect.Height();

	int stepx = GetVirtualSize().cx / GetGridSize().cx;
	int stepy = GetVirtualSize().cy / GetGridSize().cy;

	// ...instead we calculate the position of each line.
	for( int x = 0 ; x <= stepx ; x++ )
	{
		dc->MoveTo( round( static_cast< double >( GetGridSize().cx * x ) * zoom ), 0 );
		dc->LineTo( round( static_cast< double >( GetGridSize().cx * x ) * zoom ), height );
	}

	for( int y = 0; y <= stepy ; y++ )
	{
		dc->MoveTo( 0, round( static_cast< double >( GetGridSize().cy * y ) * zoom ) );
		dc->LineTo( width, round( static_cast< double >( GetGridSize().cy * y ) * zoom ) );
	}

	dc->SelectStockObject( BLACK_PEN );

}

void CDiagramEditor::DrawMargins( CDC* dc, CRect rect, double zoom ) const
/* ============================================================
	Function :		CDiagramEditor::DrawMargins
	Description :	Draws the margins.
	Access :		Protected

	Return :		void
	Parameters :	CDC* dc		-	"CDC" to draw to.
					CRect rect	-	Total rect to draw to.
					double zoom	-	Current zoom level. 
									Coordinates can be 
									multiplied with this value 
									to get scaled.
					
	Usage :			Virtual. Can be overridden in a derived 
					class to draw the margins. Will not be 
					called if margins are not visible.

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

	CPen pen;
	pen.CreatePen( PS_SOLID, 0, m_marginColor );
	dc->SelectObject( &pen );

	CPoint leftTop( rect.left + round( static_cast< double >( m_leftMargin ) * zoom ), rect.top + round( static_cast< double >( m_topMargin ) * zoom ) );
	CPoint leftBottom( rect.left + round( static_cast< double >( m_leftMargin ) * zoom ), rect.bottom - round( static_cast< double >( m_bottomMargin ) * zoom ) - 1 );
	CPoint rightTop( rect.right - round( static_cast< double >( m_rightMargin ) * zoom ) - 1, rect.top + round( static_cast< double >( m_topMargin ) * zoom ) );
	CPoint rightBottom( rect.right - round( static_cast< double >( m_rightMargin ) * zoom ) - 1, rect.bottom - round( static_cast< double >( m_bottomMargin ) * zoom ) - 1 );

	if( m_leftMargin )
	{
		dc->MoveTo( leftTop );
		dc->LineTo( leftBottom );
	}

	if( m_rightMargin )
	{
		dc->MoveTo( rightTop );
		dc->LineTo( rightBottom );
	}
	if( m_topMargin )
	{
		dc->MoveTo( leftTop );
		dc->LineTo( rightTop );
	}
	if( m_bottomMargin )
	{
		dc->MoveTo( leftBottom );
		dc->LineTo( rightBottom );
	}

	dc->SelectStockObject( BLACK_PEN );


}

void CDiagramEditor::DrawObjects( CDC* dc, double zoom ) const
/* ============================================================
	Function :		CDiagramEditor::DrawObjects
	Description :	Draws the object.
	Access :		Protected

	Return :		void
	Parameters :	CDC* dc		-	"CDC" to draw to.
					double zoom	-	Current zoom level. 
									Coordinates can be 
									multiplied with this value 
									to get scaled.
					
	Usage :			Virtual. Can be overridden in a derived 
					class to draw the data objects.

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

	if( m_objs )
	{
		int count = 0;
		CDiagramEntity* obj;
		while( ( obj = m_objs->GetAt( count++ ) ) )
			obj->DrawObject( dc, zoom );
	}

}

void CDiagramEditor::DrawSelectionMarkers( CDC* dc ) const
/* ============================================================
	Function :		CDiagramEditor::DrawSelectionMarkers
	Description :	Draws the selection markers.
	Access :		Protected

	Return :		void
	Parameters :	CDC* dc	-	The "CDC" to draw to.
					
	Usage :			Virtual. Can be overridden in a derived 
					class to draw the selection markers in 
					another way than the default black 
					rectangles. The selection rects are 
					displayed if the editor has background 
					resizing enabled and the user clicks in 
					the resize area around the virtual page 
					border. Selection markers are displayed 
					to allow resizing of the virtual page 
					with the mouse. 

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

	// Draw selection markers
	CRect rectSelect;

	dc->SelectStockObject( BLACK_BRUSH );
	rectSelect = GetSelectionMarkerRect( DEHT_TOPLEFT );
	dc->Rectangle( rectSelect );

	rectSelect = GetSelectionMarkerRect( DEHT_TOPMIDDLE );
	dc->Rectangle( rectSelect );

	rectSelect = GetSelectionMarkerRect( DEHT_TOPRIGHT );
	dc->Rectangle( rectSelect );

	rectSelect = GetSelectionMarkerRect( DEHT_BOTTOMLEFT );
	dc->Rectangle( rectSelect );

	rectSelect = GetSelectionMarkerRect( DEHT_BOTTOMMIDDLE );
	dc->Rectangle( rectSelect );

	rectSelect = GetSelectionMarkerRect( DEHT_BOTTOMRIGHT );
	dc->Rectangle( rectSelect );

	rectSelect = GetSelectionMarkerRect( DEHT_RIGHTMIDDLE );
	dc->Rectangle( rectSelect );

	rectSelect = GetSelectionMarkerRect( DEHT_LEFTMIDDLE );
	dc->Rectangle( rectSelect );

}

CRect CDiagramEditor::GetSelectionMarkerRect( UINT marker ) const
/* ============================================================
	Function :		CDiagramEditor::GetSelectionMarkerRect
	Description :	Returns the selection rect for marker
	Access :		Public

	Return :		CRect		-	The rect of the selection 
									marker.
	Parameters :	UINT marker	-	The marker to get the rect 
									for ("DEHT_"-constants 
									defined in DiagramEntity.h)
					
	Usage :			Virtual. Can be overridden in a derived 
					class to change the selection rects for 
					the virtual page. The selection rects are 
					displayed if the editor has background 
					resizing enabled and the user clicks in 
					the resize area around the virtual page 
					border. Selection markers are displayed 
					to allow resizing of the virtual page 
					with the mouse.
					"marker" can be one of the following:
						"DEHT_NONE" No hit-point
						"DEHT_BODY" Inside object body
						"DEHT_TOPLEFT" Top-left corner
						"DEHT_TOPMIDDLE" Middle top-side
						"DEHT_TOPRIGHT" Top-right corner
						"DEHT_BOTTOMLEFT" Bottom-left corner
						"DEHT_BOTTOMMIDDLE" Middle bottom-side
						"DEHT_BOTTOMRIGHT" Bottom-right corner
						"DEHT_LEFTMIDDLE" Middle left-side
						"DEHT_RIGHTMIDDLE" Middle right-side
   ============================================================*/
{

	CRect rect( 0, 0, round( static_cast< double >( GetVirtualSize().cx ) * GetZoom() ), 
						round( static_cast< double >( GetVirtualSize().cy ) * GetZoom() ) );

	CRect rectMarker;
	int horz = m_markerSize.cx / 2;
	int vert = m_markerSize.cy / 2;

	switch( marker )
	{
		case DEHT_TOPLEFT:
			rectMarker.SetRect( rect.left - horz, 
								rect.top - vert, 
								rect.left + horz, 
								rect.top + vert );
		break;

		case DEHT_TOPMIDDLE:
			rectMarker.SetRect( rect.left + ( rect.Width() / 2 ) - horz, 
								rect.top - vert, 
								rect.left + ( rect.Width() / 2 ) + horz, 
								rect.top + vert );
		break;

		case DEHT_TOPRIGHT:
			rectMarker.SetRect( rect.right - horz, 
								rect.top - vert, 
								rect.right + horz, 
								rect.top + vert );
		break;

		case DEHT_BOTTOMLEFT:
			rectMarker.SetRect( rect.left - horz, 
								rect.bottom - vert, 
								rect.left + horz, 
								rect.bottom + vert );
		break;

		case DEHT_BOTTOMMIDDLE:
			rectMarker.SetRect( rect.left + ( rect.Width() / 2 ) - horz, 
								rect.bottom - vert, 
								rect.left + ( rect.Width() / 2 ) + horz, 
								rect.bottom + vert );
		break;

		case DEHT_BOTTOMRIGHT:
			rectMarker.SetRect( rect.right - horz, 
								rect.bottom - vert, 
								rect.right + horz, 
								rect.bottom + vert );
		break;

		case DEHT_LEFTMIDDLE:
			rectMarker.SetRect( rect.left - horz, 
								rect.top + ( rect.Height() / 2 ) - vert, 
								rect.left + horz, 
								rect.top + ( rect.Height() / 2 ) + vert );
		break;

		case DEHT_RIGHTMIDDLE:
			rectMarker.SetRect( rect.right - horz, 
								rect.top + ( rect.Height() / 2 ) - vert, 
								rect.right + horz, 
								rect.top + ( rect.Height() / 2 ) + vert );
		break;
	}

	return rectMarker;

}

/////////////////////////////////////////////////////////////////////////////
// CDiagramEditor property accessors

void CDiagramEditor::SetVirtualSize( const CSize& size )
/* ============================================================
	Function :		CDiagramEditor::SetVirtualSize
	Description :	Sets the size of the virtual paper.
	Access :		Public

	Return :		void
	Parameters :	const CSize& size	-	New virtual size.
					
	Usage :			The virtual page of the editor is the 
					'paper' where the user can draw. This 
					function marks the data as changed.

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

	ASSERT( m_objs );
	SetInternalVirtualSize( size );
	m_objs->SetModified( TRUE );

}

void CDiagramEditor::SetInternalVirtualSize( const CSize& size )
/* ============================================================
	Function :		CDiagramEditor::SetInternalVirtualSize
	Description :	Changes the virtual page size without 
					setting the data as modified.
	Access :		Private

	Return :		void
	Parameters :	const CSize& size	-	New virtual size.
					
	Usage :			Internal function. 

   ============================================================*/
{
	if( m_objs && size != GetVirtualSize() )
	{

		m_objs->SetVirtualSize( size );

		SetupScrollbars();
		if( m_hWnd )
			RedrawWindow();

	}

}

CSize CDiagramEditor::GetVirtualSize() const
/* ============================================================
	Function :		CDiagramEditor::GetVirtualSize
	Description :	Returns the virtual size of the editor.
	Access :		Public

	Return :		CSize	-	The current virtual size.
	Parameters :	none

	Usage :			The virtual page of the editor is the 
					'paper' where the user can draw. 

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

	CRect rect( 0, 0, 0, 0 );
	if( m_hWnd )
		GetClientRect( &rect );
	CSize size( CSize( 0, 0 ) );

	if( m_objs )
		size = m_objs->GetVirtualSize();

	if( size.cx == 0 )
		size.cx = rect.right;
	if( size.cy == 0 )
		size.cy = rect.bottom;

	return size;

}

void CDiagramEditor::SetBackgroundColor( COLORREF col )
/* ============================================================
	Function :		CDiagramEditor::SetBackgroundColor
	Description :	Sets the background color.
	Access :		Public

	Return :		void
	Parameters :	COLORREF col	-	New background color 
										to set.
					
	Usage :			The background is the virtual area of the 
					editor (might both be smaller or bigger 
					than the client rect).

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

	m_bkgndCol = col;
	if( m_hWnd )
		RedrawWindow();

}

void CDiagramEditor::SetNonClientColor( COLORREF col )
/* ============================================================
	Function :		CDiagramEditor::SetNonClientColor
	Description :	Sets the non-client area color.
	Access :		Public

	Return :		void
	Parameters :	COLORREF col	-	New non-client area 
										color.
					
	Usage :			The non-client color is the color of the 
					area outside the virtual page.

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

	m_nonClientBkgndCol = col;
	if( m_hWnd )
		RedrawWindow();

}

void CDiagramEditor::ShowGrid( BOOL grid )
/* ============================================================
	Function :		CDiagramEditor::ShowGrid
	Description :	Sets grid visibility.
	Access :		Public

	Return :		void
	Parameters :	BOOL grid	-	"TRUE" to show the grid, "FALSE" 
									to hide.
					
	Usage :			If the grid is visible, it will be drawn 
					using the grid pen style and color. The 
					grid lines will not be scaled with the 
					zoom (the space between them will of 
					course be, however)

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

	m_grid = grid;
	if( m_hWnd )
		RedrawWindow();

}

BOOL CDiagramEditor::IsGridVisible() const
/* ============================================================
	Function :		CDiagramEditor::IsGridVisible
	Description :	Returns the visibility state of the grid.
	Access :		Public

	Return :		BOOL	-	"TRUE" if grid is visible.
	Parameters :	none

	Usage :			If the grid is visible, it will be drawn 
					using the grid pen style and color. The 
					grid lines will not be scaled with the 
					zoom (the space between them will of 
					course be, however)

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

	return m_grid;

}

void CDiagramEditor::SetGridColor( COLORREF col )
/* ============================================================
	Function :		CDiagramEditor::SetGridColor
	Description :	Sets a new grid pen color.
	Access :		Public

	Return :		void
	Parameters :	COLORREF col	-	New grid pen color.
					
	Usage :			If the grid is visible, it will be drawn 
					using the grid pen style and color. The 
					grid lines will not be scaled with the 
					zoom (the space between them will of 
					course be, however)

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

	m_gridCol = col;
	if( m_hWnd )
		RedrawWindow();

}

COLORREF CDiagramEditor::GetGridColor() const
/* ============================================================
	Function :		CDiagramEditor::GetGridColor
	Description :	Returns the current grid pen color.
	Access :		Public

	Return :		COLORREF	-	The current grid color.
	Parameters :	none

	Usage :			If the grid is visible, it will be drawn 
					using the grid pen style and color. The 
					grid lines will not be scaled with the 
					zoom (the space between them will of 
					course be, however)

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

	return m_gridCol;

}

void CDiagramEditor::SetGridSize( CSize size )
/* ============================================================
	Function :		CDiagramEditor::SetGridSize
	Description :	Sets a new grid size.
	Access :		Public

	Return :		void
	Parameters :	CSize size	-	The new grid size.
					
	Usage :			If snap to grid is on, added, moved and 
					resized objects snap to the closest grid 
					position. If the background is resized, it 
					will also snap to the grid.

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

	m_gridSize = size;
	if( m_hWnd )
		RedrawWindow();

}

CSize CDiagramEditor::GetGridSize() const
/* ============================================================
	Function :		CDiagramEditor::GetGridSize
	Description :	Gets the current grid size.
	Access :		Public

	Return :		CSize	-	The current grid size.
	Parameters :	none

	Usage :			If snap to grid is on, added, moved and 
					resized objects snap to the closest grid 
					position. If the background is resized, it 
					will also snap to the grid.

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

	return m_gridSize;

}

void CDiagramEditor::SetGridPenStyle( int style )
/* ============================================================
	Function :		CDiagramEditor::SetGridPenStyle
	Description :	Sets the new grid pen style.
	Access :		Public

	Return :		void
	Parameters :	int style	-	The new pen style, one of 
									the style constants for 
									"CreatePen".
					
	Usage :			The grid (if visible) is drawn with a pen 
					created with the grid pen style. The grid 
					lines will not be scaled with the zoom 
					(the space between them will of course be, 
					however)

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

	m_gridStyle = style;
	if( m_hWnd )
		RedrawWindow();

}

int CDiagramEditor::GetGridPenStyle() const
/* ============================================================
	Function :		CDiagramEditor::GetGridPenStyle
	Description :	Returns the pen style for the grid.
	Access :		Public

	Return :		int		-	The pen style, one of the style 
								constants for "CreatePen".
	Parameters :	none

	Usage :			The grid (if visible) is drawn with a pen 
					created with the grid pen style. The grid 
					lines will not be scaled with the zoom 
					(the space between them will of course be, 
					however)

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

	return m_gridStyle;

}

void CDiagramEditor::SetSnapToGrid( BOOL snap )
/* ============================================================
	Function :		CDiagramEditor::SetSnapToGrid
	Description :	Enable/disable snap to grid.
	Access :		Public

	Return :		void
	Parameters :	BOOL snap	-	"TRUE" if objects should 
									snap to grid.
					
	Usage :			If snap to grid is on, added, moved and 
					resized objects snap to the closest grid 
					position. If the background is resized, it 
					will also snap to the grid.

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

	m_snap = snap;

}

BOOL CDiagramEditor::GetSnapToGrid() const
/* ============================================================
	Function :		CDiagramEditor::GetSnapToGrid
	Description :	Gets the state of the snap-to-grid state.
	Access :		Public

	Return :		BOOL	-	"TRUE" if snap is on.
	Parameters :	none

	Usage :			If snap to grid is on, added, moved and 
					resized objects snap to the closest grid 
					position. If the background is resized, it 
					will also snap to the grid.

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

	return m_snap;

}

void CDiagramEditor::SetResize( BOOL bgresize )
/* ============================================================
	Function :		CDiagramEditor::SetResize
	Description :	Enables/disables background resizing.
	Access :		Public

	Return :		void
	Parameters :	BOOL bgresize	-	"TRUE" if background 
										resizing is enabled.
					
	Usage :			If background resizing is enabled and the 
					user clicks between the resize zone value 
					and the virtual edge, the background is 
					displayed as selected.

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

	m_bgResize = bgresize;

}

BOOL CDiagramEditor::GetResize() const
/* ============================================================
	Function :		CDiagramEditor::GetResize
	Description :	Returns the state of the background resize
					flag.
	Access :		Public

	Return :		BOOL	-	"TRUE" if background resizing is 
								enabled.
	Parameters :	none

	Usage :			If background resizing is enabled and the 
					user clicks between the resize zone value 
					and the virtual edge, the background is 
					displayed as selected.

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

	return m_bgResize;

}

void CDiagramEditor::SetResizeZone( int bgresizezone )
/* ============================================================
	Function :		CDiagramEditor::SetResizeZone
	Description :	Sets a new resize zone for the editor.
	Access :		Public

	Return :		void
	Parameters :	int bgresizezone	-	New resize width.
					
	Usage :			If background resizing is enabled and the 
					user clicks between the resize zone value 
					and the virtual edge, the background is 
					displayed as selected.

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

	m_bgResizeZone = bgresizezone;

}

int CDiagramEditor::GetResizeZone() const
/* ============================================================
	Function :		CDiagramEditor::GetResizeZone
	Description :	Returns the current resize zone.
	Access :		Public

	Return :		int		-	The resize zone in pixels.
	Parameters :	none

	Usage :			If background resizing is enabled and the 
					user clicks between the resize zone value 
					and the virtual edge, the background is 
					displayed as selected.

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

	return m_bgResizeZone;

}

void CDiagramEditor::SetMargins( int left, int top, int right, int bottom )
/* ============================================================
	Function :		CDiagramEditor::SetMargins
	Description :	Sets margins for the virtual screen.
	Access :		Public

	Return :		void
	Parameters :	int left	-	New left margin.
					int top		-	New top margin.
					int right	-	New right margin.
					int bottom	-	New bottom margin.
					
	Usage :			Call to set new margins for the editor.

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

	m_leftMargin = left;
	m_topMargin = top;
	m_rightMargin = right;
	m_bottomMargin = bottom;

}

void CDiagramEditor::GetMargins( int& left, int& top, int& right, int& bottom ) const
/* ============================================================
	Function :		CDiagramEditor::GetMargins
	Description :	Return the current margin.
	Access :		Public

	Return :		void
	Parameters :	int& left	-	Current left margin.
					int& top	-	Current top margin.
					int& right	-	Current right margin.
					int& bottom	-	Current bottom margin.
					
	Usage :			Call to get the margins of the editor.

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

	left = m_leftMargin;
	top = m_topMargin;
	right = m_rightMargin;
	bottom = m_bottomMargin;

}

void CDiagramEditor::SetMarginColor( COLORREF marginColor )
/* ============================================================
	Function :		CDiagramEditor::SetMarginColor
	Description :	Set current margin colors.
	Access :		Public

	Return :		void
	Parameters :	COLORREF marginColor	-	The new color.
					
	Usage :			Call to set the margin color.

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

	m_marginColor = marginColor;

	if( m_hWnd )
		RedrawWindow();

}

COLORREF CDiagramEditor::GetMarginColor() const
/* ============================================================
	Function :		CDiagramEditor::GetMarginColor
	Description :	Returns the current margin colors.
	Access :		Public

	Return :		COLORREF	-	Margin colors.
	Parameters :	none

	Usage :			Call to get the margin color.

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

	return m_marginColor;

}

void CDiagramEditor::ShowMargin( BOOL show )
/* ============================================================
	Function :		CDiagramEditor::ShowMargin
	Description :	Show/hide margins.
	Access :		Public

	Return :		void
	Parameters :	BOOL show	-	"TRUE" to show margins, "FALSE" 
									to hide.
					
	Usage :			Call to show/hide the margins.

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

	m_margin = show;

	if( m_hWnd )
		RedrawWindow();

}

BOOL CDiagramEditor::IsMarginVisible() const
/* ============================================================
	Function :		CDiagramEditor::IsMarginVisible
	Description :	Returns the visibility state of the 
					margins.
	Access :		Public

	Return :		BOOL	-	"TRUE" if the margins are 
								visible.
	Parameters :	none

	Usage :			Call to se if the margin visibility flag is 
					on.

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

	return m_margin;

}

void CDiagramEditor::SetRestraints( int restraint )
/* ============================================================
	Function :		CDiagramEditor::SetRestraints
	Description :	Sets current restraint mode
	Access :		Public

	Return :		void
	Parameters :	BOOL restraint	-	restraint mode, see Usage 
										below
					
	Usage :			The restraint mode can be one of the 
					following ( defined in DialogEditor.h):
						"RESTRAINT_NONE" No restraints.
						"RESTRAINT_VIRTUAL" No objects can be moved outside the virtual rectangle.
						"RESTRAINT_MARGIN" No objects can be moved outside the background margins.

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

	m_restraint = restraint;

}

int CDiagramEditor::GetRestraints() const
/* ============================================================
	Function :		CDiagramEditor::GetRestraints
	Description :	Returns the current restraint mode.
	Access :		Public

	Return :		int	-	The current restraint mode. See 
							Usage below

	Parameters :	none

	Usage :			The restraint mode can be one of the 
					following ( defined in DialogEditor.h):
						"RESTRAINT_NONE" No restraints.
						"RESTRAINT_VIRTUAL" No objects can be moved outside the virtual rectangle.
						"RESTRAINT_MARGIN" No objects can be moved outside the background margins.

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

	return m_restraint;

}

BOOL CDiagramEditor::GetMultidraw() const
/* ============================================================
	Function :		CDiagramEditor::GetMultidraw
	Description :	Returns the multi draw state
	Access :		Public

	Return :		BOOL	-	"TRUE" if multi draw is set
	Parameters :	none

	Usage :			Multi draw means that the user can continue 
					to add controls after one is placed.

   ============================================================*/
{
	return m_multiDraw;
}

void CDiagramEditor::SetMultidraw( BOOL multidraw )
/* ============================================================
	Function :		CDiagramEditor::SetMultidraw
	Description :	Sets the multi draw state.
	Access :		Public

	Return :		void
	Parameters :	BOOL multidraw	-	New multi draw state
					
	Usage :			Multi draw means that the user can continue 
					to add controls after one is placed.

   ============================================================*/
{
	m_multiDraw = multidraw;
	m_drawing = FALSE;
	m_interactMode = MODE_NONE;
	delete m_drawObj;
	m_drawObj = NULL;
}

void CDiagramEditor::SetZoom( double zoom )
/* ============================================================
	Function :		CDiagramEditor::SetZoom
	Description :	Set the current zoom level.
	Access :		Public

	Return :		void
	Parameters :	double zoom	-	New zoom level. 1.0 means 
					no zoom.
					
	Usage :			If the virtual screen should be zoomed in 
					to double size, the zoom value should be 
					2.0, for example.

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

	m_zoom = max( m_zoomMin, zoom );

	if( m_zoom != m_zoomMin )
		m_zoom = min( m_zoomMax, zoom );

	SetupScrollbars();
	if( m_hWnd )
		RedrawWindow();

}

double CDiagramEditor::GetZoom() const
/* ============================================================
	Function :		CDiagramEditor::GetZoom
	Description :	Returns the current zoom level of the editor. 
	Access :		Public

	Return :		double	-	The current zoom level. 1.0 is 
								no zoom.
	Parameters :	none

	Usage :			The zoom level is expressed as a double, 
					.25 means that all zoomed coordinates 
					should be multiplied with .25, for example.

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

	return m_zoom;

}

void CDiagramEditor::SetZoomFactor( double zoomFactor )
/* ============================================================
	Function :		CDiagramEditor::SetZoomFactor
	Description :	Sets a new zoom factor
	Access :		Public

	Return :		void
	Parameters :	double zoomFactor	-	The new zoom factor
					
	Usage :			The zoom factor is used when the + and - 
					keys are enabled to zoom. The default value 
					is 0.01.

   ============================================================*/
{
	m_zoomFactor = zoomFactor;
}

double CDiagramEditor::GetZoomFactor() const
/* ============================================================
	Function :		CDiagramEditor::GetZoomFactor
	Description :	Returns the current zoom factor
	Access :		Public

	Return :		double	-	The zoom factor
	Parameters :	none

	Usage :			The zoom factor is used when the + and - 
					keys are enabled to zoom. The default value 
					is 0.01.

   ============================================================*/
{
	return m_zoomFactor;
}

double CDiagramEditor::GetZoomMax() const
/* ============================================================
	Function :		CDiagramEditor::GetZoomMax
	Description :	Returns the maximum allowed zoom level
	Access :		Public

	Return :		double	-	The maximum zoom level
	Parameters :	none

	Usage :			The zoom level will never go below or above 
					the min and max zoom levels

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

	return m_zoomMax;

}

double CDiagramEditor::GetZoomMin() const
/* ============================================================
	Function :		CDiagramEditor::GetZoomMin
	Description :	Returns the minimum allowed zoom level
	Access :		Public

	Return :		double	-	The minimum zoom level
	Parameters :	none

	Usage :			The zoom level will never go below or above 
					the min and max zoom levels

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

	return m_zoomMin;

}

void CDiagramEditor::SetZoomMax( double zoommax )
/* ============================================================
	Function :		CDiagramEditor::SetZoomMax
	Description :	Sets the maximum zoom level
	Access :		Public

	Return :		void
	Parameters :	double zoommax	-	The new max zoom
					
	Usage :			The zoom level will never go below or above 
					the min and max zoom levels

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

	m_zoomMax = max( 0, zoommax );

}

void CDiagramEditor::SetZoomMin( double zoommin )
/* ============================================================
	Function :		CDiagramEditor::SetZoomMin
	Description :	Sets the mim zoom level
	Access :		Public

	Return :		void
	Parameters :	double zoommin	-	New minimum zoom level
					
	Usage :			The zoom level will never go below or above 
					the min and max zoom levels

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

	m_zoomMin = max( 0, zoommin );

}

void CDiagramEditor::SetModified( BOOL dirty )
/* ============================================================
	Function :		CDiagramEditor::SetModified
	Description :	Set the modified-state of the data for the 
					document.
	Access :		Public

	Return :		void
	Parameters :	BOOL dirty	-	"TRUE" if the data should be 
									set as modified.
					
	Usage :			Call to mark the data as modified/saved.

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

	if( m_objs )
		m_objs->SetModified( dirty );

}

BOOL CDiagramEditor::IsModified() const
/* ============================================================
	Function :		CDiagramEditor::IsModified
	Description :	Returns "TRUE" if the data in the editor is 
					modified.
	Access :		Public

	Return :		BOOL	-	"TRUE" if modified.
	Parameters :	none

	Usage :			Call to check if the data is modified.

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

	BOOL res = FALSE;
	if( m_objs )
		res = m_objs->IsModified();

	return res;

}

CSize CDiagramEditor::GetMarkerSize() const
/* ============================================================
	Function :		CDiagramEditor::GetMarkerSize
	Description :	Gets the current selection marker size for 
					the editor background.
	Access :		Public

	Return :		CSize	-	The current size.
	Parameters :	none

	Usage :			Call to get the selection marker size.

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

	return m_markerSize;

}

void CDiagramEditor::SetMarkerSize( CSize markerSize )
/* ============================================================
	Function :		CDiagramEditor::SetMarkerSize
	Description :	Sets the size of the selection markers for 
					the editor background.
	Access :		Public

	Return :		void
	Parameters :	CSize markerSize	-	New size of 
											markers.
					
	Usage :			Call to set the selection marker size.
					Will not set the selection marker size for 
					objects.

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

	m_markerSize = markerSize;

}

UINT CDiagramEditor::GetKeyboardInterface() const
/* ============================================================
	Function :		CDiagramEditor::GetKeyboardInterface
	Description :	Returns the flags for the keyboard 
					interface
	Access :		Public

	Return :		UINT	-	The current flags
	Parameters :	none

	Usage :			The keyboard interface decides what keys 
					should be reacted on. The following flags 
					can be used:
						"KEY_ARROW" Will handle arrow keys. If shift is pressed, the selected objects will be resized, moved otherwise.
						"KEY_PGUPDOWN" Will handle Pg up & pg down. If Ctrl is pressed, the selected object will move to the top or the bottom of the z-order, one step up or down otherwise.
						"KEY_DELETE" Will handle delete key. The selected object(s) will be deleted, put into the container clipboard if Ctrl is pressed.
						"KEY_ESCAPE" Will handle escape key. If multi-draw mode, no object type is selected for drawing.
						"KEY_INSERT" Will handle insert key. The selected object will be copied if Ctrl is pressed, duplicated otherwise.
						"KEY_PLUSMINUS" Will handle the plus- and minus key. Will zoom in or out.
						"KEY_CTRL" Will handle Ctrl+A,Z,X,C,V and Enter keys. A = Select all Z = Undo X = Cut C = Copy V = Paste Enter = Show property dialog for the selected object.
					KEY_ALL sets all flags. KEY_NONE no flags.

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

	return m_keyInterface;

}

void CDiagramEditor::SetKeyboardInterface( int keyInterface )
/* ============================================================
	Function :		CDiagramEditor::SetKeyboardInterface
	Description :	Sets the keyboard interface flag.
	Access :		Public

	Return :		void
	Parameters :	int keyInterface	-	The new flags
					
	Usage :			Call to set the keys the editor will handle.
					See "GetKeyboardInterface".

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

	m_keyInterface = keyInterface;

}

void CDiagramEditor::SetPopupMenu( CDiagramMenu* popupmenu )
/* ============================================================
	Function :		CDiagramEditor::SetPopupMenu
	Description :	Sets the editor popup menu to popupmenu.
	Access :		Public

	Return :		void
	Parameters :	CDiagramMenu* popupmenu	-	The popup menu 
												to use.
					
	Usage :			Call to set the popup menu for the editor.
					Note that the editor owns the popup menu.

   ============================================================*/
{
	if( m_popupMenu )
		delete m_popupMenu;

	m_popupMenu = popupmenu;

}

CDiagramMenu* CDiagramEditor::GetPopupMenu() const
/* ============================================================
	Function :		CDiagramEditor::GetPopupMenu
	Description :	Returns a pointer to the editor popup menu.
	Access :		Public

	Return :		CDiagramMenu*	-	The popup menu. 
										Might be "NULL".
	Parameters :	none

	Usage :			Call to get a pointer to the editor popup 
					menu. Note that the editor owns the menu.

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

	return m_popupMenu;

}

/////////////////////////////////////////////////////////////////////////////
// CDiagramEditor mouse handling

void CDiagramEditor::OnLButtonDown( UINT nFlags, CPoint point ) 
/* ============================================================
	Function :		CDiagramEditor::OnLButtonDown
	Description :	Handles the "WM_LBUTTONDOWN" message. We 
					select/deselect objects and set internal 
					modes as appropriate.
	Access :		Protected

	Return :		void
	Parameters :	UINT nFlags		-	Key-down flag
					CPoint point	-	Mouse position
					
	Usage :			Called from MFC. Do not call from code.

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

	SetPanning( FALSE );

	// Declarations
	int count = 0;
	CDiagramEntity* obj;
	CPoint virtpoint( point );
	ScreenToVirtual( virtpoint );

	// Setting up
	SetFocus();
	SetCapture();

	// Resetting modes
	if( m_interactMode != MODE_DRAWING )
	{
		m_interactMode = MODE_NONE;
		m_subMode = DEHT_NONE;
	}

	m_bgResizeSelected = FALSE;
	m_multiSelObj = NULL;

	// If we click on an already selected object, 
	// and have more than one object selected,
	// we want to move all selected objects
	if( m_objs && GetSelectCount() > 1 && !( nFlags & MK_CONTROL ) )
	{

		while( ( obj = m_objs->GetAt( count++ ) ) )
		{
			if( obj->GetHitCode( virtpoint ) == DEHT_BODY && obj->IsSelected() )
			{
				m_interactMode = MODE_MOVING;
				CRect rect = obj->GetRect();

				// We might need to use a denormalized
				// rect, as lines will not be normalized.
				BOOL useright = FALSE;
				BOOL usebottom = FALSE;
				if( rect.left > rect.right )
					useright = TRUE;
				if( rect.top > rect.bottom )
					usebottom = TRUE;

				VirtualToScreen( rect );

				int startx = rect.left;
				if( useright )
					startx = rect.right;
				int starty = rect.top;
				if( usebottom )
					starty = rect.bottom;

				// Anchor object
				m_multiSelObj = obj;

				// Offset from top-left corner
				// in the anchor object.
				m_deltaPoint = point - CPoint( startx, starty );

			}
		}

		if( m_interactMode == MODE_MOVING )
		{
			// We have clicked in one of many selected objects.
			// Set cursor and snapshot for Undo.
			::SetCursor( m_multiSelObj->GetCursor( DEHT_BODY ) );
			m_objs->Snapshot();
		}

	}

	if( m_objs && m_interactMode == MODE_DRAWING )
	{
		// The user wants to draw an object.
		// We add it to the data container and sets the appropriate mode

		if( OutsideRestraints( virtpoint ) )
		{
			m_interactMode = MODE_NONE;
			return;
		}

		m_objs->Snapshot();

		UnselectAll();

		CDiagramEntity* newobj = m_drawObj->Clone();

		// If snap-to-grid is on, we must
		// update the desired position
		if( m_snap )
		{

			virtpoint.x = SnapX( virtpoint.x );
			virtpoint.y = SnapY( virtpoint.y );

		}

		newobj->SetRect( CRect( virtpoint.x, virtpoint.y, virtpoint.x , virtpoint.y ) );
		newobj->Select( TRUE );

		// Add the object to the container
		AddObject( newobj );

		// Set modes
		m_interactMode = MODE_RESIZING;
		m_subMode = DEHT_BOTTOMRIGHT;

	}

	if( m_objs && m_interactMode == MODE_NONE )
	{

		// Clearing states
		// If Ctrl is not held down, we
		// clear all selections
		if( !( nFlags & MK_CONTROL ) )
			UnselectAll();

		count = GetObjectCount();
		BOOL goon = TRUE;

		// We check if we click on any object. If that is 
		// the case, we return on what part of the object 
		// we clicked.
		while( goon && ( obj = m_objs->GetAt( --count ) ) )
		{
			CRect rect = obj->GetRect();
			VirtualToScreen( rect );

			int hitCode = obj->GetHitCode( point, rect );
			if( hitCode != DEHT_NONE )
			{
				goon = FALSE;
				if( !( nFlags & MK_CONTROL ) )
				{
					// We unselect all again, as we might
					// have overlapping objects
					UnselectAll();
					obj->Select( TRUE );
				}
				else
				{
					// We toggle the selection of the 
					// object if Ctrl is pressed
					if( obj->IsSelected() )
						obj->Select( FALSE );
					else
						obj->Select( TRUE );
				}

				// We set the appropriate mode, either
				// moving or resizing depending on where
				// the click hit.
				if( hitCode == DEHT_BODY && !( nFlags & MK_CONTROL ) )
				{
					::SetCursor( obj->GetCursor( DEHT_BODY ) );
					m_interactMode = MODE_MOVING;

					CRect rect = obj->GetRect();

					// We might need to use a denormalized
					// rect, as lines will not be normalized.
					BOOL useright = FALSE;
					BOOL usebottom = FALSE;
					if( rect.left > rect.right )
						useright = TRUE;
					if( rect.top > rect.bottom )
						usebottom = TRUE;

					VirtualToScreen( rect );

					int startx = rect.left;
					if( useright )
						startx = rect.right;
					int starty = rect.top;
					if( usebottom )
						starty = rect.bottom;
					if( GetSelectCount() > 1 )
						m_multiSelObj = obj;
					m_deltaPoint = point - CPoint( startx, starty );

				}
				else if( !( nFlags & MK_CONTROL ) )
				{
					m_interactMode = MODE_RESIZING;
					m_subMode = hitCode;
				}
				else
					m_interactMode = MODE_NONE;
			}
		}

		// Save to undo-stack...
		if( m_interactMode != MODE_NONE )
			m_objs->Snapshot();

		// If no objects were selected, we assume either 
		// rubberbanding or background resize
		if( !IsAnyObjectSelected() )
		{
			if( m_bgResize )
			{
				// If we allow background resizing, we test if
				// the click was inside the resize zone.
				if( ( virtpoint.x >= GetVirtualSize().cx - m_bgResizeZone &&
					virtpoint.x <= GetVirtualSize().cx )
					||
					( virtpoint.y >= GetVirtualSize().cy - m_bgResizeZone && 
					virtpoint.y <= GetVirtualSize().cy )
					 )
				{
					m_bgResizeSelected = TRUE;
					int hitCode = GetHitCode( virtpoint );
					if( hitCode != DEHT_NONE && hitCode != DEHT_BODY )
					{
						// It was - set the appropriate mode
						m_interactMode = MODE_BGRESIZING;
						m_subMode = hitCode;
						m_objs->Snapshot();
					}

					RedrawWindow();
				}
			}

			if( m_interactMode == MODE_NONE )
			{
				// If nothing else is set, we assume
				// rubberbanding.
				m_selectionRect.left = m_selectionRect.right = point.x;
				m_selectionRect.top = m_selectionRect.bottom = point.y;
				m_interactMode = MODE_RUBBERBANDING;
			}
		}
	}

	// Cleaning up and redrawing as necessary.
	RemoveUnselectedPropertyDialogs();
	if( m_interactMode != MODE_NONE )
		RedrawWindow();

	CWnd::OnLButtonDown( nFlags, point );

}

void CDiagramEditor::OnMouseMove( UINT nFlags, CPoint point )
/* ============================================================
	Function :		CDiagramEditor::OnMouseMove
	Description :	Handles the "WM_MOUSEMOVE" message. We handle 
					moving or resizing of objects, or resizing 
					of the virtual screen.
	Access :		Protected

	Return :		void
	Parameters :	UINT nFlags		-	Key-down flags
					CPoint point	-	Mouse position
					
	Usage :			Called from MFC. Do not call from code.

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

	if( m_interactMode != MODE_NONE )
	{

		CClientDC	dc( this );
		CRect clientRect;
		GetClientRect( &clientRect );
		CPoint virtpoint( point );
		ScreenToVirtual( virtpoint );

		if( m_interactMode == MODE_BGRESIZING )
		{

			if( m_snap )
			{

				virtpoint.x = SnapX( virtpoint.x );
				virtpoint.y = SnapY( virtpoint.y );

			}

			virtpoint.x = max( virtpoint.x, 1 );
			virtpoint.y = max( virtpoint.y, 1 );
			int xsize = GetVirtualSize().cx;
			int ysize = GetVirtualSize().cy;

			if( m_subMode == DEHT_TOPRIGHT )
			{
				xsize = virtpoint.x;
			}
			else if( m_subMode == DEHT_RIGHTMIDDLE )
			{
				xsize = virtpoint.x;
			}
			else if( m_subMode == DEHT_BOTTOMRIGHT )
			{
				xsize = virtpoint.x;
				ysize = virtpoint.y;
			}
			else if( m_subMode == DEHT_BOTTOMMIDDLE )
			{
				ysize = virtpoint.y;
			}
			else if( m_subMode == DEHT_BOTTOMLEFT )
			{
				ysize = virtpoint.y;
			}

			if( m_restraint == RESTRAINT_VIRTUAL )
			{
				CSize size = GetContainingSize();
				xsize = max( size.cx, xsize );
				ysize = max( size.cy, ysize );
			}
			else if( m_restraint == RESTRAINT_MARGIN )
			{
				CSize size = GetContainingSize();
				xsize = max( size.cx + m_rightMargin, xsize );
				ysize = max( size.cy + m_bottomMargin, ysize );
				xsize = max( m_leftMargin + m_rightMargin, xsize );
				ysize = max( m_topMargin + m_bottomMargin, ysize );
			}

			SetVirtualSize( CSize( xsize, ysize ) );
			ScrollPoint( point );

		}
		if( m_interactMode == MODE_RESIZING )
		{

			if( m_snap )
			{

				virtpoint.x = SnapX( virtpoint.x );
				virtpoint.y = SnapY( virtpoint.y );

			}

			// If we are moving, we will update one or 
			// two sides of the object.
			double ypos = static_cast< double >( virtpoint.y );
			double xpos = static_cast< double >( virtpoint.x );

			CDiagramEntity* obj = GetSelectedObject();
			if( obj )
			{
				CSize minimum = obj->GetMinimumSize();
				CSize maximum = obj->GetMaximumSize();
				if( m_subMode == DEHT_BOTTOMMIDDLE )
				{

					// Constraints
					if( minimum.cy != -1 && ypos - obj->GetTop() < minimum.cy )
						ypos = obj->GetTop() + minimum.cy;
					if( maximum.cy != -1 && ypos - obj->GetTop() > maximum.cy )
						ypos = obj->GetTop() + maximum.cy;

					AdjustForRestraints( xpos, ypos );
					obj->SetRect( obj->GetLeft(), obj->GetTop(), obj->GetRight(), ypos );

				}
				else if( m_subMode == DEHT_TOPMIDDLE )
				{

					// Constraints
					if( minimum.cy != -1 && obj->GetBottom() - ypos < minimum.cy )
						ypos = obj->GetBottom() - minimum.cy;
					if( maximum.cy != -1 && obj->GetBottom() - ypos > maximum.cy )
						ypos = obj->GetBottom() - maximum.cy;

					AdjustForRestraints( xpos, ypos );
					obj->SetRect( obj->GetLeft(), ypos, obj->GetRight(), obj->GetBottom() );

				}
				else if( m_subMode == DEHT_LEFTMIDDLE )
				{

					// Constraints
					if( minimum.cx != -1 && obj->GetRight() - xpos < minimum.cx )
						xpos = obj->GetRight() - minimum.cx;
					if( maximum.cx != -1 && obj->GetRight() - xpos > maximum.cx )
						xpos = obj->GetRight() - maximum.cx;

					AdjustForRestraints( xpos, ypos );
					obj->SetRect( xpos, obj->GetTop(), obj->GetRight(), obj->GetBottom() );

				}
				else if( m_subMode == DEHT_RIGHTMIDDLE )
				{

					// Constraints
					if( minimum.cx != -1 && xpos - obj->GetLeft() < minimum.cx )
						xpos = obj->GetLeft() + minimum.cx;
					if( maximum.cx != -1 && xpos - obj->GetLeft() > maximum.cx )
						xpos = obj->GetLeft() + maximum.cx;

					AdjustForRestraints( xpos, ypos );
					obj->SetRect( obj->GetLeft(), obj->GetTop(), xpos, obj->GetBottom() );

				}
				else if( m_subMode == DEHT_TOPLEFT )
				{

					// Constraints
					if( minimum.cy != -1 && obj->GetBottom() - ypos < minimum.cy )
						ypos = obj->GetBottom() - minimum.cy;
					if( minimum.cx != -1 && obj->GetRight() - xpos < minimum.cx )
						xpos = obj->GetRight() - minimum.cx;
					if( maximum.cy != -1 && obj->GetBottom() - ypos > maximum.cy )
						ypos = obj->GetBottom() - maximum.cy;
					if( maximum.cx != -1 && obj->GetRight() - xpos > maximum.cx )
						xpos = obj->GetRight() - maximum.cx;

					AdjustForRestraints( xpos, ypos );
					obj->SetRect( xpos, ypos, obj->GetRight(), obj->GetBottom() );

				}
				else if( m_subMode == DEHT_TOPRIGHT )
				{

					// Constraints
					if( minimum.cy != -1 && obj->GetBottom() - ypos < minimum.cy )
						ypos = obj->GetBottom() - minimum.cy;
					if( minimum.cx != -1 && xpos - obj->GetLeft() < minimum.cx )
						xpos = obj->GetLeft() + minimum.cx;
					if( maximum.cy != -1 && obj->GetBottom() - ypos > maximum.cy )
						ypos = obj->GetBottom() - maximum.cy;
					if( maximum.cx != -1 && xpos - obj->GetLeft() > maximum.cx )
						xpos = obj->GetLeft() + maximum.cx;

					AdjustForRestraints( xpos, ypos );
					obj->SetRect( obj->GetLeft(), ypos, xpos, obj->GetBottom() );

				}
				else if( m_subMode == DEHT_BOTTOMLEFT )
				{

					// Constraints
					if( minimum.cy != -1 && ypos - obj->GetTop() < minimum.cy )
						ypos = obj->GetTop() + minimum.cy;
					if( minimum.cx != -1 && obj->GetRight() - xpos < minimum.cx )
						xpos = obj->GetRight() - minimum.cx;
					if( maximum.cy != -1 && ypos - obj->GetTop() > maximum.cy )
						ypos = obj->GetTop() + maximum.cy;
					if( maximum.cx != -1 && obj->GetRight() - xpos > maximum.cx )
						xpos = obj->GetRight() - maximum.cx;

					AdjustForRestraints( xpos, ypos );
					obj->SetRect( xpos, obj->GetTop(), obj->GetRight(), ypos );

				}
				else if( m_subMode == DEHT_BOTTOMRIGHT )
				{

					// Constraints
					if( minimum.cy != -1 && ypos - obj->GetTop() < minimum.cy )
						ypos = obj->GetTop() + minimum.cy;
					if( minimum.cx != -1 && xpos - obj->GetLeft() < minimum.cx )
						xpos = obj->GetLeft() + minimum.cx;
					if( maximum.cy != -1 && ypos - obj->GetTop() > maximum.cy )
						ypos = obj->GetTop() + maximum.cy;
					if( maximum.cx != -1 && xpos - obj->GetLeft() > maximum.cx )
						xpos = obj->GetLeft() + maximum.cx;

					AdjustForRestraints( xpos, ypos );
					obj->SetRect( obj->GetLeft(), obj->GetTop(), xpos, ypos );

				}

			}

			// Scroll if we are outside any edge
			ScrollPoint( point );
			RedrawWindow();
		}
		else if( m_objs && m_interactMode == MODE_MOVING )
		{

			// If we move, we just update the positions
			// of all the objects.
			double offsetx = round( static_cast< double >( m_deltaPoint.x ) / GetZoom() );
			double offsety = round( static_cast< double >( m_deltaPoint.y ) / GetZoom() );
			int count = 0;
			CDiagramEntity* obj;
			double width;
			double height;

			double left;
			double top;

			if( m_multiSelObj )
			{
				left = virtpoint.x - offsetx;
				top = virtpoint.y - offsety;
				if( m_snap )
				{
					left = SnapX(  static_cast< int >( left ) );
					top = SnapY( static_cast< int >( top ) );
				}

				offsetx = left - m_multiSelObj->GetLeft();
				offsety = top - m_multiSelObj->GetTop();

				InsideRestraints( offsetx, offsety );

				m_multiSelObj->SetRect( m_multiSelObj->GetLeft() + offsetx, m_multiSelObj->GetTop() + offsety, m_multiSelObj->GetRight() + offsetx, m_multiSelObj->GetBottom() + offsety );

				while( ( obj = m_objs->GetAt( count++ ) ) )
					if( obj->IsSelected() && obj != m_multiSelObj )
						obj->MoveRect( offsetx, offsety );
			}
			else
			{

				obj = GetSelectedObject();
				if( obj )
				{

					width = obj->GetRight() - obj->GetLeft();
					height = obj->GetBottom() - obj->GetTop();

					left = virtpoint.x - offsetx;
					top = virtpoint.y - offsety;

					if( m_snap )
					{
						left = SnapX( static_cast< int >( left ) );
						top = SnapY( static_cast< int >( top ) );
					}

					double right = left + width;
					double bottom = top + height;

					AdjustForRestraints( left, top, right, bottom );
					obj->SetRect( left, top, right , bottom );

				}
			}

			// Scroll if we are outside any edge
			CPoint outside = ScrollPoint( point );
			RedrawWindow();

		}
		else if( m_interactMode == MODE_RUBBERBANDING )
		{

			// We are selecting objects
			CRect rect( m_selectionRect );

			// Erase old selection rect
			if( m_selectionRect.left != m_selectionRect.right || m_selectionRect.top != m_selectionRect.bottom )
			{
				rect.NormalizeRect();
				dc.DrawFocusRect( rect );
			}

			// Scroll if we are outside any edge
			CPoint outside = ScrollPoint( point );
			m_selectionRect.OffsetRect( -outside.x, -outside.y );

			// Update and draw the selection rect
			m_selectionRect.right = point.x;
			m_selectionRect.bottom = point.y;
			rect = m_selectionRect;
			rect.NormalizeRect();

			dc.DrawFocusRect( rect );

		}

	}

	CWnd::OnMouseMove( nFlags, point );

}

void CDiagramEditor::OnLButtonUp( UINT nFlags, CPoint point )
/* ============================================================
	Function :		CDiagramEditor::OnLButtonUp
	Description :	Handles the "WM_LBUTTONUP" message. Mainly, 
					we snap to grid if appropriate.
	Access :		Protected

	Return :		void
	Parameters :	UINT nFlags		-	not interested.
					CPoint point	-	The position of the mouse.
					
	Usage :			Called from MFC. Do not call from code.

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

	// Reset modes
	ReleaseCapture();

	int count = 0;
	CDiagramEntity* obj;

	if( m_objs && m_interactMode == MODE_MOVING )
	{

		// We set all the object rectangles to trigger
		// the virtual MoveObject function, thus 
		// allowing derived editors to know that 
		// objects has been moved.
		while( ( obj = m_objs->GetAt( count++ ) ) )
		{
			if( obj->IsSelected() )
			{
				CRect rect = obj->GetRect();
				if( m_snap )
				{
					// If we move objects, and snap to grid is on
					// we snap here
					int height = rect.Height();
					int width = rect.Width();

					// Find closest snap-points
					rect.left = SnapX( rect.left );
					rect.top = SnapY( rect.top );

					rect.right = rect.left + width;
					rect.bottom = rect.top + height;
				}

				MoveObject( obj, rect );
			}
		}

		m_objs->SetModified( TRUE );

	}
	else if( m_objs && m_interactMode == MODE_RESIZING )
	{
		// If we resize objects, and snap to grid is on
		// we snap here

		if( m_snap )
		{
			while( ( obj = m_objs->GetAt( count++ ) ) )
			{
				if( obj->IsSelected() )
				{
					CRect rect = obj->GetRect();
					CRect newrect = rect;

					// Find closest snap-points
					rect.left = SnapX( rect.left );
					rect.top = SnapY( rect.top );
					rect.right = SnapX( rect.right );
					rect.bottom = SnapY ( rect.bottom );

					// Updating rect depending on resize-type.
					switch( m_subMode )
					{
						case DEHT_BOTTOMMIDDLE:
							newrect.bottom = rect.bottom;
						break;
						case DEHT_TOPMIDDLE:
							newrect.top = rect.top;
						break;
						case DEHT_LEFTMIDDLE:
							newrect.left = rect.left;
						break;
						case DEHT_RIGHTMIDDLE:
							newrect.right = rect.right;
						break;
						case DEHT_TOPLEFT:
							newrect.top = rect.top;
							newrect.left = rect.left;
						break;
						case DEHT_TOPRIGHT:
							newrect.top = rect.top;
							newrect.right = rect.right;
						break;
						case DEHT_BOTTOMLEFT:
							newrect.bottom = rect.bottom;
							newrect.left = rect.left;
						break;
						case DEHT_BOTTOMRIGHT:
							newrect.bottom = rect.bottom;
							newrect.right = rect.right;
						break;
					}

					obj->SetRect( newrect );
				}
			}
		}
		m_objs->SetModified( TRUE );

	}
	else if( m_objs && m_interactMode == MODE_RUBBERBANDING )
	{

		// Remove all former selections
		UnselectAll();

		// We convert the selection rect to virtual coordinates, 
		// and make sure that the rect contains at least some 
		// width and height ( IntersectRect will not work otherwise )
		CRect rect( m_selectionRect );
		ScreenToVirtual( rect );
		if( rect.TopLeft() == rect.BottomRight() )
			rect.InflateRect( 1, 1 );

		// We loop all objects, checking if we got any overlap.
		count = 0;
		while( ( obj = m_objs->GetAt( count++ ) ) )
		{
			if( obj->BodyInRect( rect ) )
				obj->Select( TRUE );
		}

	}

	// Redraw and reset modes
	RedrawWindow();
	if( m_multiDraw && m_drawing && m_drawObj )
		m_interactMode = MODE_DRAWING;
	else
	{
		m_drawing = FALSE;
		m_interactMode = MODE_NONE;
	}

	m_subMode = DEHT_NONE;
	CWnd::OnLButtonUp( nFlags, point );

}

void CDiagramEditor::OnLButtonDblClk( UINT nFlags, CPoint point ) 
/* ============================================================
	Function :		CDiagramEditor::OnLButtonDblClk
	Description :	Handles the "WM_LBUTTONDBLCLK" message. We 
					show a property dialog if appropriate.
	Access :		Protected

	Return :		void
	Parameters :	UINT nFlags		-	not interested
					CPoint point	-	not interested
					
	Usage :			Called from MFC. Do not call from code.

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

	ShowProperties();
	CWnd::OnLButtonDblClk( nFlags, point );

}

void CDiagramEditor::OnRButtonDown( UINT nFlags, CPoint point ) 
/* ============================================================
	Function :		CDiagramEditor::OnRButtonDown
	Description :	Handles the "WM_RBUTTONDOWN" message. 
	Access :		Protected

	Return :		void
	Parameters :	UINT nFlags		-	not interested
					CPoint point	-	not interested
					
	Usage :			Called from MFC. Do not call from code.

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

	SetPanning( FALSE );

	if( m_objs )
	{
		int count = 0;
		CDiagramEntity* obj;
		CPoint virtpoint( point );
		ScreenToVirtual( virtpoint );

		// We check if we click on any object body. If that is 
		// the case, we select the object
		while( ( obj = m_objs->GetAt( count++ ) ) )
		{
			if( obj->GetHitCode( virtpoint ) == DEHT_BODY )
			{
				if( !obj->IsSelected() )
				{
					UnselectAll();
					obj->Select( TRUE );
				}
			}
		}

		RedrawWindow();
	}

	m_interactMode = MODE_NONE;
	m_drawing = FALSE;

	CWnd::OnRButtonDown( nFlags, point );

}

void CDiagramEditor::OnRButtonUp( UINT nFlags, CPoint point ) 
/* ============================================================
	Function :		CDiagramEditor::OnRButtonUp
	Description :	Handles the "WM_RBUTTONUP" message. We show 
					popup menues as appropriate.
	Access :		Protected

	Return :		void
	Parameters :	UINT nFlags		-	not interested
					CPoint point	-	not interested
					
	Usage :			Called from MFC. Do not call from code.

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

	CPoint screen( point );
	CPoint virtpoint( point );

	ClientToScreen( &screen );
	ScreenToVirtual( virtpoint );

	if( GetSelectCount() == 1 )
	{
		CDiagramEntity* obj = GetSelectedObject();
		if( obj->GetHitCode( virtpoint ) == DEHT_BODY )
			obj->ShowPopup( screen, this );
		else
			ShowPopup( screen );
	}
	else
		ShowPopup( screen );

	CWnd::OnRButtonUp( nFlags, point );

}

UINT CDiagramEditor::OnGetDlgCode() 
/* ============================================================
	Function :		CDiagramEditor::OnGetDlgCode
	Description :	Handles the "WM_GETDLGCODE" message. We 
					return the keys we want.
	Access :		Protected

	Return :		UINT	-	"DLGC_WANTALLKEYS", we want all 
								keys.
	Parameters :	none

	Usage :			Called from MFC. Do not call from code.

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

	return DLGC_WANTALLKEYS;

}

void CDiagramEditor::OnKeyDown( UINT nChar, UINT nRepCnt, UINT nFlags ) 
/* ============================================================
	Function :		CDiagramEditor::OnKeyDown
	Description :	Handles the "WM_KEYDOWN" message. We provide 
					a simple keyboard interface.
	Access :		Protected

	Return :		void
	Parameters :	UINT nChar		-	Character pressed
					UINT nRepCnt	-	not interested
					UINT nFlags		-	not interested
					
	Usage :			Called from MFC. Do not call from code.

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

	SetPanning( FALSE );

	if( m_keyInterface != KEY_NONE )
	{
		// Clearing the flags
		GetAsyncKeyState( VK_SHIFT );
		GetAsyncKeyState( VK_CONTROL );

		if( m_objs )
		{

			int count = 0;
			CDiagramEntity* obj;

			double offsetstepx = 1.0 / GetZoom();
			double offsetstepy = 1.0 / GetZoom();
			if( m_snap )
			{
				offsetstepx = static_cast< double >( m_gridSize.cx )/ GetZoom();
				offsetstepy = static_cast< double >( m_gridSize.cy )/ GetZoom();
			}

			offsetstepx = max( offsetstepx, 1 );
			offsetstepy = max( offsetstepy, 1 );

			double offsetx = 0.0;
			double offsety = 0.0;

			BOOL move = FALSE;
			BOOL resize = FALSE;
			BOOL redraw = TRUE;
			
			if( ( m_keyInterface & KEY_ARROW ) && ( nChar == VK_DOWN ) )
			{
				offsety=offsetstepy;
				if( GetAsyncKeyState( VK_SHIFT ) )
					resize = TRUE;
				else
					move = TRUE;
			}
			else if( ( m_keyInterface & KEY_ARROW ) && ( nChar == VK_UP ) )
			{
				offsety=-offsetstepy;
				if( GetAsyncKeyState( VK_SHIFT ) )
					resize = TRUE;
				else
					move = TRUE;
			}
			else if( ( m_keyInterface & KEY_ARROW ) && ( nChar == VK_LEFT ) )
			{
				offsetx=-offsetstepx;
				if( GetAsyncKeyState( VK_SHIFT ) )
					resize = TRUE;
				else
					move = TRUE;
			}
			else if( ( m_keyInterface & KEY_ARROW ) && ( nChar == VK_RIGHT ) )
			{
				offsetx=offsetstepx;
				if( GetAsyncKeyState( VK_SHIFT ) )
					resize = TRUE;
				else
					move = TRUE;
			}
			else if( ( m_keyInterface & KEY_DELETE ) && ( nChar == VK_DELETE ) )
			{
				if( GetAsyncKeyState( VK_SHIFT ) )
					Cut();
				else
					DeleteAllSelected();
			}
			else if( ( m_keyInterface & KEY_INSERT ) && ( nChar == VK_INSERT ) )
			{
				if( GetAsyncKeyState( VK_SHIFT ) )
					Paste();
				else
					Duplicate();
			}
			else if( ( m_keyInterface & KEY_CTRL ) && nChar == VK_RETURN && GetAsyncKeyState( VK_CONTROL ) )
				ShowProperties();
			else if( ( m_keyInterface & KEY_CTRL ) && nChar == _TCHAR( 'A' ) && GetAsyncKeyState( VK_CONTROL ) )
				SelectAll();
			else if( ( m_keyInterface & KEY_CTRL ) && nChar == _TCHAR( 'X' ) && GetAsyncKeyState( VK_CONTROL ) )
				Cut();
			else if( ( m_keyInterface & KEY_CTRL ) && nChar == _TCHAR( 'V' ) && GetAsyncKeyState( VK_CONTROL ) )
				Paste();
			else if( ( m_keyInterface & KEY_CTRL ) && nChar == _TCHAR( 'C' ) && GetAsyncKeyState( VK_CONTROL ) )
				Copy();
			else if( ( m_keyInterface & KEY_CTRL ) && nChar == _TCHAR( 'Z' ) && GetAsyncKeyState( VK_CONTROL ) )
				Undo();
			else if( ( m_keyInterface & KEY_PGUPDOWN ) && nChar == VK_NEXT && GetAsyncKeyState( VK_CONTROL ) )
				Bottom();
			else if( ( m_keyInterface & KEY_PGUPDOWN ) && nChar == VK_PRIOR && GetAsyncKeyState( VK_CONTROL ) )
				Front();
			else if( ( m_keyInterface & KEY_PGUPDOWN ) && nChar == VK_NEXT)
				Down();
			else if( ( m_keyInterface & KEY_PGUPDOWN ) && nChar == VK_PRIOR)
				Up();

			if( move )
			{
				InsideRestraints( offsetx, offsety );
				m_objs->Snapshot();
				while( ( obj = m_objs->GetAt( count++ ) ) )
				{
					if( obj->IsSelected() )
					{
						double width = obj->GetRight() - obj->GetLeft();
						double height = obj->GetBottom() - obj->GetTop();

						double left = obj->GetLeft() + offsetx;
						double top = obj->GetTop() + offsety;

						if( m_snap )
						{
							left = SnapX( static_cast< int >( left ) );
							top = SnapY( static_cast< int >( top ) );
						}

						double right = left + width;
						double bottom = top + height;

						AdjustForRestraints( left, top, right, bottom );
						MoveObject( obj, CRect( round( left ), round( top ), round( right ), round( bottom ) ) );
						redraw = TRUE;
						m_objs->SetModified( TRUE );
					}
				}
			}

			if( resize )
			{
				m_objs->Snapshot();
				InsideRestraints( offsetx, offsety );
				while( ( obj = m_objs->GetAt( count++ ) ) )
				{
					if( obj->IsSelected() )
					{

						double right = obj->GetRight() + offsetx;
						double bottom = obj->GetBottom() + offsety;
						if( m_snap )
						{
							right = SnapX( static_cast< int >( right ) );
							bottom = SnapY( static_cast< int >( bottom ) );
						}

						obj->SetRect( obj->GetLeft(), obj->GetTop(), right, bottom );

						m_objs->SetModified( TRUE );
						redraw = TRUE;
					}
				}
			}

			if( redraw )
				RedrawWindow();
		}

		if( ( m_keyInterface & KEY_ESCAPE ) && ( nChar == VK_ESCAPE ) )
		{
			m_interactMode = MODE_NONE;
			m_drawing = FALSE;
		}

		// Keys independent of any data
		if( ( m_keyInterface & KEY_PLUSMINUS ) && ( nChar == VK_ADD ) )
			SetZoom( GetZoom() + GetZoomFactor() );

		if( ( m_keyInterface & KEY_PLUSMINUS ) && ( nChar == VK_SUBTRACT ) )
			SetZoom( GetZoom() - GetZoomFactor() );
	}

	CWnd::OnKeyDown( nChar, nRepCnt, nFlags );

}

BOOL CDiagramEditor::OnSetCursor( CWnd* pWnd, UINT nHitTest, UINT message ) 
/* ============================================================
	Function :		CDiagramEditor::OnSetCursor
	Description :	Handles the "WM_SETCURSOR" message. We set 
					cursors as appropriate.
	Access :		Protected

	Return :		BOOL			-	not interested
	Parameters :	CWnd* pWnd		-	not interested
					UINT nHitTest	-	not interested
					UINT message	-	not interested
					
	Usage :			Called from MFC. Do not call from code.

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

	BOOL res = TRUE;
	HCURSOR cursor = GetCursor();
	if( cursor )
		::SetCursor( cursor );
	else
		res = CWnd::OnSetCursor( pWnd, nHitTest, message );

	return res;

}

HCURSOR CDiagramEditor::GetCursor( int hit ) const
/* ============================================================
	Function :		CDiagramEditor::GetCursor
	Description :	Returns a "HCURSOR" for the hit-point value 
					(defined in DiagramEntity.h) 
	Access :		Public

	Return :		HCURSOR	-	The cursor to show, "NULL" if 
								default.
	Parameters :	int hit	-	The hit-point value
					
	Usage :			Virtual. Can be overridden in a derived
					class to display other resize cursors. 
					"hit" can be one of the following:
						"DEHT_NONE" No hit-point
						"DEHT_BODY" Inside object body
						"DEHT_TOPLEFT" Top-left corner
						"DEHT_TOPMIDDLE" Middle top-side
						"DEHT_TOPRIGHT" Top-right corner
						"DEHT_BOTTOMLEFT" Bottom-left corner
						"DEHT_BOTTOMMIDDLE" Middle bottom-side
						"DEHT_BOTTOMRIGHT" Bottom-right corner
						"DEHT_LEFTMIDDLE" Middle left-side
						"DEHT_RIGHTMIDDLE" Middle right-side

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

	// We only show cursors for a subset of the hit-point codes.
	HCURSOR cursor = LoadCursor( NULL, IDC_ARROW );
	switch( hit )
	{
		case DEHT_TOPRIGHT:
			cursor = LoadCursor( NULL, IDC_SIZENESW );
		break;
		case DEHT_BOTTOMMIDDLE:
			cursor = LoadCursor( NULL, IDC_SIZENS );
		break;
		case DEHT_BOTTOMRIGHT:
			cursor = LoadCursor( NULL, IDC_SIZENWSE );
		break;
		case DEHT_BOTTOMLEFT:
			cursor = LoadCursor( NULL, IDC_SIZENESW );
		break;
		case DEHT_RIGHTMIDDLE:
			cursor = LoadCursor( NULL, IDC_SIZEWE );
		break;
	}

	return cursor;

}

int CDiagramEditor::GetHitCode( CPoint point )
/* ============================================================
	Function :		CDiagramEditor::GetHitCode
	Description :	Will return the hit-point value (defined in 
					DiagramEntity.h) of point.
	Access :		Public

	Return :		int				-	The hit-point define of 
										point. "DEHT_NONE" if none.
	Parameters :	CPoint point	-	The point to hit-test.
					
	Usage :			Virtual. Can be overridden in a derived 
					class to return other hit-point values. 
					Should normally not be called from code.
					The hit point can be one of the following:
						"DEHT_NONE" No hit-point
						"DEHT_BODY" Inside object body
						"DEHT_TOPLEFT" Top-left corner
						"DEHT_TOPMIDDLE" Middle top-side
						"DEHT_TOPRIGHT" Top-right corner
						"DEHT_BOTTOMLEFT" Bottom-left corner
						"DEHT_BOTTOMMIDDLE" Middle bottom-side
						"DEHT_BOTTOMRIGHT" Bottom-right corner
						"DEHT_LEFTMIDDLE" Middle left-side
						"DEHT_RIGHTMIDDLE" Middle right-side

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

	CRect rect( 0, 0, GetVirtualSize().cx, GetVirtualSize().cy );

	int result = DEHT_NONE;

	if( rect.PtInRect( point ) )
		result = DEHT_BODY;

	CRect rectTest;

	// We return all selection marker points for aestethical 
	// reasons, even if we can't resize the background to 
	// the top and left.
	VirtualToScreen( point );
	rectTest = GetSelectionMarkerRect( DEHT_TOPLEFT );
	if( rectTest.PtInRect( point ) )
		result = DEHT_TOPLEFT;

	rectTest = GetSelectionMarkerRect( DEHT_TOPMIDDLE );
	if( rectTest.PtInRect( point ) )
		result = DEHT_TOPMIDDLE;

	rectTest = GetSelectionMarkerRect( DEHT_TOPRIGHT );
	if( rectTest.PtInRect( point ) )
		result = DEHT_TOPRIGHT;

	rectTest = GetSelectionMarkerRect( DEHT_BOTTOMLEFT );
	if( rectTest.PtInRect( point ) )
		result = DEHT_BOTTOMLEFT;

	rectTest = GetSelectionMarkerRect( DEHT_BOTTOMMIDDLE );
	if( rectTest.PtInRect( point ) )
		result = DEHT_BOTTOMMIDDLE;

	rectTest = GetSelectionMarkerRect( DEHT_BOTTOMRIGHT );
	if( rectTest.PtInRect( point ) )
		result = DEHT_BOTTOMRIGHT;

	rectTest = GetSelectionMarkerRect( DEHT_LEFTMIDDLE );
	if( rectTest.PtInRect( point ) )
		result = DEHT_LEFTMIDDLE;

	rectTest = GetSelectionMarkerRect( DEHT_RIGHTMIDDLE );
	if( rectTest.PtInRect( point ) )
		result = DEHT_RIGHTMIDDLE;

	return result;

}

void CDiagramEditor::InsideRestraints( double& x, double& y )
/* ============================================================
	Function :		CDiagramEditor::InsideRestraints
	Description :	Returns the max x and y that the selected 
					objects can move, with the desired x and y 
					as in-parameters.
	Access :		Protected

	Return :		void
	Parameters :	double& x	-	Desired x-movement
					double& y	-	Desired y-movement.
					
	Usage :			Internal function. Will not actually move 
					the objects, only calculate the maximum 
					possible movement.

   ============================================================*/
{
	if( m_restraint != RESTRAINT_NONE )
	{

		int count = 0;
		CDiagramEntity* obj;
		double minleft = 0xffffffff;
		double mintop = 0xffffffff;
		double maxright = 0;
		double maxbottom = 0;

		while( ( obj = m_objs->GetAt( count++ ) ) )
		{
			if( obj->IsSelected() )
			{

				// Correcting, depending on restraint mode.
				// Note that checks will have to be made for all 
				// coordinates against all sides, as the coordinates 
				// might not be normalized (as for a line, for example).

				double left = obj->GetLeft() + x;
				double top = obj->GetTop() + y;
				double right = obj->GetRight() + x;
				double bottom = obj->GetBottom() + y;

				minleft = min( minleft, left );
				minleft = min( minleft, right );
				mintop = min( mintop, top );
				mintop = min( mintop, bottom );

				maxright = max( maxright, left );
				maxright = max( maxright, right );
				maxbottom = max( maxbottom, top );
				maxbottom = max( maxbottom, bottom );

			}
		}

		double leftedge = 0;
		double rightedge = 0;
		double topedge = 0;
		double bottomedge = 0;

		if( m_restraint == RESTRAINT_VIRTUAL )
		{
			leftedge = 0;
			topedge = 0;
			rightedge = GetVirtualSize().cx;
			bottomedge = GetVirtualSize().cy;
		}
		else if( m_restraint == RESTRAINT_MARGIN )
		{
			leftedge = m_leftMargin;
			topedge = m_topMargin;
			rightedge = GetVirtualSize().cx - m_rightMargin;
			bottomedge = GetVirtualSize().cy - m_bottomMargin;
		}

		if( minleft < leftedge )
			x = x - ( minleft - leftedge );
		if( mintop < topedge )
			y = y - ( mintop - topedge );
		if( maxright > rightedge )
			x = rightedge - ( maxright - x );
		if( maxbottom > bottomedge )
			y = bottomedge - ( maxbottom - y );

	}
}

void CDiagramEditor::AdjustForRestraints( double& left, double& top, double& right, double& bottom )
/* ============================================================
	Function :		CDiagramEditor::AdjustForRestraints
	Description :	Adjust the rect represented by the in-
					params to the current constraints.
	Access :		Protected

	Return :		void
	Parameters :	double& left	-	Desired left position. 
										Contains the corrected 
										left position on return.
					double& top		-	Desired top position.
										Contains the corrected 
										top position on return.
					double& right	-	Desired right position.
										Contains the corrected 
										right position on return.
					double& bottom	-	Desired bottom position.
										Contains the corrected 
										bottom position on return.
					
	Usage :			Internal function. Will not resize the rect. 
					The coordinates need not be normalized.

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

	// Saving the size
	double width = fabs( right - left );
	double height = fabs( bottom - top );

	// Correcting, depending on restraint mode.
	// Note that checks will have to be made for all 
	// coordinates against all sides, as the coordinates 
	// might not be normalized (as for a line, for example).
	if( m_restraint == RESTRAINT_VIRTUAL )
	{

		if( left < 0 )
		{
			left = 0;
			right = left + width;
		}
		if( top < 0 )
		{
			top = 0;
			bottom = top + height;
		}
		if( right < 0 )
		{
			right = 0;
			left = right + width;
		}
		if( bottom < 0 )
		{
			bottom = 0;
			top = bottom + height;
		}

		if( right > GetVirtualSize().cx )
		{
			right = GetVirtualSize().cx;
			left = right - width;
		}
		if( bottom > GetVirtualSize().cy )
		{
			bottom = GetVirtualSize().cy;
			top = bottom - height;
		}

		if( left > GetVirtualSize().cx )
		{
			left = GetVirtualSize().cx;
			right = left - width;
		}
		if( top > GetVirtualSize().cy )
		{
			top = GetVirtualSize().cy;
			bottom = top - height;
		}
	}
	else if( m_restraint == RESTRAINT_MARGIN )
	{
		if( left < m_leftMargin )
		{
			left = m_leftMargin;
			right = left + width;
		}
		if( top < m_topMargin )
		{
			top = m_topMargin;
			bottom = top + height;
		}
		if( right < m_leftMargin )
		{
			right = m_leftMargin;
			left = right + width;
		}
		if( bottom < m_topMargin )
		{
			bottom = m_topMargin;
			top = bottom + height;
		}

		if( right > GetVirtualSize().cx - m_rightMargin )
		{
			right = ( GetVirtualSize().cx - m_rightMargin );
			left = right - width;
		}
		if( bottom > GetVirtualSize().cy - m_bottomMargin )
		{
			bottom = ( GetVirtualSize().cy - m_bottomMargin );
			top = bottom - height;
		}
		if( left > GetVirtualSize().cx - m_rightMargin )
		{
			left = ( GetVirtualSize().cx - m_rightMargin );
			right = left - width;
		}
		if( top > GetVirtualSize().cy - m_bottomMargin )
		{
			top = ( GetVirtualSize().cy - m_bottomMargin );
			bottom = top - height;
		}
	}

}

void CDiagramEditor::AdjustForRestraints( double& xpos, double& ypos )
/* ============================================================
	Function :		CDiagramEditor::AdjustForRestraints
	Description :	Adjust the rect represented by the in-
					params to the current constraints.
	Access :		Protected

	Return :		void
	Parameters :	double& xpos	-	Desired x position. 
										Contains the corrected 
										x position on return.
					double& ypos	-	Desired y position. 
										Contains the corrected 
										y position on return.
					
	Usage :			Internal function. Will only correct 
					position parameters, not change any object.

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

	if( m_restraint == RESTRAINT_VIRTUAL )
	{
		xpos = max( xpos, 0 );
		xpos = min( xpos, GetVirtualSize().cx );
		ypos = max( ypos, 0 );
		ypos = min( ypos, GetVirtualSize().cy );
	}
	else if( m_restraint == RESTRAINT_MARGIN )
	{
		xpos = max( xpos, m_leftMargin );
		xpos = min( xpos, GetVirtualSize().cx - m_rightMargin );
		ypos = max( ypos, m_topMargin );
		ypos = min( ypos, GetVirtualSize().cy - m_bottomMargin );
	}

}

CSize CDiagramEditor::GetContainingSize() const
/* ============================================================
	Function :		CDiagramEditor::GetContainingSize
	Description :	Return the minimum size enveloping all 
					objects.
	Access :		Protected

	Return :		CSize	-	Minimum size necessary to 
								contain all objects.
	Parameters :	none

	Usage :			Internal function. Calculates the minimum
					size necessary for all objects.

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

	int count = 0;
	CDiagramEntity* obj;
	int maxx = 0;
	int maxy = 0;

	if( m_objs )
	{
		while( ( obj = m_objs->GetAt( count++ ) ) )
		{
			maxx = max( round( obj->GetRight() ), maxx );
			maxy = max( round( obj->GetBottom() ), maxy );
		}
	}

	return CSize( maxx, maxy );

}

/////////////////////////////////////////////////////////////////////////////
// CDiagramEditor scrolling

void CDiagramEditor::OnHScroll( UINT nSBCode, UINT nPos, CScrollBar* pScrollBar ) 
/* ============================================================
	Function :		CDiagramEditor::OnHScroll
	Description :	Handles the "WM_VSCROLL" message. Updates the 
					screen.
	Access :		Protected

	Return :		void
	Parameters :	UINT nSBCode			-	Type of scroll operation
					UINT nPos				-	New scroll position
					CScrollBar* pScrollBar	-	
					
	Usage :			Called from MFC. Do not call from code.

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

	SCROLLINFO si;
	si.cbSize = sizeof( SCROLLINFO );
	si.fMask = SIF_POS | SIF_RANGE;
	GetScrollInfo( SB_HORZ, &si );
	switch( nSBCode )
	{
		case SB_LEFT:			// Scroll to far left.
			si.nPos = si.nMin;
			break;
		case SB_LINELEFT:		// Scroll left.
			si.nPos = max( si.nPos - 1, si.nMin );
			break;
		case SB_LINERIGHT:		// Scroll right.
			si.nPos = min( si.nPos + 1, si.nMax );
			break;
		case SB_PAGELEFT:		// Scroll one page left.
			si.nPos = max( si.nPos - static_cast< int >( si.nPage ), si.nMin );
			break;
		case SB_PAGERIGHT:		// Scroll one page right.
			si.nPos = min( si.nPos + static_cast< int >( si.nPage ), si.nMax );
			break;
		case SB_RIGHT:			// Scroll to far right.
			si.nPos = si.nMax;
			break;
		case SB_THUMBPOSITION:	// Scroll to absolute position. The current position is specified by the nPos parameter.
			si.nPos = nPos;
			break;
		case SB_THUMBTRACK:		//
			si.nPos = nPos;
			break;
	}

	SetHScroll( si.nPos );
//	SetScrollInfo( SB_HORZ, &si );
	RedrawWindow();
	CWnd::OnHScroll( nSBCode, nPos, pScrollBar );

}

void CDiagramEditor::OnVScroll( UINT nSBCode, UINT nPos, CScrollBar* pScrollBar ) 
/* ============================================================
	Function :		CDiagramEditor::OnVScroll
	Description :	Handles the "WM_VSCROLL" message. Updates the 
					screen.
	Access :		Protected

	Return :		void
	Parameters :	UINT nSBCode			-	Type of scroll 
												operation.
					UINT nPos				-	New scroll position.
					CScrollBar* pScrollBar	-	
					
	Usage :			Called from MFC. Do not call from code.

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

	SCROLLINFO si;
	si.cbSize = sizeof( SCROLLINFO );
	si.fMask = SIF_POS | SIF_RANGE;
	GetScrollInfo( SB_VERT, &si );
	switch( nSBCode )
	{
		case SB_TOP:			// Scroll to far left.
			si.nPos = si.nMin;
			break;
		case SB_LINEUP:			// Scroll left.
			si.nPos = max( si.nPos - 1, si.nMin );
			break;
		case SB_LINEDOWN:		// Scroll right.
			si.nPos = min( si.nPos + 1, si.nMax );
			break;
		case SB_PAGEUP:			// Scroll one page left.
			si.nPos = max( si.nPos - static_cast< int >( si.nPage ), si.nMin );
			break;
		case SB_PAGEDOWN:		// Scroll one page right.
			si.nPos = min( si.nPos + static_cast< int >( si.nPage ), si.nMax );
			break;
		case SB_BOTTOM:			// Scroll to far right.
			si.nPos = si.nMax;
			break;
		case SB_THUMBPOSITION:	// Scroll to absolute position. The current position is specified by the nPos parameter.
			si.nPos = nPos;
			break;
		case SB_THUMBTRACK:		//
			si.nPos = nPos;
			break;
	}

	SetVScroll( si.nPos );

//	SetScrollInfo( SB_VERT, &si );
	RedrawWindow();

	CWnd::OnVScroll( nSBCode, nPos, pScrollBar );

}

int CDiagramEditor::HScroll( int scroll )
/* ============================================================
	Function :		CDiagramEditor::HScroll
	Description :	Scrolls the editor window scroll steps
					horisontally.
	Access :		Protected

	Return :		int			-	The number of steps actually 
									scrolled.
	Parameters :	int scroll	-	The number of steps to scroll.
					
	Usage :			Internal function

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

	int retval = 0;

	if( m_hWnd )
	{
		CRect clientRect;
		GetClientRect( &clientRect );
		if(clientRect.Size().cx < GetVirtualSize().cx )	
		{
			int desiredpos = 0;

			SCROLLINFO si;
			si.cbSize = sizeof( SCROLLINFO );
			if( GetScrollInfo( SB_HORZ, &si ) )
			{
				desiredpos = si.nPos + scroll;
				si.nPos = desiredpos;

				SetHScroll( si.nPos );
//				SetScrollInfo( SB_HORZ, &si );
				GetScrollInfo( SB_HORZ, &si );
				retval = scroll - ( desiredpos - si.nPos );
				if( retval )
					RedrawWindow();
			}
		}
	}

	return retval;

}

int CDiagramEditor::VScroll( int scroll )
/* ============================================================
	Function :		CDiagramEditor::VScroll
	Description :	Scrolls the editor window scroll steps
					vertically.
	Access :		Protected

	Return :		int			-	The number of steps actually 
									scrolled.
	Parameters :	int scroll	-	The number of steps to scroll.
					
	Usage :			Internal function.

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

	int retval = 0;

	if( m_hWnd )
	{
		CRect clientRect;
		GetClientRect( &clientRect );
		if(clientRect.Size().cy < GetVirtualSize().cy )
		{
			int desiredpos = 0;

			SCROLLINFO si;
			si.cbSize = sizeof( SCROLLINFO );
			if( GetScrollInfo( SB_VERT, &si ) )
			{
				desiredpos = si.nPos + scroll;
				si.nPos = desiredpos;

				SetVScroll( si.nPos );
//				SetScrollInfo( SB_VERT, &si );
				GetScrollInfo( SB_VERT, &si );
				retval = scroll - ( desiredpos - si.nPos );
				if( retval )
					RedrawWindow();
			}
		}
	}

	return retval;

}

CPoint CDiagramEditor::ScrollPoint( CPoint point )
/* ============================================================
	Function :		CDiagramEditor::ScrollPoint
	Description :	Scrolls the editor if point is outside the 
					window.
	Access :		Protected

	Return :		CPoint			-	The pixels scrolled 
										horisontally and 
										vertically.
	Parameters :	CPoint point	-	The position of the mouse 
										pointer.
					
	Usage :			Internal function.

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

	CPoint out( 0, 0 );
	CRect clientRect;
	GetClientRect( &clientRect );

	if( point.x > clientRect.right )
		out.x = HScroll( 10 );

	if( point.x < 0 )
		out.x = HScroll( -10 );

	if( point.y > clientRect.bottom )
		out.y = VScroll( 10 );

	if( point.y < 0 )
		out.y = VScroll( -10 );

	return out;

}

void CDiagramEditor::SetupScrollbars()
/* ============================================================
	Function :		CDiagramEditor::SetupScrollbars
	Description :	Sets up/removes scrollbars depending on the 
					relation between the client- and virtual 
					rect.
	Access :		Protected

	Return :		void
	Parameters :	none

	Usage :			Internal function.

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

	if( m_hWnd )
	{
		CRect rect;
		GetClientRect( rect );

		DWORD add = 0;
		DWORD remove = 0;

		if( round( static_cast< double >( GetVirtualSize().cx ) * GetZoom() ) <= rect.right )
		{
			if( GetStyle() & WS_HSCROLL )
				remove |= WS_HSCROLL;
		}
		else
		{
			if( !( GetStyle() & WS_HSCROLL ) )
				add |= WS_HSCROLL;
		}
		if( round( static_cast< double >( GetVirtualSize().cy ) * GetZoom() ) <= rect.bottom )
		{
			if( GetStyle() & WS_VSCROLL )
				remove |= WS_VSCROLL;
		}
		else
		{
			if( !( GetStyle() & WS_VSCROLL ) )
				add |= WS_VSCROLL;
		}

		SCROLLINFO si;
		si.cbSize = sizeof( SCROLLINFO );
		si.fMask = SIF_RANGE | SIF_PAGE;
		si.nMin = 0;
		int width = round( static_cast< double >( GetVirtualSize().cx ) * GetZoom() );
		if( width > rect.right )
		{
			si.nMax = width;
			si.nPage = rect.right;
		}
		else
		{
			si.nMax = 0;
			si.nPage = 0;
		}

		SetScrollInfo( SB_HORZ, &si );
		
		int height = round( static_cast< double >( GetVirtualSize().cy ) * GetZoom() );
		if( height > rect.bottom )
		{
			si.nMax = height;
			si.nPage = rect.bottom;
		}
		else
		{
			si.nMax = 0;
			si.nPage = 0;
		}

		SetScrollInfo( SB_VERT, &si );

		if( add || remove )
			ModifyStyle( remove, add, SWP_FRAMECHANGED );

	}

}

/////////////////////////////////////////////////////////////////////////////
// CDiagramEditor sizing

void CDiagramEditor::OnSize( UINT nType, int cx, int cy ) 
/* ============================================================
	Function :		CDiagramEditor::OnSize
	Description :	Handles the "WM_SIZE" message. Sets up/modifies 
					scrollbars as necessary.
	Access :		Protected

	Return :		void
	Parameters :	UINT nType	-	
					int cx		-	The new x size
					int cy		-	The new y size
					
	Usage :			Called from MFC. Do not call from code.

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

	CWnd::OnSize( nType, cx, cy );
	SetupScrollbars();

}

/////////////////////////////////////////////////////////////////////////////
// CDiagramEditor coordinate handling

void CDiagramEditor::ScreenToVirtual( CRect& rect ) const
/* ============================================================
	Function :		CDiagramEditor::ScreenToVirtual
	Description :	Converts rect from screen- to virtual 
					coordinates.
	Access :		Public
					
	Return :		void
	Parameters :	CRect& rect	-	The rect to convert
					
	Usage :			Call to - for example - display coordinates,

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

	// We have to normalize, add scroll bar positions and 
	// apply current zoom.

	rect.NormalizeRect();

	SCROLLINFO sih;
	SCROLLINFO siv;

	sih.cbSize = sizeof( SCROLLINFO );
	sih.fMask = SIF_POS;
	siv.cbSize = sizeof( SCROLLINFO );
	siv.fMask = SIF_POS;

	if( !::GetScrollInfo( m_hWnd, SB_HORZ, &sih ) )
		sih.nPos = 0;
	if( !::GetScrollInfo( m_hWnd, SB_VERT, &siv ) )
		siv.nPos = 0;

	rect.SetRect( round( static_cast< double >( rect.left	+ sih.nPos ) / GetZoom() ), 
				  round( static_cast< double >( rect.top	+ siv.nPos ) / GetZoom() ), 
				  round( static_cast< double >( rect.right	+ sih.nPos ) / GetZoom() ), 
				  round( static_cast< double >( rect.bottom	+ siv.nPos ) / GetZoom() ) );

}

void CDiagramEditor::ScreenToVirtual( CPoint& point ) const
/* ============================================================
	Function :		CDiagramEditor::ScreenToVirtual
	Description :	Converts point from screen- to virtual 
					coordinates.
	Access :		Public

	Return :		void
	Parameters :	CPoint& point	-	The point to convert.	
					
	Usage :			Call to - for example - display coordinates,

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

	// Convert point to the virtual
	// coordinate system. We have to normalize, 
	// add scroll bar positions and apply current 
	// scale.

	SCROLLINFO sih;
	SCROLLINFO siv;

	sih.cbSize = sizeof( SCROLLINFO );
	sih.fMask = SIF_POS;
	siv.cbSize = sizeof( SCROLLINFO );
	siv.fMask = SIF_POS;

	if( !::GetScrollInfo( m_hWnd, SB_HORZ, &sih ) )
		sih.nPos = 0;
	if( !::GetScrollInfo( m_hWnd, SB_VERT, &siv ) )
		siv.nPos = 0;

	point.x = round( static_cast< double >( point.x + sih.nPos ) / GetZoom() );
	point.y = round( static_cast< double >( point.y + siv.nPos ) / GetZoom() );

}

void CDiagramEditor::ScreenToVirtual( CSize& size ) const
/* ============================================================
	Function :		CDiagramEditor::ScreenToVirtual
	Description :	Converts size from screen- to virtual 
					coordinates.
	Access :		Public

	Return :		void
	Parameters :	CSize& size	-	The size to convert.
					
	Usage :			Call to - for example - display coordinates,

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

	// We have to deduct scroll bar positions 
	// and apply current zoom.


	SCROLLINFO sih;
	SCROLLINFO siv;

	sih.cbSize = sizeof( SCROLLINFO );
	sih.fMask = SIF_POS;
	siv.cbSize = sizeof( SCROLLINFO );
	siv.fMask = SIF_POS;

	if( !::GetScrollInfo( m_hWnd, SB_HORZ, &sih ) )
		sih.nPos = 0;
	if( !::GetScrollInfo( m_hWnd, SB_VERT, &siv ) )
		siv.nPos = 0;

	size.cx = round( static_cast< double >( size.cx + sih.nPos ) / GetZoom() );
	size.cy = round( static_cast< double >( size.cy + siv.nPos ) / GetZoom() );

}

void CDiagramEditor::VirtualToScreen( CRect& rect ) const
/* ============================================================
	Function :		CDiagramEditor::VirtualToScreen
	Description :	Converts rect from the virtual to the 
					screen coordinate system. 
	Access :		Public

	Return :		void
	Parameters :	CRect& rect	-	The rect to convert. Will be 
									normalized on return.
					
	Usage :			Call to - for example - display coordinates,

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

	// We have to normalize, deduct scroll bar positions 
	// and apply current zoom.

	rect.NormalizeRect();

	SCROLLINFO sih;
	SCROLLINFO siv;

	sih.cbSize = sizeof( SCROLLINFO );
	sih.fMask = SIF_POS;
	siv.cbSize = sizeof( SCROLLINFO );
	siv.fMask = SIF_POS;

	if( !::GetScrollInfo( m_hWnd, SB_HORZ, &sih ) )
		sih.nPos = 0;
	if( !::GetScrollInfo( m_hWnd, SB_VERT, &siv ) )
		siv.nPos = 0;

	rect.SetRect( round( static_cast< double >( rect.left	) * GetZoom() - static_cast< double >( sih.nPos ) ), 
				  round( static_cast< double >( rect.top	) * GetZoom() - static_cast< double >( siv.nPos ) ), 
				  round( static_cast< double >( rect.right	) * GetZoom() - static_cast< double >( sih.nPos ) ), 
				  round( static_cast< double >( rect.bottom	) * GetZoom() - static_cast< double >( siv.nPos ) ) );

}

void CDiagramEditor::VirtualToScreen( CPoint& point ) const
/* ============================================================
	Function :		CDiagramEditor::VirtualToScreen
	Description :	Converts point from the virtual to the 
					screen coordinate system. 
	Access :		Public

	Return :		void
	Parameters :	CPoint& point	-	The point to convert. 
					
	Usage :			Call to - for example - display coordinates,

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

	point.x = round( static_cast< double >( point.x ) * GetZoom() );
	point.y = round( static_cast< double >( point.y ) * GetZoom() );


}

int CDiagramEditor::SnapX( int coord ) const
/* ============================================================
	Function :		CDiagramEditor::SnapX
	Description :	Snaps coord vertically to the closest 
					grid point.
	Access :		Protected

	Return :		int			-	The resulting x-coordinate.
	Parameters :	int coord	-	The coordinate to snap
					
	Usage :			Internal function. Will snap even if snap 
					to grid is off.

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

	double x = static_cast< double >( coord );
	double gridx = static_cast< double >( m_gridSize.cx );
	return round( x / gridx ) * m_gridSize.cx;

}

int CDiagramEditor::SnapY( int coord ) const
/* ============================================================
	Function :		CDiagramEditor::SnapY
	Description :	Snaps coord horisontally to the closest 
					grid point.
	Access :		Protected

	Return :		int			-	The resulting y-coordinate.
	Parameters :	int coord	-	The coordinate to snap
					
	Usage :			Internal function. Will snap even if snap 
					to grid is off.

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

	double y = static_cast< double >( coord );
	double gridy = static_cast< double >( m_gridSize.cy );
	return round( y / gridy ) * m_gridSize.cy;

}

/////////////////////////////////////////////////////////////////////////////
// CDiagramEditor object manipulations

BOOL CDiagramEditor::IsAnyObjectSelected() const
/* ============================================================
	Function :		CDiagramEditor::IsAnyObjectSelected
	Description :	Returns "TRUE" if any object is selected.
	Access :		Public

	Return :		BOOL	-	"TRUE" if any object is selected.
	Parameters :	none

	Usage :			Call to check if any objects are selected.

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

	BOOL res = FALSE;
	if( m_objs )
	{
		int count = 0;
		CDiagramEntity* obj;
		while( ( obj = m_objs->GetAt( count++ ) ) )
			if( obj->IsSelected() )
				res = TRUE;
	}

	return res;

}

int CDiagramEditor::GetSelectCount() const
/* ============================================================
	Function :		CDiagramEditor::GetSelectCount
	Description :	Returns the number of selected objects in 
					the data container.
	Access :		Public

	Return :		int	-	The number of selected objects.
	Parameters :	none

	Usage :			Call to get the number of selected objects.

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

	int res = 0;

	if( m_objs )
		res = m_objs->GetSelectCount();

	return res;

}

CDiagramEntity* CDiagramEditor::GetSelectedObject() const
/* ============================================================
	Function :		CDiagramEditor::GetSelectedObject
	Description :	Returns the top selected object in the 
					container.
	Access :		Public

	Return :		CDiagramEntity*	-	The top selected object, 
										or "NULL" if none.
	Parameters :	none

	Usage :			Call to get the currently selected object. 
					Note that this function will return a single 
					object (top in the z-order) even if several 
					are selected.

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

	int count = 0;
	CDiagramEntity* retval = NULL;
	CDiagramEntity* obj;

	if( m_objs )
		while( ( obj = m_objs->GetAt( count++ ) ) )
			if( obj->IsSelected() )
				retval = obj;

	return retval;

}

int CDiagramEditor::GetObjectCount() const
/* ============================================================
	Function :		CDiagramEditor::GetObjectCount
	Description :	Returns the number of objects in the container.
	Access :		Public

	Return :		int	-	The number of objects.
	Parameters :	none

	Usage :			Call to get the number of objects in the 
					data container.

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

	int size = 0;
	if( m_objs )
		size = m_objs->GetSize();

	return size;

}

void CDiagramEditor::AddObject( CDiagramEntity* obj ) 
/* ============================================================
	Function :		CDiagramEditor::AddObject
	Description :	Adds "obj" to the data container.
	Access :		Public

	Return :		void
	Parameters :	CDiagramEntity* obj	-	The object to add.
					
	Usage :			Called to add objects to the container after 
					- for example - a load operation. Note that 
					the modified flag must be set manually if 
					this is desired (if implementing an Append 
					command, for example).
					This function can be overridden to trap
					object additions.

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

	if( m_objs )
		m_objs->Add( obj );

}

CDiagramEntity* CDiagramEditor::GetObject( int index )  const
/* ============================================================
	Function :		CDiagramEditor::GetObject
	Description :	Gets the object at index "index".
	Access :		Public

	Return :		CDiagramEntity*	-	The object, "NULL" if not 
										found.
	Parameters :	int index		-	The index to get the 
										object from
					
	Usage :			Call to get a specific object from the 
					container. 
					The objects are the responsibility of the 
					container and should not be deleted.

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

	CDiagramEntity* obj = NULL;
	if( m_objs )
		obj = m_objs->GetAt( index );

	return obj;

}

void CDiagramEditor::StartDrawingObject( CDiagramEntity* obj )
/* ============================================================
	Function :		CDiagramEditor::StartDrawingObject
	Description :	Starts drawing an object of type "obj".
	Access :		Public

	Return :		void
	Parameters :	CDiagramEntity* obj	-	Pointer to an object 
											of the desired type.
					
	Usage :			Should be called with a pointer to the 
					appropriate object type when the user 
					selects to draw an object. obj becomes 
					the property of the editor and should not 
					be deleted.

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

	delete m_drawObj;
	m_drawObj = obj;

	if( obj )
	{
		m_drawing = TRUE;
		m_interactMode = MODE_DRAWING;
		::SetCursor( LoadCursor( NULL, IDC_CROSS ) );
	}
	else
	{
		m_drawing = FALSE;
		m_interactMode = MODE_NONE;
		::SetCursor( LoadCursor( NULL, IDC_ARROW ) );
	}

}

BOOL CDiagramEditor::IsDrawing() const
/* ============================================================
	Function :		CDiagramEditor::IsDrawing
	Description :	Returns if the user is drawing or not
	Access :		Public

	Return :		BOOL	-	"TRUE" if the user is currently 
								drawing
	Parameters :	none

	Usage :			Called for command enabling if multi-draw
					mode is enabled.

   ============================================================*/
{
	return m_drawObj && m_drawing;
}

void CDiagramEditor::UnselectAll()
/* ============================================================
	Function :		CDiagramEditor::UnselectAll
	Description :	Unselects all objects in the container.
	Access :		Public

	Return :		void
	Parameters :	none

	Usage :			Call to unselect all objects. 
					Should not be callable if "GetObjectCount() 
					== 0" (there are no objects in the container).

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

	if( m_objs )
	{
		int count = 0;
		CDiagramEntity* obj;
		while( ( obj = m_objs->GetAt( count++ ) ) )
			obj->Select( FALSE );
	}

}

void CDiagramEditor::SelectAll()
/* ============================================================
	Function :		CDiagramEditor::SelectAll
	Description :	Selects all objects.
	Access :		Public

	Return :		void
	Parameters :	none

	Usage :			Call to select all objects. 
					Should not be callable if "GetObjectCount() 
					== 0" (there are no objects in the container).

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

	if( m_objs )
	{
		int count = 0;
		CDiagramEntity* obj;
		while( ( obj = m_objs->GetAt( count++ ) ) )
			obj->Select( TRUE );
		RedrawWindow();
	}

}

void CDiagramEditor::DeleteAllSelected()
/* ============================================================
	Function :		CDiagramEditor::DeleteAllSelected
	Description :	Deletes all selected objects from the 
					container.
	Access :		Public

	Return :		void
	Parameters :	none

	Usage :			Call to delete all selected objects from the 
					editor. Should not be callable if 
					"GetSelectCount() == 0" ( or "IsAnyObjectSelected() 
					== FALSE" )

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

	if( m_objs )
	{
		m_objs->Snapshot();
		m_objs->RemoveAllSelected();
		RedrawWindow();
	}

}

void CDiagramEditor::DeleteAll()
/* ============================================================
	Function :		CDiagramEditor::DeleteAll
	Description :	Deletes all objects from the container.
	Access :		Public

	Return :		void
	Parameters :	none

	Usage :			Call to delete all objects from the editor.

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

	if( m_objs )
	{
		m_objs->Snapshot();
		m_objs->RemoveAll();
		RedrawWindow();
	}

}

/////////////////////////////////////////////////////////////////////////////
// CDiagramEditor layout

void CDiagramEditor::LeftAlignSelected()
/* ============================================================
	Function :		CDiagramEditor::LeftAlignSelected
	Description :	Aligns all selected objects to the left of
					the top selected object in the data container. 
	Access :		Public

	Return :		void
	Parameters :	none

	Usage :			Call to align the left edge of all selected 
					objects.
					Should only be callable if "GetSelectCount() > 
					1", that is, more than one object is selected.

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

	if( GetSelectCount() > 1 )
	{
		m_objs->Snapshot();
		CDiagramEntity* obj = GetSelectedObject();
		if( obj )
		{
			double left = obj->GetLeft();
			int count = 0;
			while( ( obj = m_objs->GetAt( count++ ) ) )
			{
				if( obj->IsSelected() )
				{
					double width = obj->GetRight() - obj->GetLeft();
					double right = left + width;
					obj->SetRect( left, obj->GetTop(), right, obj->GetBottom() );
				}
			}

		}
		SetModified( TRUE );
		RedrawWindow();
	}

}

void CDiagramEditor::RightAlignSelected()
/* ============================================================
	Function :		CDiagramEditor::RightAlignSelected
	Description :	Aligns all selected objects to the right of
					the top selected object in the data container. 
	Access :		Public

	Return :		void
	Parameters :	none

	Usage :			Call to align the right edge of all selected 
					objects.
					Should only be callable if "GetSelectCount() > 
					1", that is, more than one object is selected.

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

	if( GetSelectCount() > 1 )
	{
		m_objs->Snapshot();
		CDiagramEntity* obj = GetSelectedObject();
		if( obj )
		{
			double right = obj->GetRight();
			int count = 0;
			while( ( obj = m_objs->GetAt( count++ ) ) )
			{
				if( obj->IsSelected() )
				{
					double width = obj->GetRight() - obj->GetLeft();
					double left = right - width;
					obj->SetRect( left, obj->GetTop(), right, obj->GetBottom() );
				}
			}

		}
		SetModified( TRUE );
		RedrawWindow();
	}

}

void CDiagramEditor::TopAlignSelected()
/* ============================================================
	Function :		CDiagramEditor::TopAlignSelected
	Description :	Aligns all selected objects to the top of
					the top selected object in the data container. 
	Access :		Public

	Return :		void
	Parameters :	none

	Usage :			Call to align the top of all selected 
					objects.
					Should only be callable if "GetSelectCount() > 
					1", that is, more than one object is selected.

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

	if( GetSelectCount() > 1 )
	{
		m_objs->Snapshot();
		CDiagramEntity* obj = GetSelectedObject();
		if( obj )
		{
			double top = obj->GetTop();
			int count = 0;
			while( ( obj = m_objs->GetAt( count++ ) ) )
			{
				if( obj->IsSelected() )
				{
					double height = obj->GetBottom() - obj->GetTop();
					double bottom = top + height;
					obj->SetRect( obj->GetLeft(), top, obj->GetRight(), bottom );
				}
			}

		}
		SetModified( TRUE );
		RedrawWindow();
	}

}

void CDiagramEditor::BottomAlignSelected()
/* ============================================================
	Function :		CDiagramEditor::BottomAlignSelected
	Description :	Aligns all selected objects to the bottom of
					the top selected object in the data container. 
	Access :		Public

	Return :		void
	Parameters :	none

	Usage :			Call to align the bottom of all selected 
					objects.
					Should only be callable if "GetSelectCount() > 
					1", that is, more than one object is selected.

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

	if( GetSelectCount() > 1 )
	{
		m_objs->Snapshot();
		CDiagramEntity* obj = GetSelectedObject();
		if( obj )
		{
			double bottom = obj->GetBottom();
			int count = 0;
			while( ( obj = m_objs->GetAt( count++ ) ) )
			{
				if( obj->IsSelected() )
				{
					double height = obj->GetBottom() - obj->GetTop();
					double top = bottom - height;
					obj->SetRect( obj->GetLeft(), top, obj->GetRight(), bottom );
				}
			}

		}
		SetModified( TRUE );
		RedrawWindow();
	}

}

void CDiagramEditor::MakeSameSizeSelected()
/* ============================================================
	Function :		CDiagramEditor::MakeSameSizeSelected
	Description :	Makes all selected objects the same size as 
					the top selected object in the data container.
	Access :		Public

	Return :		void
	Parameters :	none

	Usage :			Call to make all selected objects the same 
					size.
					Should only be called if "GetSelectCount() > 
					1", that is, more than one object is selected.

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

	if( GetSelectCount() > 1 )
	{
		m_objs->Snapshot();
		CDiagramEntity* obj = GetSelectedObject();
		if( obj )
		{
			double width = obj->GetRight() - obj->GetLeft();
			double height = obj->GetBottom() - obj->GetTop();
			int count = 0;
			while( ( obj = m_objs->GetAt( count++ ) ) )
				if( obj->IsSelected() )
					obj->SetRect( obj->GetLeft(), obj->GetTop(), obj->GetLeft() + width, obj->GetTop() + height );

		}
		SetModified( TRUE );
		RedrawWindow();
	}

}

void CDiagramEditor::Duplicate()
/* ============================================================
	Function :		CDiagramEditor::Duplicate
	Description :	Duplicates the currently selected object.
	Access :		Public

	Return :		void
	Parameters :	none

	Usage :			Call to put a copy of the selected object on 
					the screen.

   ============================================================*/
{
	if( GetSelectCount() == 1 )
	{
		CDiagramEntity* obj = GetSelectedObject();
		if( obj )
		{
			m_objs->Snapshot();
			m_objs->Duplicate( obj );
			obj->Select( FALSE );
			RedrawWindow();
		}
	}
}

void CDiagramEditor::Up()
/* ============================================================
	Function :		CDiagramEditor::Up
	Description :	Moves the currently selected object one 
					step up in the object z-order.
	Access :		Public

	Return :		void
	Parameters :	none

	Usage :			Call to move the selected object one step 
					up in the z-order.
					This command should only be callable if
					"GetSelectCount()" returns 1, that is, if one 
					and only one object is selected.

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

	if( GetSelectCount() == 1 )
	{
		CDiagramEntity* obj = GetSelectedObject();
		if( obj )
		{
			m_objs->Up( obj );
			RedrawWindow();
		}
	}
}

void CDiagramEditor::Down()
/* ============================================================
	Function :		CDiagramEditor::Down
	Description :	Moves the currently selected object one 
					step down in the object z-order.
	Access :		Public

	Return :		void
	Parameters :	none

	Usage :			Call to move the selected object one step 
					down in the z-order.
					This command should only be callable if
					"GetSelectCount()" returns 1, that is, if one 
					and only one object is selected.

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

	if( GetSelectCount() == 1 )
	{
		CDiagramEntity* obj = GetSelectedObject();
		if( obj )
		{
			m_objs->Down( obj );
			RedrawWindow();
		}
	}
}

void CDiagramEditor::Front()
/* ============================================================
	Function :		CDiagramEditor::Front
	Description :	Moves the selected object to the front of 
					all other objects.
	Access :		Public

	Return :		void
	Parameters :	none

	Usage :			Call to move the selected object to the 
					top of the z-order. 
					This command should only be callable if
					"GetSelectCount()" returns 1, that is, if one 
					and only one object is selected.

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

	if( GetSelectCount() == 1 )
	{
		CDiagramEntity* obj = GetSelectedObject();
		if( obj )
		{
			m_objs->Front( obj );
			RedrawWindow();
		}
	}
}

void CDiagramEditor::Bottom()
/* ============================================================
	Function :		CDiagramEditor::Bottom
	Description :	Moves the selected object to the bottom of 
					all objects.
	Access :		Public

	Return :		void
	Parameters :	none

	Usage :			Call to move the selected object to the 
					bottom of the z-order. 
					This command should only be callable if
					"GetSelectCount()" returns 1, that is, if one 
					and only one object is selected.

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

	if( GetSelectCount() == 1 )
	{
		CDiagramEntity* obj = GetSelectedObject();
		if( obj )
		{
			m_objs->Bottom( obj );
			RedrawWindow();
		}
	}
}

void CDiagramEditor::ShowProperties()
/* ============================================================
	Function :		CDiagramEditor::ShowProperties
	Description :	Shows the property dialog for the one and 
					only selected object in the data container.
	Access :		Public

	Return :		void
	Parameters :	none

	Usage :			Call to display a property dialog for the 
					selected object. This command should 
					normally only be callable if "GetSelectCount()" 
					returns 1, that is, if one and only one object 
					is selected.

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

	if( GetSelectCount() == 1 )
	{
		CDiagramEntity* obj = GetSelectedObject();
		if( obj )
			obj->ShowProperties( this );
	}

}

void CDiagramEditor::RemoveUnselectedPropertyDialogs()
/* ============================================================
	Function :		CDiagramEditor::RemoveUnselectedPropertyDialogs
	Description :	Removes all property dialogs for unselected 
					objects.
	Access :		Private

	Return :		void
	Parameters :	none

	Usage :			Internal function. Call to remove all 
					property dialogs for non-selected objects 
					from screen.

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

	if( m_objs )
	{
		int count = 0;
		CDiagramEntity* obj;
		while( ( obj = m_objs->GetAt( count++ ) ) )
			if( !obj->IsSelected() )
				obj->ShowProperties( this, FALSE );
	}

}

void CDiagramEditor::ShowPopup( CPoint point ) 
/* ============================================================
	Function :		CDiagramEditor::ShowPopup
	Description :	Shows a popup menu in response to a right-
					click if no object is selected.
	Access :		Public

	Return :		void
	Parameters :	CPoint point	-	The point to track the 
										menu from.
					
	Usage :			Internal function. Call to show the editor 
					popup menu.

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

	if( !m_popupMenu )
		m_popupMenu = new CDiagramMenu;

	if( m_popupMenu )
	{
		CMenu* menu = m_popupMenu->GetPopupMenu( this );
		if( menu )
			menu->TrackPopupMenu( TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this );
	}

}

/////////////////////////////////////////////////////////////////////////////
// CDiagramEditor copy/paste/undo

void CDiagramEditor::OnEditCut()
/* ============================================================
	Function :		CDiagramEditor::OnEditCut
	Description :	Command handler for the MFC standard 
					"ID_EDIT_CUT" command.
	Access :		Protected

	Return :		void
	Parameters :	none

	Usage :			Called from MFC. Call "Cut" from code instead.

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

	Cut();

}

void CDiagramEditor::OnEditCopy()
/* ============================================================
	Function :		CDiagramEditor::OnEditCopy
	Description :	Command handler for the MFC standard 
					"ID_EDIT_COPY" command.
	Access :		Protected

	Return :		void
	Parameters :	none

	Usage :			Called from MFC. Call "Copy" from code 
					instead.

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

	Copy();

}

void CDiagramEditor::OnEditPaste()
/* ============================================================
	Function :		CDiagramEditor::OnEditPaste
	Description :	Command handler for the MFC standard 
					"ID_EDIT_PASTE" command.
	Access :		Protected

	Return :		void
	Parameters :	none

	Usage :			Called from MFC. Call "Paste" from code 
					instead.

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

	Paste();

}

void CDiagramEditor::OnEditGroup()
/* ============================================================
	Function :		CDiagramEditor::OnEditGroup
	Description :	Handler for the "ID_EDIT_GROUP" command
	Access :		Protected

	Return :		void
	Parameters :	none

	Usage :			Groups the currently selected objects. 
					Grouped objects can be moved as a 
					single entity. Technically, when one object 
					in a group is selected, all other objects 
					are also selected automatically.

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

	Group();

}

void CDiagramEditor::OnEditUngroup()
/* ============================================================
	Function :		CDiagramEditor::OnEditUngroup
	Description :	Handler for the "ID_EDIT_UNGROUP" command
	Access :		Protected

	Return :		void
	Parameters :	none

	Usage :			Ungroups the currently selected objects.
					Grouped objects can be moved as a 
					single entity. Technically, when one object 
					in a group is selected, all other objects 
					are also selected automatically.

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

	Ungroup();

}

void CDiagramEditor::Cut()
/* ============================================================
	Function :		CDiagramEditor::Cut
	Description :	Cut the currently selected items to the 
					internal "CDiagramEntityContainer" clipboard.
	Access :		Public

	Return :		void
	Parameters :	none

	Usage :			Call to delete and place the currently 
					selected objects to the 'clipboard'.

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

	if( m_objs )
	{
		m_objs->Snapshot();
		m_objs->CopyAllSelected();
		DeleteAllSelected();
	}

}

void CDiagramEditor::Copy()
/* ============================================================
	Function :		CDiagramEditor::Copy
	Description :	Copy the currently selected items to the 
					internal "CDiagramEntityContainer" clipboard.
	Access :		Public

	Return :		void
	Parameters :	none

	Usage :			Call to copy the selected objects to the 
					'clipboard'.

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

	if( m_objs )
	{
		m_objs->Snapshot();
		m_objs->CopyAllSelected();
	}

}

void CDiagramEditor::Paste()
/* ============================================================
	Function :		CDiagramEditor::Paste
	Description :	Paste copied or cut objects to the screen.
	Access :		Public

	Return :		void
	Parameters :	none

	Usage :			Call to paste the contents of the clipboard 
					to the screen.

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

	// Removes the current selection and pastes the contents
	// of the data paste-array.
	if( m_objs )
	{
		SetRedraw( FALSE );
		m_objs->Snapshot();
		UnselectAll();
		m_objs->Paste();
		SetRedraw( TRUE );
		RedrawWindow();
	}

}

void CDiagramEditor::Undo()
/* ============================================================
	Function :		CDiagramEditor::Undo
	Description :	Undo the last operation.
	Access :		Public

	Return :		void
	Parameters :	none

	Usage :			Call to restore the objects to the last 
					snapshot.

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

	if( m_objs )
	{
		m_objs->Undo();
		RedrawWindow();
	}

}

void CDiagramEditor::Group()
/* ============================================================
	Function :		CDiagramEditor::OnEditGroup
	Description :	Groups the currently selected objects.
	Access :		Public

	Return :		void
	Parameters :	none

	Usage :			Call to group the currently selected 
					objects. Grouped objects can be moved as a 
					single entity. Technically, when one object 
					in a group is selected, all other objects 
					are also selected automatically.

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

	if( m_objs )
	{
		SetRedraw( FALSE );
		m_objs->Snapshot();
		m_objs->Group();
		SetRedraw( TRUE );
		RedrawWindow();
	}

}

void CDiagramEditor::Ungroup()
/* ============================================================
	Function :		CDiagramEditor::OnEditUngroup
	Description :	Ungroups the currently selected objects.
	Access :		Public

	Return :		void
	Parameters :	none

	Usage :			Call to ungroup the currently selected 
					objects. Grouped objects can be moved as a 
					single entity. Technically, when one object 
					in a group is selected, all other objects 
					are also selected automatically.

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

	if( m_objs )
	{
		SetRedraw( FALSE );
		m_objs->Snapshot();
		m_objs->Ungroup();
		SetRedraw( TRUE );
		RedrawWindow();
	}

}

void CDiagramEditor::UpdateCut( CCmdUI* pCmdUI ) const
/* ============================================================
	Function :		CDiagramEditor::UpdateCut
	Description :	Command enabling for a Cut command UI-
					element.
	Access :		Public

	Return :		void
	Parameters :	CCmdUI* pCmdUI	-	Command element to 
										update
					
	Usage :			Can be called from a doc/view command update 
					function

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

	// We can only cut if there is something selected
	pCmdUI->Enable( IsAnyObjectSelected() );

}

void CDiagramEditor::UpdateCopy( CCmdUI* pCmdUI ) const
/* ============================================================
	Function :		CDiagramEditor::UpdateCopy
	Description :	Command enabling for a Copy command UI-
					element.
	Access :		Public

	Return :		void
	Parameters :	CCmdUI* pCmdUI	-	Command element to 
										update
					
	Usage :			Can be called from a doc/view command update 
					function

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

	pCmdUI->Enable( IsAnyObjectSelected() );

}

void CDiagramEditor::UpdatePaste( CCmdUI* pCmdUI ) const
/* ============================================================
	Function :		CDiagramEditor::UpdatePaste
	Description :	Command enabling for a Paste command UI-
					element.
	Access :		Public

	Return :		void
	Parameters :	CCmdUI* pCmdUI	-	Command element to 
										update
					
	Usage :			Can be called from a doc/view command update 
					function

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

	if( m_objs )
		pCmdUI->Enable( m_objs->ObjectsInPaste() );

}

void CDiagramEditor::UpdateUndo( CCmdUI* pCmdUI ) const
/* ============================================================
	Function :		CDiagramEditor::UpdateUndo
	Description :	Command enabling for an Undo command UI-
					element.
	Access :		Public

	Return :		void
	Parameters :	CCmdUI* pCmdUI	-	Command element to 
										update
					
	Usage :			Can be called from a doc/view command update 
					function

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

	if( m_objs )
		pCmdUI->Enable( m_objs->IsUndoPossible() );

}

void CDiagramEditor::UpdateGroup( CCmdUI* pCmdUI ) const
/* ============================================================
	Function :		CDiagramEditor::UpdateGroup
	Description :	Command enabling for an Group command UI-
					element.
	Access :		Public

	Return :		void
	Parameters :	CCmdUI* pCmdUI	-	Command element to 
										update
					
	Usage :			Can be called from a doc/view command update 
					function

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

	if( m_objs )
		pCmdUI->Enable( GetSelectCount() > 1 );

}

void CDiagramEditor::UpdateUngroup( CCmdUI* pCmdUI ) const
/* ============================================================
	Function :		CDiagramEditor::UpdateUngroup
	Description :	Command enabling for an Ungroup command UI-
					element.
	Access :		Public

	Return :		void
	Parameters :	CCmdUI* pCmdUI	-	Command element to 
										update
					
	Usage :			Can be called from a doc/view command update 
					function

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

	if( m_objs )
		pCmdUI->Enable( GetSelectCount() > 1 );

}

/////////////////////////////////////////////////////////////////////////////
// CDiagramEditor saving

void CDiagramEditor::Save( CStringArray& stra )
/* ============================================================
	Function :		CDiagramEditor::Save
	Description :	Saves a string representation of the 
					container (normally the virtual size) and 
					all objects to "stra".
	Access :		Public

	Return :		void
	Parameters :	CStringArray& stra	-	The array to fill
					
	Usage :			Call to save the data of the editor to a 
					"CStringArray". Virtual. Can be overridden in 
					a derived class to add non-container data 
					before and after the objects

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

	if( m_objs )
	{
		stra.Add( m_objs->GetString() );
		SaveObjects( stra );
		SetModified( FALSE );
	}

}

BOOL CDiagramEditor::FromString( const CString& str )
/* ============================================================
	Function :		CDiagramEditor::FromString
	Description :	Sets the virtual size from a string 
					representation of the paper.
	Access :		Public

	Return :		BOOL				-	"TRUE" if "str" is a 
											representation of a 
											'paper'.
	Parameters :	const CString& str	-	String representation.
					
	Usage :			Call to set the size of the virtual paper 
					from a string.

   ============================================================*/
{
	BOOL result = FALSE;
	if( m_objs )
		result = m_objs->FromString( str );

	return result;

}


void CDiagramEditor::SaveObjects( CStringArray& stra )
/* ============================================================
	Function :		CDiagramEditor::SaveObjects
	Description :	Saves a string representation of the 
					current objects to "stra", one object in 
					each string.
	Access :		Public

	Return :		void
	Parameters :	CStringArray& stra	-	The array to fill
					
	Usage :			Virtual. Can be overridden in a derived 
					class to add more per-object information to 
					the save array.

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

	if( m_objs )
	{
		int count = 0;
		CDiagramEntity* obj;
		while( ( obj = m_objs->GetAt( count++ ) ) )
			stra.Add( obj->GetString() );
	}

}

/////////////////////////////////////////////////////////////////////////////
// CDiagramEditor command handling

void CDiagramEditor::OnObjectCommand( UINT nID )
/* ============================================================
	Function :		CDiagramEditor::OnObjectCommand
	Description :	Called from "WM_COMMAND" messages in the 
					range "CMD_START" to "CMD_END" inclusive 
					(defined in DiagramEntity.h). Those are 
					messages that will be passed on to all 
					selected objects.
	Access :		Protected

	Return :		void
	Parameters :	UINT nID	-	The command to send.
					
	Usage :			Called from MFC. Do not call from code.

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

	if( m_objs )
	{
		m_objs->Snapshot();
		m_objs->SendMessageToObjects( nID, TRUE, NULL, static_cast< CWnd* >( this ) );
		RedrawWindow();
	}

}

int CDiagramEditor::GetInteractMode() const
/* ============================================================
	Function :		CDiagramEditor::GetInteractMode
	Description :	Get the interact mode.
	Access :		Protected

	Return :		int		-	Current interact mode
	Parameters :	none

	Usage :			The interact mode can be one of the 
					following:
						"MODE_NONE" No mode
						"MODE_RUBBERBANDING" User is selecting
						"MODE_MOVING" User is moving object(s)
						"MODE_RESIZING" User is resizing object
						"MODE_DRAWING" User is drawing object
						"MODE_BGRESIZING" User is resizing the virtual paper

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

	return m_interactMode;

}

void CDiagramEditor::SetInteractMode( int interactMode, int subMode )
/* ============================================================
	Function :		CDiagramEditor::SetInteractMode
	Description :	Sets the current mode of the editor
	Access :		Protected

	Return :		void
	Parameters :	int interactMode	-	The mode to set
					int subMode			-	The point of the 
											selected object, if 
											appropriate.
					
	Usage :			Call to set the interact mode of the 
					editor. The interact mode can be one of 
					the following:
						"MODE_NONE" No mode
						"MODE_RUBBERBANDING" User is selecting
						"MODE_MOVING" User is moving object(s)
						"MODE_RESIZING" User is resizing object
						"MODE_DRAWING" User is drawing object
						"MODE_BGRESIZING" User is resizing the virtual paper
					The submode, relevant when the mode is 
					MODE_RESIZING or MODE_BGRESIZING, can be 
					one of the following:
						"DEHT_NONE" No hit
						"DEHT_BODY" Object body
						"DEHT_TOPLEFT" Top-left corner
						"DEHT_TOPMIDDLE" Top-side
						"DEHT_TOPRIGHT" Top-right corner
						"DEHT_BOTTOMLEFT" Bottom left corner
						"DEHT_BOTTOMMIDDLE" Bottom-side
						"DEHT_BOTTOMRIGHT" Bottom-right corner
						"DEHT_LEFTMIDDLE" Left side
						"DEHT_RIGHTMIDDLE" Right side
					and indicates the point where the mouse is 
					as regards the currently selected object 
					(or the background).

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

	m_interactMode = interactMode;
	m_subMode = subMode;

}

CDiagramEntity* CDiagramEditor::GetDrawingObject()
/* ============================================================
	Function :		CDiagramEditor::GetDrawingObject
	Description :	Gets the object the user will draw.
	Access :		Protected

	Return :		CDiagramEntity*	-	Object
	Parameters :	none

	Usage :			Call to get the object that will be drawn 
					when the user clicks the 'paper'.

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

	return m_drawObj;

}

COLORREF CDiagramEditor::GetBackgroundColor() const
/* ============================================================
	Function :		CDiagramEditor::GetBackgroundColor
	Description :	Accessor for the background color
	Access :		Public

	Return :		COLORREF	-	Current background color
	Parameters :	none

	Usage :			Call to get the background color

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

	return m_bkgndCol;

}

/////////////////////////////////////////////////////////////////////////////
// CDiagramEditor zoom to fit functions

void CDiagramEditor::ZoomToFitScreen()
/* ============================================================
	Function :		CDiagramEditor::ZoomToFitScreen
	Description :	Zooms and scrolls so that all of the 
					current diagram is visible on screen.
	Access :		Public

	Return :		void
	Parameters :	none

	Usage :			Call to make the complete diagram visible.

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

	SetRedraw( FALSE );
	CRect rect;
	GetClientRect( rect );
	CPoint start = GetDiagramEntityContainer()->GetStartPoint();
	ZoomToFit( start, CSize( rect.Width(), rect.Height() ) );
	SetRedraw( TRUE );
	RedrawWindow();

}

BOOL CDiagramEditor::ZoomToFit( CPoint start, CSize size )
/* ============================================================
	Function :		CDiagramEditor::ZoomToFit
	Description :	Zooms the diagram to the size "size" and 
					sets the scrollbar positions at "start".
	Access :		Public

	Return :		BOOL			-	"TRUE" if any zoom (if
										that is the case, the 
										screen should be redrawn.
	Parameters :	CPoint start	-	Starting position of the 
										diagram, non-zoomed.
					CSize size		-	Size the diagram should 
										be zoomed to.
					
	Usage :			Call to zoom the diagram to a specific size.

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

	BOOL result = FALSE;

	if( ZoomToFit( size ) )
	{

		// Set scrollbar positions
		SCROLLINFO si;
		si.cbSize = sizeof( SCROLLINFO );
		si.fMask = SIF_POS;
		if( GetScrollInfo( SB_HORZ, &si ) )
		{
			si.nPos = round( ( start.x - GetMarkerSize().cx ) * GetZoom() );
			SetHScroll( si.nPos );

			si.nPos = round( ( start.y - GetMarkerSize().cy ) * GetZoom() );
			SetVScroll( si.nPos );

		}
		result = TRUE;
	}

	return result;

}

BOOL CDiagramEditor::ZoomToFit( CSize size )
/* ============================================================
	Function :		CDiagramEditor::ZoomToFit
	Description :	Zooms the diagram to the size "size".
	Access :		Public

	Return :		BOOL		-	"TRUE" if zoomed. The screen
									should be redrawn if that is 
									the case.
	Parameters :	CSize size	-	Size to zoom to.
					
	Usage :			Call to zoom the diagram to a specific size.

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

	BOOL result = FALSE;
	CSize objects = GetDiagramEntityContainer()->GetTotalSize();

	if( objects.cx && objects.cy )
	{
		// We add a little margin
		objects.cx += GetMarkerSize().cx * 2;
		objects.cy += GetMarkerSize().cy * 2;

		// Calculate new zoom
		double zoomx = static_cast< double >( size.cx ) / static_cast< double >( objects.cx );
		double zoomy = static_cast< double >( size.cy ) / static_cast< double >( objects.cy );

		SetZoom( min( zoomx, zoomy ) );
		result = TRUE;

	}
	
	return result;

}

/////////////////////////////////////////////////////////////////////////////
// CDiagramEditor mouse wheel handling

BOOL CDiagramEditor::OnMouseWheel( UINT nFlags, short zDelta, CPoint pt )
/* ============================================================
	Function :		CDiagramEditor::OnMouseWheel
	Description :	Handles the "WM_MOUSEWHEEL"-message.
	Access :		Protected

	Return :		BOOL			-	From default
	Parameters :	UINT nFlags		-	Not used
					short zDelta	-	Delta (notches * "WHEEL_DELTA")
					CPoint pt		-	Mouse position
					
	Usage :			Called from MFC. Scrolls the vertically.

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

	if( GetScrollWheelMode() == WHEEL_SCROLL )
	{
		SCROLLINFO si;
		si.cbSize = sizeof( SCROLLINFO );
		si.fMask = SIF_POS | SIF_RANGE;
		static int mouseDelta;

		mouseDelta += zDelta;
		int increment = ( mouseDelta / WHEEL_DELTA );
		if( GetScrollInfo( SB_VERT, &si ) )
		{
			if( si.nMax )
			{
				si.nPos -= increment;
				mouseDelta -= increment * WHEEL_DELTA;
				SetVScroll( si.nPos );
//				SetScrollInfo( SB_VERT, &si, TRUE );
				RedrawWindow();
			}
		}
	}
	else if( GetScrollWheelMode() == WHEEL_ZOOM )
	{
		if( zDelta < 0 )
			SetZoom( GetZoom() + GetZoomFactor() );
		else
			SetZoom( GetZoom() - GetZoomFactor() );
	}

	return CWnd::OnMouseWheel( nFlags, zDelta, pt );

}

/////////////////////////////////////////////////////////////////////////////
// CDiagramEditor panning

void CDiagramEditor::OnMButtonDown( UINT nFlags, CPoint point ) 
/* ============================================================
	Function :		CDiagramEditor::OnMButtonDown
	Description :	Handler for the "WM_MBUTTONDOWN"-message
	Access :		Protected

	Return :		void
	Parameters :	UINT nFlags		-	Not used
					CPoint point	-	Current mouse position
					
	Usage :			Called from MFC. Don't call from code. 
					We start panning.

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

	if( !GetPanning() )
	{
		SCROLLINFO si;
		if( GetScrollInfo( SB_HORZ, &si ) ||
			GetScrollInfo( SB_VERT, &si ) )
		{
			SetPanningOrigin( point );
			SetPanning( TRUE );
		}
	}
	else
	{
		SetPanning( FALSE );
	}

	CWnd::OnMButtonDown( nFlags, point );
	RedrawWindow();

}

void CDiagramEditor::OnTimer( UINT nIDEvent )
/* ============================================================
	Function :		CDiagramEditor::OnTimer
	Description :	Handler for the "WM_TIMER"-message
	Access :		Protected

	Return :		void
	Parameters :	UINT nIDEvent	-	Current timer
					
	Usage :			Called from MFC. We pan if we are in 
					panning mode.

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

	if( nIDEvent == m_panningTimer )
	{

		KillTimer( m_panningTimer );

		const MSG* msg = GetCurrentMessage();
		CPoint test = GetPanningOrigin();
		CPoint point( msg->pt );
		ScreenToClient( &point );

		int diffx = ( point.x - test.x ) / 16;
		int diffy = ( point.y - test.y ) / 16;

		SCROLLINFO sih;
		sih.cbSize = sizeof( SCROLLINFO );
		sih.fMask = SIF_POS;
		sih.nPos = 0;
		SCROLLINFO siv;
		siv.cbSize = sizeof( SCROLLINFO );
		siv.fMask = SIF_POS;
		siv.nPos = 0;

		GetScrollInfo( SB_HORZ, &sih );
		GetScrollInfo( SB_VERT, &siv );

		sih.nPos += diffx;
		siv.nPos += diffy;

		SetHScroll( sih.nPos );
		SetVScroll( siv.nPos );
//		SetScrollInfo( SB_HORZ, &sih );
//		SetScrollInfo( SB_VERT, &siv );
		RedrawWindow();

		m_panningTimer = SetTimer( m_panningTimer, 10, NULL );

	}

}

void CDiagramEditor::OnKillFocus( CWnd* pNewWnd )
/* ============================================================
	Function :		CDiagramEditor::OnKillFocus
	Description :	Handler for the "WM_KILLFOCUS"-message
	Access :		Protected

	Return :		void
	Parameters :	CWnd* pNewWnd	-	Window getting the focus
					
	Usage :			Called from MFC. We kill the panning

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

	if( GetPanning() )
		SetPanning( FALSE );

	CWnd::OnKillFocus( pNewWnd );

}

BOOL CDiagramEditor::GetPanning() const
/* ============================================================
	Function :		CDiagramEditor::GetPanning
	Description :	Accessor for the panning flag.
	Access :		Public

	Return :		BOOL	-	"TRUE" if we are currently 
								panning.
	Parameters :	none

	Usage :			Call to see if we are panning

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

	return m_panning;

}

CPoint CDiagramEditor::GetPanningOrigin() const
/* ============================================================
	Function :		CDiagramEditor::GetPanningOrigin
	Description :	Accessor for the panning origin.
	Access :		Public

	Return :		CPoint	-	Origin for panning.
	Parameters :	none

	Usage :			Call to get the panning origin. The panning 
					origin is the point where the user clicked 
					the middle mouse button.

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

	return m_panOrigin;

}

void CDiagramEditor::SetPanning( BOOL panning )
/* ============================================================
	Function :		CDiagramEditor::SetPanning
	Description :	Accessor for the panning flag
	Access :		Public

	Return :		void
	Parameters :	BOOL panning	-	"TRUE" if we should 
										start panning.
					
	Usage :			Call to start or stop panning.

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

	if( m_panning && panning == FALSE )
		KillTimer( m_panningTimer );

	m_panning = panning;

	if( m_panning )
		m_panningTimer = SetTimer( m_panningTimer, 10, NULL );

}

void CDiagramEditor::SetPanningOrigin( CPoint point )
/* ============================================================
	Function :		CDiagramEditor::SetPanningOrigin
	Description :	Sets the panning origin.
	Access :		Public

	Return :		void
	Parameters :	CPoint point	-	The origin for the 
										panning.
					
	Usage :			Call to set the origin when starting to 
					pan the screen. The panning origin is the
					point where the user clicked the middle 
					mouse button.

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

	m_panOrigin = point;

}

void CDiagramEditor::DrawPanning( CDC* dc ) const
/* ============================================================
	Function :		CDiagramEditor::DrawPanning
	Description :	Draws the panning origin marker.
	Access :		Public

	Return :		void
	Parameters :	CDC* dc	-	"CDC" to draw to
					
	Usage :			Call to draw the panning origin marker. You
					can override this to draw a different 
					origin marker for the pan.

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

	CDiagramEditor* const local = const_cast< CDiagramEditor* const >( this );

	SCROLLINFO sih;
	sih.cbSize = sizeof( SCROLLINFO );
	sih.fMask = SIF_POS;
	SCROLLINFO siv;
	siv.cbSize = sizeof( SCROLLINFO );
	siv.fMask = SIF_POS;
	if( !local->GetScrollInfo( SB_HORZ, &sih ) )
		sih.nPos = 0;
	if( !local->GetScrollInfo( SB_VERT, &siv ) )
		siv.nPos = 0;

	dc->SelectStockObject( WHITE_BRUSH );
	CPen pen;
	pen.CreatePen( PS_SOLID, 0, RGB( 128, 128, 128 ) );

	dc->SelectObject( &pen );
	CPoint	pt = GetPanningOrigin();
	CRect	rect( pt.x - 16 + sih.nPos, pt.y - 16 + siv.nPos, pt.x + 16 + sih.nPos, pt.y + 16 + siv.nPos);
	dc->Ellipse( rect );

	rect.InflateRect( -12, -12 );
	dc->SelectStockObject( LTGRAY_BRUSH );
	dc->Ellipse( rect );

	dc->SelectStockObject( BLACK_PEN );

}

void CDiagramEditor::MoveObject( CDiagramEntity* obj, const CRect& rect )
/* ============================================================
	Function :		CDiagramEditor::MoveObject
	Description :	Moves an object to "rect"
	Access :		Protected

	Return :		void
	Parameters :	CDiagramEntity* obj	-	Object to move
					const CRect& rect	-	New position
					
	Usage :			This function can be overridden to trap
					object movement.

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

	obj->SetRect( rect );

}

HCURSOR CDiagramEditor::GetCursor()
/* ============================================================
	Function :		CDiagramEditor::GetCursor
	Description :	Gets a handle to the cursor appropriate 
					depending on mode, position etc.
	Access :		Protected

	Return :		HCURSOR	-	Appropriate cursor
	Parameters :	none

	Usage :			Call to get a cursor to display depending on 
					the current context.

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

	HCURSOR result = NULL;

	if( m_panning )
	{
		const MSG* msg = GetCurrentMessage();
		CPoint test = GetPanningOrigin();
		CPoint point( msg->pt );
		ScreenToClient( &point );

		if( point.x > test.x + 16 )
		{
			if ( point.y > test.y + 16 )
				result = m_cursorSouthEast;
			else if( point.y < test.y - 16 )
				result = m_cursorNorthEast;
			else
				result = m_cursorEast;
		}
		else if( point.x < test.x - 16 )
		{
			if ( point.y > test.y + 16 )
				result = m_cursorSouthWest;
			else if( point.y < test.y  - 16 )
				result = m_cursorNorthWest;
			else
				result = m_cursorWest;
		}
		else if( point.y < test.y )
			result = m_cursorSouth;
		else
			result = m_cursorNorth;


	}
	else if( m_interactMode == MODE_DRAWING )
	{
		result = LoadCursor( NULL, IDC_CROSS );
	}
	else if( m_objs )
	{

		int count = 0;
		CDiagramEntity* obj = NULL;
		const MSG* msg = GetCurrentMessage();
		CPoint point( msg->pt );
		ScreenToClient( &point );

		while( ( obj = m_objs->GetAt( count++ ) ) )
		{
			if( obj->IsSelected() )
			{

				CRect rect = obj->GetRect();
				VirtualToScreen( rect );

				int hitCode = obj->GetHitCode( point, rect );
				if( hitCode != DEHT_NONE && hitCode != DEHT_BODY )
					result = obj->GetCursor( hitCode );

			}
		}

		if( result == NULL )
		{
			int hitCode = GetHitCode( point );
			if( hitCode != DEHT_NONE && hitCode != DEHT_BODY && m_bgResizeSelected )
				result = GetCursor( hitCode );
		}
	}

	return result;

}

void CDiagramEditor::SetScrollWheelMode( int mode )
/* ============================================================
	Function :		CDiagramEditor::SetScrollWheelMode
	Description :	Accessor for the scroll wheel mode flag.
	Access :		Public

	Return :		void
	Parameters :	int mode	-	New mode
					
	Usage :			"mode" can be any of
						"WHEEL_SCROLL" The editor will scroll when the scroll wheel is rolled.
						"WHEEL_ZOOM" The editor will zoom when the scroll wheel is rolled.
   ============================================================*/
{

	m_scrollwheel = mode;

}

int CDiagramEditor::GetScrollWheelMode() const
/* ============================================================
	Function :		CDiagramEditor::GetScrollWheelMode
	Description :	Accessor for the scroll wheel mode flag.
	Access :		Public

	Return :		int	-	Current mode
	Parameters :	none

	Usage :			The mode can be any of
						"WHEEL_SCROLL" The editor will scroll when the scroll wheel is rolled.
						"WHEEL_ZOOM" The editor will zoom when the scroll wheel is rolled.
   ============================================================*/
{

	return m_scrollwheel;

}

void CDiagramEditor::SetHScroll( int pos )
/* ============================================================
	Function :		CDiagramEditor::SetHScroll
	Description :	Sets the horizontal scrollbar position to 
					"pos".
	Access :		Protected

	Return :		void
	Parameters :	int pos	-	New position
					
	Usage :			Called when the scroll bar position is set. 
					Can be overrided to trigger scroll bar 
					events.

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

	pos = max( 0, pos );

	SCROLLINFO si;
	si.cbSize = sizeof( SCROLLINFO );
	si.fMask = SIF_POS;
	si.nPos = pos;

	SetScrollInfo( SB_HORZ, &si );

}

void CDiagramEditor::SetVScroll( int pos )
/* ============================================================
	Function :		CDiagramEditor::SetVScroll
	Description :	Sets the vertical scrollbar position to 
					"pos".
	Access :		Protected

	Return :		void
	Parameters :	int pos	-	New position
					
	Usage :			Called when the scroll bar position is set. 
					Can be overrided to trigger scroll bar 
					events.

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

	pos = max( 0, pos );

	SCROLLINFO si;
	si.cbSize = sizeof( SCROLLINFO );
	si.fMask = SIF_POS;
	si.nPos = pos;

	SetScrollInfo( SB_VERT, &si );

}

BOOL CDiagramEditor::OutsideRestraints( CPoint point )
/* ============================================================
	Function :		CDiagramEditor::OutsideRestraints
	Description :	Checks if "point" lies outside the current 
					restraints.
	Access :		Protected

	Return :		BOOL			-	"TRUE" if "point" is 
										outside the current 
										constraints.
	Parameters :	CPoint point	-	Coordinates to check.
					
	Usage :			Call to see if coordinates are outside the 
					current restraints.

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

	BOOL result = FALSE;


	if( m_restraint == RESTRAINT_VIRTUAL )
	{

		if( point.x < 0 )
			result = TRUE;
		if( point.y < 0 )
			result = TRUE;

		if( point.x > GetVirtualSize().cx )
			result = TRUE;
		if( point.y > GetVirtualSize().cy )
			result = TRUE;

	}
	else if( m_restraint == RESTRAINT_MARGIN )
	{

		if( point.x < m_leftMargin )
			result = TRUE;
		if( point.y < m_topMargin )
			result = TRUE;

		if( point.x > GetVirtualSize().cx - m_rightMargin )
			result = TRUE;
		if( point.y > GetVirtualSize().cy - m_bottomMargin )
			result = TRUE;

	}

	return result;

}

void CDiagramEditor::ScrollIntoView()
/* ============================================================
	Function :		CDiagramEditor::ScrollIntoView
	Description :	Scrolls the selected items into view.
	Access :		Public

	Return :		void
	Parameters :	none

	Usage :			Call to scroll the selected item(s) into 
					view.

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

	if( GetSelectCount() )
	{
		CPoint start = GetDiagramEntityContainer()->GetStartPoint();
		CSize objects = GetDiagramEntityContainer()->GetTotalSize();
		CRect rect;
		GetClientRect( rect );
		CSize screen( rect.Width(), rect.Height() );
		if( objects.cx > screen.cx || objects.cy > screen.cy )
		{

			ZoomToFitScreen();

		}
		else
		{

			int leftmargin = ( screen.cx - objects.cx ) / 2;
			int topmargin = ( screen.cy - objects.cy ) / 2;
			int leftstart = start.x - leftmargin;
			int topstart = start.y - topmargin;

			SetHScroll( leftstart );
			SetVScroll( topstart );
			RedrawWindow();

		}
	}

}

void CDiagramEditor::ScrollIntoView( CDiagramEntity* obj )
/* ============================================================
	Function :		CDiagramEditor::ScrollIntoView
	Description :	Scrolls "obj" into view.
	Access :		Public

	Return :		void
	Parameters :	CDiagramEntity* obj	-	Object to scroll 
											into view

	Usage :			Call to scroll "obj" into view.

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

	CRect objrect = obj->GetRect();
	objrect.NormalizeRect();

	CPoint start = objrect.TopLeft();
	CSize objects( objrect.Width(), objrect.Height() );
	CRect rect;
	GetClientRect( rect );
	CSize screen( rect.Width(), rect.Height() );
	if( objects.cx > screen.cx || objects.cy > screen.cy )
	{

		ZoomToFitScreen();

	}
	else
	{

		int leftmargin = ( screen.cx - objects.cx ) / 2;
		int topmargin = ( screen.cy - objects.cy ) / 2;
		int leftstart = start.x - leftmargin;
		int topstart = start.y - topmargin;

		SetHScroll( leftstart );
		SetVScroll( topstart );
		RedrawWindow();

	}

}

#pragma warning( default : 4706 )

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under A Public Domain dedication


Written By
Software Developer (Senior) Abstrakt Mekanik AB
Sweden Sweden
45 years old, married, three kids.

Started with computers more than 20 years ago on a CBM-64.

Read Theoretical Philosophy at the University of Lund.

Working as a C++ consultant developer.

Science-fiction freak. Enjoy vintage punkrock.

Comments and Discussions