Click here to Skip to main content
15,860,972 members
Articles / Desktop Programming / WTL

Form Designer

26 Jul 2021CPOL24 min read 349.8K   82.5K   230  
Component for adding scriptable forms capabilities to an application.
// FormEditor.cpp : Implementation of CFormEditor
//
// Author : David Shepherd
//			Copyright (c) 2002, DaeDoe-Software
//
/////////////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "DDForms.h"
#include "FormEditor.h"

#include "FormEditorItemCollection.h"
#include "FormEditorItemDetails.h"
#include "FormPropertiesAccessor.h"
#include "InsertItemDlg.h"
#include "Script.h"

#import <msscript.ocx> raw_interfaces_only

#import "..\DDPropPageAll\DDPropPageAll.tlb"

// minimum form dimensions
#define MIN_FORM_WIDTH			50
#define MIN_FORM_HEIGHT			50
// maximum form dimensions
#define MAX_FORM_WIDTH			2500
#define MAX_FORM_HEIGHT			2500

// minimum item dimensions
#define MIN_ITEM_WIDTH			10
#define MIN_ITEM_HEIGHT			10
// default item dimensions
#define DEFAULT_ITEM_WIDTH		50
#define DEFAULT_ITEM_HEIGHT		50

// minimum grid dimensions
#define MIN_GRID_WIDTH			1
#define MIN_GRID_HEIGHT			1

// number of horizontal and vertical pixels to scroll per line
#define HSCROLL_LINE_SIZE		10
#define VSCROLL_LINE_SIZE		10

// drag rect size (the width and height are the same)
#define DRAG_RECT_SIZE			2

// custom clipboard format name
#define CLIPBOARD_FORMAT_NAME	_T("{A20C77E1-2C45-11d6-B6A6-F2FFFB705547}")

// default form name
#define DEFAULT_FORM_NAME		L"Form"

// default item base name
#define DEFAULT_ITEM_BASE_NAME	L"Object"

// storage stream name
#define STREAM_NAME				L"DaeDoeForm"

// timer id
#define TIMER_ID				1
// timer elapse time (in milliseconds)
#define TIMER_ELAPSE			250

// dictates whether unlocking is allowed
#define CAN_UNLOCK				TRUE
// default locked mode
#define DEFAULT_LOCKED_MODE		TRUE
// maximum number of items which can be hosted in locked mode
#define MAX_LOCKED_MODE_ITEMS	6
// unlock key
#define UNLOCK_KEY				0x49ACC539,0x07ABFA45,0x7629FEDE,0xEDA76CB0

namespace AxFormEditor {

/////////////////////////////////////////////////////////////////////////////
// CExtendedPropPageData

CExtendedPropPageData::CExtendedPropPageData()
{
	// initialise everything
}

CExtendedPropPageData::~CExtendedPropPageData()
{
	// clean up
}

/////////////////////////////////////////////////////////////////////////////
// CItemInfo

CItemInfo::CItemInfo()
{
	// initialise everything
	m_LastDragRect=CRect(0,0,0,0);
}

CItemInfo::CItemInfo(const CItemInfo &ItemInfo)
{
	ATLASSERT(FALSE);	// not allowed
}

CItemInfo::~CItemInfo()
{
	// clean up
	if(m_HostWindow.IsWindow())		// host window
	{
		ATLASSERT(FALSE);
		(void)m_HostWindow.DestroyWindow();
	}
	if(m_DragFrame.IsWindow())		// drag frame
	{
		ATLASSERT(FALSE);
		(void)m_DragFrame.DestroyWindow();
	}
	if(m_TabNumber.IsWindow())		// tab number
	{
		ATLASSERT(FALSE);
		(void)m_TabNumber.DestroyWindow();
	}
}

const CItemInfo &CItemInfo::operator=(const CItemInfo &ItemInfo)
{
	ATLASSERT(FALSE);	// not allowed
	return *this;
}

CItemInfo *CItemInfo::CreateObject()
{
	// create a new object
	return new CItemInfo;
}

void CItemInfo::DeleteObject(const CItemInfo *pItemInfo)
{
	// delete an existing object
	ATLASSERT(pItemInfo!=NULL);
	delete pItemInfo;
}

/////////////////////////////////////////////////////////////////////////////
// CItemInfo predicates

bool IsItemTabNumberLess(
	const CItemInfo *p1,const CItemInfo *p2)
{
	// check the item info pointers
	ATLASSERT(p1!=NULL and p2!=NULL);

	// get the tab number for item 1
	long TabNumber1=p1->m_TabNumber.GetTabNumber();
	// get the tab number for item 2
	long TabNumber2=p2->m_TabNumber.GetTabNumber();
	// compare the tab numbers
	return (TabNumber1 < TabNumber2) ? true : false;
}

bool IsItemHorzontalPositionLess(
	const CItemInfo *p1,const CItemInfo *p2)
{
	// check the item info pointers
	ATLASSERT(p1!=NULL and p2!=NULL);

	// get the item rect for item 1
	CRect ItemRect1(0,0,0,0);
	(void)p1->m_HostWindow.GetWindowRect(ItemRect1);
	// get the item rect for item 2
	CRect ItemRect2(0,0,0,0);
	(void)p2->m_HostWindow.GetWindowRect(ItemRect2);
	// compare the horizontal positions
	return (ItemRect1.left < ItemRect2.left) ? true : false;
}

bool IsItemVerticalPositionLess(
	const CItemInfo *p1,const CItemInfo *p2)
{
	// check the item info pointers
	ATLASSERT(p1!=NULL and p2!=NULL);

	// get the item rect for item 1
	CRect ItemRect1(0,0,0,0);
	(void)p1->m_HostWindow.GetWindowRect(ItemRect1);
	// get the item rect for item 2
	CRect ItemRect2(0,0,0,0);
	(void)p2->m_HostWindow.GetWindowRect(ItemRect2);
	// compare the vertical positions
	return (ItemRect1.top < ItemRect2.top) ? true : false;
}

bool IsItemIDispatchEqual(
	CItemInfo *p1,const IDispatch *p2)
{
	// check the item info pointer
	ATLASSERT(p1!=NULL);
	// check the IDispatch pointer
	ATLASSERT(p2!=NULL);

	// get the items IDispatch interface
	CComPtr<IDispatch> spDispatch;
	(void)p1->m_HostWindow.QueryControl(&spDispatch);
	// compare the interfaces
	return (spDispatch==p2) ? true : false;
}

/////////////////////////////////////////////////////////////////////////////
// CEventInfo

CEventInfo::CEventInfo()
{
	// initialise everything
}

CEventInfo::~CEventInfo()
{
	// clean up
}

/////////////////////////////////////////////////////////////////////////////
// CFormEditorState

CFormEditorState::CFormEditorState()
{
	// initialise everything
	m_Busy=FALSE;
	m_Modified=FALSE;
	m_CanPaste=FALSE;
	m_CanUndo=FALSE;
	m_CanRedo=FALSE;
	m_ItemCount=0;
	m_SelectedItemCount=0;
}

CFormEditorState::~CFormEditorState()
{
	// clean up
}

BOOL CFormEditorState::operator==(const CFormEditorState &Obj) const
{
	// test for equality
	if(	m_Busy==Obj.m_Busy			 and
		m_Modified==Obj.m_Modified	 and
		m_CanPaste==Obj.m_CanPaste	 and
		m_CanUndo==Obj.m_CanUndo	 and
		m_CanRedo==Obj.m_CanRedo	 and
		m_ItemCount==Obj.m_ItemCount and
		m_SelectedItemCount==Obj.m_SelectedItemCount)
	{
		return TRUE;	// equal
	}
	return FALSE;		// not equal
}

BOOL CFormEditorState::operator!=(const CFormEditorState &Obj) const
{
	// test for unequality
	return (*this==Obj) ? FALSE : TRUE;
}

/////////////////////////////////////////////////////////////////////////////
// CFormEditor

CFormEditor::CFormEditor()
{
	// initialise everything
	m_bWindowOnly=TRUE;
	// form
	m_FormName=DEFAULT_FORM_NAME;
	m_LastFormDragRect=CRect(0,0,0,0);
	// selection box
	m_SelectionBoxActive=FALSE;
	m_SelectionBoxAnchorPos=CPoint(0,0);
	m_LastSelectionBoxDragRect=CRect(0,0,0,0);
	// clipboard
	m_ClipboardFormatId=0;
	// tab ordering
	m_TabOrderingModeActive=FALSE;
	m_NextTabNumber=1;
	// state
	m_Busy=FALSE;
	m_Modified=FALSE;
	m_CanPaste=FALSE;
	// locked mode
	m_LockedModeActive=DEFAULT_LOCKED_MODE;
	// properties
	m_BorderVisible=VARIANT_TRUE;
	m_BackColor=MAKE_OLE_COLOR(COLOR_WINDOW);
	m_Form.GetDimensions(m_DefaultFormWidth,m_DefaultFormHeight);
	m_Form.GetColors(m_DefaultFormBackColor,m_DefaultFormForeColor);
}

CFormEditor::~CFormEditor()
{
	// clean up
}

BOOL CFormEditor::InRunMode()
{
	// determine if run mode is active
	// since the user mode ambient property may not be supported by
	// the container we assume run mode if this fails
	BOOL RunMode=TRUE;
	(void)GetAmbientUserMode(RunMode);
	return VB2B(RunMode);
}

BOOL CFormEditor::InDesignMode()
{
	// determine if design mode is active
	return (InRunMode()) ? FALSE : TRUE;
}

void CFormEditor::SetFormDimensions(long Width,long Height)
{
	// set the form dimensions
	m_Form.SetDimensions(Width,Height);
	PROPERTY_CHANGED(DISPID_UNKNOWN);	// multiple properties
}

void CFormEditor::SetFormColors(OLE_COLOR BackColor,OLE_COLOR ForeColor)
{
	// set the form colors
	m_Form.SetColors(BackColor,ForeColor);
	// update the ambient properties for all host windows
	for(CItemInfoPtrList::const_iterator iter=
		m_ItemInfoPtrList.begin(); iter!=m_ItemInfoPtrList.end(); iter++)
	{
		// get the host window IAxWinAmbientDispatch interface
		CComPtr<IAxWinAmbientDispatch> spAxWinAmbientDispatch;
		if(!SUCCEEDED((*iter)->m_HostWindow.QueryHost(&spAxWinAmbientDispatch)))
		{
			throw std::exception();
		}
		// background color
		if(!SUCCEEDED(spAxWinAmbientDispatch->put_BackColor(BackColor)))
		{
			throw std::exception();
		}
		// foreground color
		if(!SUCCEEDED(spAxWinAmbientDispatch->put_ForeColor(ForeColor)))
		{
			throw std::exception();
		}
	}
	PROPERTY_CHANGED(DISPID_UNKNOWN);	// multiple properties
}

CItemInfo *CFormEditor::HitTest(const CPoint &Pos)
{
	// start from the top item and work down to the bottom
	for(CItemInfoPtrList::reverse_iterator iter=
		m_ItemInfoPtrList.rbegin(); iter!=m_ItemInfoPtrList.rend(); iter++)
	{
		// see if the item is at the passed position
		if(GetItemRect(**iter).PtInRect(Pos))
		{
			return *iter;
		}
	}
	return NULL;	// not found
}

void CFormEditor::SelectForm()
{
	// there should be no items selected
	ATLASSERT(GetSelectedItemCount()==0);
	// if the form is not already selected
	if(IsFormSelected()==FALSE)
	{
		// select the form
		DWORD EnabledDragHandles=
			CDragFrame::DragHandleRight		  |
			CDragFrame::DragHandleBottomRight |
			CDragFrame::DragHandleBottom;
		(void)m_FormDragFrame.Create(
			*this,m_Form,this,EnabledDragHandles,FALSE,TRUE);
	}
}

void CFormEditor::UnselectForm()
{
	// if the form is currently selected
	if(IsFormSelected())
	{
		// unselect the form
		(void)m_FormDragFrame.DestroyWindow();
	}
}

BOOL CFormEditor::IsFormSelected()
{
	// determine if the form is selected
	return m_FormDragFrame.IsWindow() ? TRUE : FALSE;
}

void CFormEditor::SelectItem(CItemInfo &ItemInfo)
{
	// the form should not be selected
	ATLASSERT(IsFormSelected()==FALSE);
	// if the item is not already selected
	if(IsItemSelected(ItemInfo)==FALSE)
	{
		// if there are other items selected
		long SelectedItemCount=GetSelectedItemCount();
		if(SelectedItemCount==1)	// only need to do this once
		{
			// disable all drag frame handles for the selected item
			GetSelectedItems().front()->m_DragFrame.
				EnableDragHandles(CDragFrame::DragHandleNone);
		}
		// select the item
		DWORD EnabledDragHandles=(SelectedItemCount==0) ?
			CDragFrame::DragHandleAll : CDragFrame::DragHandleNone;
		(void)ItemInfo.m_DragFrame.Create(
			*this,ItemInfo.m_HostWindow,this,EnabledDragHandles);
	}
}

void CFormEditor::SelectAllItemsInRect(const CRect &Rect)
{
	// select all items contained in the passed rectangle
	for(CItemInfoPtrList::const_iterator iter=
		m_ItemInfoPtrList.begin(); iter!=m_ItemInfoPtrList.end(); iter++)
	{
		// get the item rect
		CRect ItemRect=GetItemRect(**iter);
		// select the item if fully contained in the passed rectangle
		CRect IntersectRect(0,0,0,0);
		(void)IntersectRect.IntersectRect(Rect,ItemRect);
		if(IntersectRect==ItemRect)
		{
			SelectItem(**iter);
		}
	}
}

void CFormEditor::SelectItems(const CItemInfoPtrList &ItemInfoPtrList)
{
	// select the passed items
	for(CItemInfoPtrList::const_iterator iter=
		ItemInfoPtrList.begin(); iter!=ItemInfoPtrList.end(); iter++)
	{
		SelectItem(**iter);
	}
}

void CFormEditor::SelectAllItems()
{
	// select all items
	SelectItems(m_ItemInfoPtrList);
}

void CFormEditor::UnselectItem(CItemInfo &ItemInfo)
{
	// if the item is currently selected
	if(IsItemSelected(ItemInfo))
	{
		// unselect the item
		(void)ItemInfo.m_DragFrame.DestroyWindow();
	}
	// if there is only one item left selected
	if(GetSelectedItemCount()==1)
	{
		// enable all drag frame handles for the selected item
		GetSelectedItems().front()->m_DragFrame.
			EnableDragHandles(CDragFrame::DragHandleAll);
	}
}

void CFormEditor::UnselectItems(const CItemInfoPtrList &ItemInfoPtrList)
{
	// unselect the passed items
	for(CItemInfoPtrList::const_iterator iter=
		ItemInfoPtrList.begin(); iter!=ItemInfoPtrList.end(); iter++)
	{
		UnselectItem(**iter);
	}
}

void CFormEditor::UnselectAllItems()
{
	// unselect all items
	UnselectItems(m_ItemInfoPtrList);
}

BOOL CFormEditor::IsItemSelected(CItemInfo &ItemInfo)
{
	// determine if the item is selected
	return ItemInfo.m_DragFrame.IsWindow() ? TRUE : FALSE;
}

CItemInfoPtrList CFormEditor::GetSelectedItems()
{
	// return a list of selected items
	CItemInfoPtrList ItemInfoPtrList;
	for(CItemInfoPtrList::const_iterator iter=
		m_ItemInfoPtrList.begin(); iter!=m_ItemInfoPtrList.end(); iter++)
	{
		if(IsItemSelected(**iter))	// if the item is selected
		{
			// update the list
			ItemInfoPtrList.push_back(*iter);
		}
	}
	return ItemInfoPtrList;
}

DWORD CFormEditor::GetSelectedItemCount()
{
	// return the number of selected items
	DWORD Count=0;
	for(CItemInfoPtrList::const_iterator iter=
		m_ItemInfoPtrList.begin(); iter!=m_ItemInfoPtrList.end(); iter++)
	{
		if(IsItemSelected(**iter))	// if the item is selected
		{
			// update the count
			Count++;
		}
	}
	return Count;
}

void CFormEditor::HideAllDragFrames()
{
	// hide all drag frames
	if(IsFormSelected())
	{
		// form drag frame
		(void)m_FormDragFrame.ShowWindow(SW_HIDE);
	}
	// item drag frames
	CItemInfoPtrList ItemInfoPtrList=GetSelectedItems();
	for(CItemInfoPtrList::const_iterator iter=
		ItemInfoPtrList.begin(); iter!=ItemInfoPtrList.end(); iter++)
	{
		CItemInfo &ItemInfo=**iter;
		// hide the drag frame
		(void)ItemInfo.m_DragFrame.ShowWindow(SW_HIDE);
	}
}

void CFormEditor::RestoreAndRepositionAllDragFrames()
{
	// restore and reposition all drag frames
	if(IsFormSelected())
	{
		// form drag frame
		m_FormDragFrame.AutoPositionAndSize();
		(void)m_FormDragFrame.ShowWindow(SW_SHOW);
	}
	// item drag frames
	CItemInfoPtrList ItemInfoPtrList=GetSelectedItems();
	for(CItemInfoPtrList::const_iterator iter=
		ItemInfoPtrList.begin(); iter!=ItemInfoPtrList.end(); iter++)
	{
		CItemInfo &ItemInfo=**iter;
		// restore and reposition the drag frame
		ItemInfo.m_DragFrame.AutoPositionAndSize();
		(void)ItemInfo.m_DragFrame.ShowWindow(SW_SHOW);
	}
}

void CFormEditor::HideAllTabNumbers()
{
	// hide all tab numbers
	for(CItemInfoPtrList::const_iterator iter=
		m_ItemInfoPtrList.begin(); iter!=m_ItemInfoPtrList.end(); iter++)
	{
		CItemInfo &ItemInfo=**iter;
		// hide the tab number
		(void)ItemInfo.m_TabNumber.ShowWindow(SW_HIDE);
	}
}

void CFormEditor::RestoreAndRepositionAllTabNumbers()
{
	// restore and reposition all tab numbers
	for(CItemInfoPtrList::const_iterator iter=
		m_ItemInfoPtrList.begin(); iter!=m_ItemInfoPtrList.end(); iter++)
	{
		CItemInfo &ItemInfo=**iter;
		// restore and reposition the tab number
		ItemInfo.m_TabNumber.AutoPosition();
		(void)ItemInfo.m_TabNumber.ShowWindow(SW_SHOW);
	}
}

void CFormEditor::HideAllDragFramesAndTabNumbers()
{
	// hide all drag frames and tab numbers
	HideAllDragFrames();
	HideAllTabNumbers();
}

void CFormEditor::RestoreAndRepositionAllDragFramesAndTabNumbers()
{
	// restore and reposition all drag frames and tab numbers
	RestoreAndRepositionAllDragFrames();
	RestoreAndRepositionAllTabNumbers();
}

CRect CFormEditor::GetItemRect(CItemInfo &ItemInfo)
{
	// get the item rect reletive to the screen
	CRect ItemRect(0,0,0,0);
	(void)ItemInfo.m_HostWindow.GetWindowRect(ItemRect);
	// convert to form coordinates
	(void)m_Form.ScreenToClient(ItemRect);
	return ItemRect;
}

CRect CFormEditor::GetBoundingRectForItems(
	const CItemInfoPtrList &ItemInfoPtrList)
{
	// return the rectangle which encapsulates the passed items
	CRect BoundingRect(0,0,0,0);
	for(CItemInfoPtrList::const_iterator iter=
		ItemInfoPtrList.begin(); iter!=ItemInfoPtrList.end(); iter++)
	{
		// get the item rect
		CRect ItemRect=GetItemRect(**iter);
		// initialise the bounding rectangle on the first pass
		if(iter==ItemInfoPtrList.begin())
		{
			BoundingRect=ItemRect;
		}
		// update the bounding rectangle
		(void)BoundingRect.UnionRect(BoundingRect,ItemRect);
	}
	return BoundingRect;
}

CRect CFormEditor::ForceRectIntoRect(
	const CRect &Destination,const CRect &Source)
{
	CRect NewRect=Source;
	// check the width is not too large
	if(NewRect.Width() > Destination.Width())
	{
		NewRect.right=NewRect.left+Destination.Width();
	}
	// check the height is not too large
	if(NewRect.Height() > Destination.Height())
	{
		NewRect.bottom=NewRect.top+Destination.Height();
	}
	// check the horizontal positioning
	if(NewRect.left < 0)
	{
		NewRect.OffsetRect(-NewRect.left,0);
	}
	if(NewRect.right > Destination.right)
	{
		NewRect.OffsetRect(Destination.right-NewRect.right,0);
	}
	// check the vertical positioning
	if(NewRect.top < 0)
	{
		NewRect.OffsetRect(0,-NewRect.top);
	}
	if(NewRect.bottom > Destination.bottom)
	{
		NewRect.OffsetRect(0,Destination.bottom-NewRect.bottom);
	}
	return NewRect;
}

CRect CFormEditor::GetBoundingRectForSelectedItems()
{
	// return the rectangle which encapsulates all selected items
	return GetBoundingRectForItems(GetSelectedItems());
}

CRect CFormEditor::GetBoundingRectForAllItems()
{
	// return the rectangle which encapsulates all items
	return GetBoundingRectForItems(m_ItemInfoPtrList);
}

CPoint CFormEditor::ClientPosToFormPos(const CPoint &Pos)
{
	// convert client position coordinates to form coordinates
	CPoint NewPos=Pos;
	(void)ClientToScreen(&NewPos);
	(void)m_Form.ScreenToClient(&NewPos);
	return NewPos;
}

CRect CFormEditor::ClientRectToFormRect(const CRect &Rect)
{
	// convert client rectangle coordinates to form coordinates
	CRect NewRect=Rect;
	(void)ClientToScreen(NewRect);
	(void)m_Form.ScreenToClient(NewRect);
	return NewRect;
}

CPoint CFormEditor::SnapPosToGrid(const CPoint &Pos)
{
	// get grid settings
	long Width=1;
	long Height=1;
	// if the grid is not visible ignore the settings
	if(m_Form.IsGridVisible())
	{
		m_Form.GetGrid(Width,Height);
	}
	// snap the passed position to the gird
	CPoint NewPos=Pos;
	NewPos.x=Width*(NewPos.x/Width);
	NewPos.y=Height*(NewPos.y/Height);
	return NewPos;
}

CRect CFormEditor::SnapRectToGrid(const CRect &Rect)
{
	// determine the snap offset
	CPoint Pos=Rect.TopLeft();
	CPoint NewPos=SnapPosToGrid(Pos);
	CPoint Offset=(NewPos-Pos);
	// snap the passed rectangle to the grid
	CRect NewRect=Rect;
	NewRect.OffsetRect(Offset);
	return NewRect;
}

CItemInfo &CFormEditor::InsertItem(const CLSID &ClassId,const CRect &Rect)
{
	// insert an item given the class id
	return CreateItem(L"",L"",Rect,ClassId,CComPtr<IStream>());
}

CItemInfo &CFormEditor::InsertItem(const std::wstring &ProgId,const CRect &Rect)
{
	// insert an item give the prog id
	CLSID ClassId=CLSID_NULL;
	if(!SUCCEEDED(CLSIDFromProgID(ProgId.c_str(),&ClassId)))
	{
		throw std::exception();
	}
	return InsertItem(ClassId,Rect);
}

void CFormEditor::DeleteItem(CItemInfo &ItemInfo)
{
	// ensure the item is not selected
	UnselectItem(ItemInfo);

	// remove item info from the list
	CItemInfoPtrList::iterator iter=std::find(
		m_ItemInfoPtrList.begin(),m_ItemInfoPtrList.end(),&ItemInfo);
	ATLASSERT(iter!=m_ItemInfoPtrList.end());	// should always exist
	m_ItemInfoPtrList.erase(iter);				// remove item info

	// clean up item info
	(void)ItemInfo.m_HostWindow.DestroyWindow();
	ATLASSERT(ItemInfo.m_DragFrame.IsWindow()==FALSE);
	ATLASSERT(ItemInfo.m_TabNumber.IsWindow()==FALSE);
	// delete item info
	CItemInfo::DeleteObject(&ItemInfo);
}

void CFormEditor::DeleteSelectedItems()
{
	// delete all selected items
	CItemInfoPtrList ItemInfoPtrList=GetSelectedItems();
	CItemInfoPtrList::const_iterator iter=ItemInfoPtrList.begin();
	while(iter!=ItemInfoPtrList.end())
	{
		// we need to be careful here since the iterator
		// will become invalid for the deleted item
		CItemInfoPtrList::const_iterator iter_del=iter++;
		DeleteItem(**iter_del);
	}
}

void CFormEditor::DeleteAllItems()
{
	// delete all items
	CItemInfoPtrList::const_iterator iter=m_ItemInfoPtrList.begin();
	while(iter!=m_ItemInfoPtrList.end())
	{
		// we need to be careful here since the iterator
		// will become invalid for the deleted item
		CItemInfoPtrList::const_iterator iter_del=iter++;
		DeleteItem(**iter_del);
	}
}

void CFormEditor::GetItemPosition(
	CItemInfo &ItemInfo,CRect &Rect,long &TabNumber)
{
	// get the item rect
	Rect=GetItemRect(ItemInfo);
	// find item info in the list
	CItemInfoPtrList::const_iterator iter=std::find(
		m_ItemInfoPtrList.begin(),m_ItemInfoPtrList.end(),&ItemInfo);
	ATLASSERT(iter!=m_ItemInfoPtrList.end());	// should always exist
	// the item tab number is the same as the item index but 1 based
	TabNumber=std::distance(
		(CItemInfoPtrList::const_iterator)m_ItemInfoPtrList.begin(),iter)+1;
}

void CFormEditor::SetItemPosition(
	CItemInfo &ItemInfo,const CRect &Rect,long TabNumber)
{
	// the item should fit within the form
#ifdef _DEBUG
	CRect FormRect(0,0,0,0);
	(void)m_Form.GetClientRect(FormRect);
	ATLASSERT(Rect.left	  >= 0);
	ATLASSERT(Rect.top	  >= 0);
	ATLASSERT(Rect.right  <= FormRect.right);
	ATLASSERT(Rect.bottom <= FormRect.bottom);
#endif
	// the tab number should be valid
	ATLASSERT(TabNumber >= 1 and TabNumber <= (long)m_ItemInfoPtrList.size());

	// remove item info from the list
	CItemInfoPtrList::iterator iter_delete=std::find(
		m_ItemInfoPtrList.begin(),m_ItemInfoPtrList.end(),&ItemInfo);
	ATLASSERT(iter_delete!=m_ItemInfoPtrList.end());	// should always exist
	m_ItemInfoPtrList.erase(iter_delete);				// remove item info
	CAutoItemInfoPtr ItemInfoPtr(&ItemInfo);
	// insert item info back into the list at the correct position
	CItemInfoPtrList::iterator iter_insert=m_ItemInfoPtrList.begin();
	std::advance(iter_insert,TabNumber-1);
	m_ItemInfoPtrList.insert(iter_insert,&ItemInfo);
	(void)ItemInfoPtr.Release();
	// determine the window to insert the item after
	HWND InsertAfter=m_ItemRootWindow;
	if(TabNumber < (long)m_ItemInfoPtrList.size())
	{
		// advance to the next item
		CItemInfoPtrList::const_iterator iter_next=m_ItemInfoPtrList.begin();
		std::advance(iter_next,TabNumber);
		// get the item host window
		InsertAfter=(**iter_next).m_HostWindow;
	}
	// set the item position
	(void)ItemInfo.m_HostWindow.SetWindowPos(
		InsertAfter,Rect,SWP_NOACTIVATE|SWP_NOCOPYBITS);
}

void CFormEditor::CutSelectedItemsToClipboard()
{
	// copy selected items to the clipboard
	CopySelectedItemsToClipboard();
	// delete selected items
	DeleteSelectedItems();
}

void CFormEditor::CopySelectedItemsToClipboard()
{
	// create the memory stream
	CAutoGlobalMemory GlobalMem(
		GlobalAlloc(GMEM_MOVEABLE|GMEM_NODISCARD|GMEM_DDESHARE,0));
	if(GlobalMem==NULL)
	{
		throw std::exception();
	}
	CComPtr<IStream> spStream;
	if(!SUCCEEDED(CreateStreamOnHGlobal(GlobalMem,FALSE,&spStream)))
	{
		throw std::exception();
	}
	// save selected items to the memory stream
	InternalSave(spStream,StreamTypeClipboard);

	// open the clipboard
	CAutoClipboard Clipboard(*this);
	// empty the clipboard and become the new owner
	if(EmptyClipboard()==FALSE)
	{
		throw std::exception();
	}
	// set the clipboard data
	if(SetClipboardData(
		m_ClipboardFormatId,GlobalMem.Release())==NULL)
	{
		throw std::exception();
	}
}

CItemInfoPtrList CFormEditor::PasteItemsFromClipboard()
{
	// open the clipboard
	CAutoClipboard Clipboard(*this);
	// get the clipboard data
	HGLOBAL hGlobal=::GetClipboardData(m_ClipboardFormatId);
	if(hGlobal==NULL)
	{
		throw std::exception();
	}
	// create the memory stream
	CComPtr<IStream> spStream;
	if(!SUCCEEDED(CreateStreamOnHGlobal(hGlobal,FALSE,&spStream)))
	{
		throw std::exception();
	}
	// create items from the memory stream
	return InternalLoad(spStream,StreamTypeClipboard);
}

void CFormEditor::UpdateUndoStorage(BOOL PreserveRedoStorage/*=FALSE*/)
{
	// save the current form into the undo storage
	InternalSave(
		m_UndoStorage.CreateNewElement(),StreamTypeUndoRedo);
	// clear the redo storage
	if(PreserveRedoStorage==FALSE)
	{
		m_RedoStorage.RemoveAllElements();
	}
}

void CFormEditor::UpdateRedoStorage()
{
	// save the current form into the redo storage
	InternalSave(
		m_RedoStorage.CreateNewElement(),StreamTypeUndoRedo);
}

void CFormEditor::UndoLastChange()
{
	// update the redo storage
	UpdateRedoStorage();
	// load the form from the undo storage
	(void)InternalLoad(
		m_UndoStorage.GetStreamOnLastElement(),StreamTypeUndoRedo);
	// remove
	m_UndoStorage.RemoveLastElement();
}

void CFormEditor::RedoLastUndo()
{
	// update the undo storage
	UpdateUndoStorage(TRUE);
	// load the form from the redo storage
	(void)InternalLoad(
		m_RedoStorage.GetStreamOnLastElement(),StreamTypeUndoRedo);
	// remove
	m_RedoStorage.RemoveLastElement();
}

void CFormEditor::ClearUndoStorage()
{
	// clear all undo storage elements
	m_UndoStorage.RemoveAllElements();
}

void CFormEditor::ClearRedoStorage()
{
	// clear all redo storage elements
	m_RedoStorage.RemoveAllElements();
}

void CFormEditor::ClearUndoAndRedoStorage()
{
	// clear the undo and redo storage
	ClearUndoStorage();
	ClearRedoStorage();
}

void CFormEditor::BeginSetTabOrder()
{
	// the editor should not already be in tab ordering mode
	ATLASSERT(m_TabOrderingModeActive==FALSE);
	// there should be nothing selected
	ATLASSERT(IsFormSelected()==FALSE);
	ATLASSERT(GetSelectedItemCount()==0);

	// get the editor state
	CFormEditorState State=GetState();
	// create all item tab numbers
	DWORD TabNumber=1;
	for(CItemInfoPtrList::const_iterator iter=m_ItemInfoPtrList.begin();
		iter!=m_ItemInfoPtrList.end(); iter++)
	{
		CItemInfo &ItemInfo=**iter;
		// create the tab number
		(void)ItemInfo.m_TabNumber.Create(
			*this,ItemInfo.m_HostWindow,m_ItemInfoPtrList.size());
		// set the tab number
		ItemInfo.m_TabNumber.SetTabNumber(TabNumber++);
	}
	// switch the editor into tab ordering mode
	m_TabOrderingModeActive=TRUE;
	m_NextTabNumber=1;
	// set the busy flag
	SetBusy(TRUE);
	FireStateChangedEvent(State);	// state changed
}

void CFormEditor::EndSetTabOrder()
{
	// the editor should be in tab ordering mode
	ATLASSERT(m_TabOrderingModeActive);

	// get the editor state
	CFormEditorState State=GetState();
	// update the undo storage
	UpdateUndoStorage();
	// destroy all item tab numbers
	for(CItemInfoPtrList::const_iterator iter=m_ItemInfoPtrList.begin();
		iter!=m_ItemInfoPtrList.end(); iter++)
	{
		CItemInfo &ItemInfo=**iter;
		// destroy the tab number
		(void)ItemInfo.m_TabNumber.DestroyWindow();
	}
	// sort the item info list by tab order
	SortListUsingPredicate(m_ItemInfoPtrList,IsItemTabNumberLess);
	// update the zorder of all items based on the tab order
	HWND InsertAfter=m_ItemRootWindow;
	for(CItemInfoPtrList::reverse_iterator iter=m_ItemInfoPtrList.rbegin();
		iter!=m_ItemInfoPtrList.rend(); iter++)
	{
		CItemInfo &ItemInfo=**iter;
		// update the zorder
		(void)ItemInfo.m_HostWindow.SetWindowPos(
			InsertAfter,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE|SWP_NOCOPYBITS);
		// insert the next item after this one
		InsertAfter=ItemInfo.m_HostWindow;
	}
	// switch the editor back from tab ordering mode
	m_TabOrderingModeActive=FALSE;
	// set the modified flag
	SetModified(TRUE);
	// reset the busy flag
	SetBusy(FALSE);
	FireStateChangedEvent(State);	// state changed
}

void CFormEditor::UpdateItemTabOrder(CItemInfo &ItemInfo)
{
	// the editor should be in tab ordering mode
	ATLASSERT(m_TabOrderingModeActive);
	// check the next tab number is valid
	ATLASSERT(m_NextTabNumber <= m_ItemInfoPtrList.size());

	// get the old item tab number
	DWORD OldTabNumber=ItemInfo.m_TabNumber.GetTabNumber();

	for(CItemInfoPtrList::const_iterator iter=m_ItemInfoPtrList.begin();
		iter!=m_ItemInfoPtrList.end(); iter++)
	{
		// if the tab number is being decreased
		if(m_NextTabNumber < OldTabNumber)
		{
			// increment relevant tab numbers
			// for(long l=m_NextTabNumber; l<OldTabNumber; l++)
			CItemInfo &ItemInfo=**iter;
			// update the tab number
			long TabNumber=ItemInfo.m_TabNumber.GetTabNumber();
			if(TabNumber >= (long)m_NextTabNumber and TabNumber < (long)OldTabNumber)
			{
				ItemInfo.m_TabNumber.SetTabNumber(TabNumber+1);
			}
		}
		// if the tab number is being increased
		else if(m_NextTabNumber > OldTabNumber)
		{
			// decrement relevant tab numbers
			// for(long l=OldTabNumber+1; l<=m_NextTabNumber; l++)
			CItemInfo &ItemInfo=**iter;
			// update the tab number
			long TabNumber=ItemInfo.m_TabNumber.GetTabNumber();
			if(TabNumber > (long)OldTabNumber and TabNumber <= (long)m_NextTabNumber)
			{
				ItemInfo.m_TabNumber.SetTabNumber(TabNumber-1);
			}
		}
	}
	// update the item tab number
	ItemInfo.m_TabNumber.SetTabNumber(m_NextTabNumber);
	// update the next tab number
	if(m_NextTabNumber < m_ItemInfoPtrList.size())
	{
		m_NextTabNumber++;
	}
}

void CFormEditor::SetBusy(BOOL Busy)
{
	// set the busy flag
	ATLASSERT(m_Busy!=Busy);
	m_Busy=Busy;
	// do not allow deactivation when the editor is busy
	CComQIPtr<IOleControlSite> spOleControlSite(m_spClientSite);
	if(spOleControlSite!=NULL)
	{
		// not all containers will support this method
		(void)spOleControlSite->LockInPlaceActive(m_Busy);
	}
}

void CFormEditor::SetModified(BOOL Modified)
{
	// set the modified flag
	m_Modified=Modified;
}

CFormEditorState CFormEditor::GetState()
{
	// create the state
	CFormEditorState State;
	State.m_Busy=m_Busy;
	State.m_Modified=m_Modified;
	State.m_CanPaste=m_CanPaste;
	State.m_CanUndo=
		(m_UndoStorage.GetElementCount() > 0) ? TRUE : FALSE;
	State.m_CanRedo=
		(m_RedoStorage.GetElementCount() > 0) ? TRUE : FALSE;
	State.m_ItemCount=m_ItemInfoPtrList.size();
	State.m_SelectedItemCount=GetSelectedItemCount();

	return State;
}

void CFormEditor::FireStateChangedEvent(
	const CFormEditorState &OldState,BOOL Force/*=FALSE*/)
{
	if(m_nFreezeEvents==0)
	{
		// get the current state
		CFormEditorState State=GetState();
		// fire the state changed event
		if(State!=OldState or Force)
		{
			(void)Fire_StateChanged(
				B2VB(State.m_Busy),
				B2VB(State.m_Modified),
				B2VB(State.m_CanPaste),
				B2VB(State.m_CanUndo),
				B2VB(State.m_CanRedo),
				State.m_ItemCount,
				State.m_SelectedItemCount);
		}
	}
}

BOOL CFormEditor::IsNameValid(const std::wstring &Name)
{
	// determine if the passed name is valid
	BOOL IsValid=TRUE;
	// the name should not be empty
	if(Name.empty())
	{
		IsValid=FALSE;
	}
	// the name should not contain any invalid characters
	if(std::find_if(Name.begin(),Name.end(),
		boost::compose_f_gx_hx(
			std::logical_and<bool>(),
			std::not1(std::ptr_fun(iswalnum)),
			std::not1(std::bind2nd(std::equal_to<WCHAR>(),L'_'))))!=Name.end())
	{
		IsValid=FALSE;
	}
	// the name should not begin with a number
	if(Name.length() and iswdigit(*Name.begin()))
	{
		IsValid=FALSE;
	}
	return IsValid;
}

BOOL CFormEditor::IsNameUnique(
	const std::wstring &Name,const CWindow &Ignore/*=NULL*/)
{
	// determine if the name is unique
	BOOL IsUnique=TRUE;
	// if the form should not be ignored
	if(m_Form!=Ignore)
	{
		// do a caseless comparison on the form name
		if(!_wcsicmp(m_FormName.c_str(),Name.c_str()))
		{
			IsUnique=FALSE;
		}
	}
	// check all item names
	for(CItemInfoPtrList::const_iterator
			ItemInfoPtrIter=m_ItemInfoPtrList.begin();
		ItemInfoPtrIter!=m_ItemInfoPtrList.end() and IsUnique;
		ItemInfoPtrIter++)
	{
		CItemInfo &ItemInfo=**ItemInfoPtrIter;
		// if the current item should not be ignored
		if(ItemInfo.m_HostWindow!=Ignore)
		{
			// do a caseless comparison on the item name
			if(!_wcsicmp(ItemInfo.m_Name.c_str(),Name.c_str()))
			{
				IsUnique=FALSE;
			}
		}
	}
	// check all exposed object names
	for(CObjectNameToIDispatchMap::const_iterator
			ExposedObjectIter=m_ExposedObjects.begin();
		ExposedObjectIter!=m_ExposedObjects.end() and IsUnique;
		ExposedObjectIter++)
	{
		// do a caseless comparison on the exposed object name
		if(!_wcsicmp(ExposedObjectIter->first.c_str(),Name.c_str()))
		{
			IsUnique=FALSE;
		}
	}
	return IsUnique;
}

std::wstring CFormEditor::CreateUniqueItemName(const std::wstring &BaseName)
{
	// the base name should be valid
	ATLASSERT(IsNameValid(BaseName));
	// create the unique item name
	for(long l=1;/*forever*/;l++)
	{
		// create an item name (using base name + number)
		std::wstringstream strm;
		strm << BaseName;
		strm << l;
		// use the name if unique
		std::wstring Name=strm.str().c_str();
		if(IsNameUnique(Name))
		{
			return Name;
		}
	}
	// should never get to here
	ATLASSERT(FALSE);
	return L"";
}

std::wstring CFormEditor::CreateUniqueItemName(
	const CComPtr<IOleObject> &spOleObject)
{
	// get the base name
	CAutoCoTaskMem<WCHAR> CoBaseName;
	if(!SUCCEEDED(spOleObject->
		GetUserType(USERCLASSTYPE_SHORT,&CoBaseName)))
	{
		throw std::exception();
	}
	// remove invalid characters
	std::wstring BaseName=CoBaseName;
	BaseName.erase(
		std::remove_if(BaseName.begin(),BaseName.end(),
			boost::compose_f_gx_hx(
				std::logical_and<bool>(),
				std::not1(std::ptr_fun(iswalnum)),
				std::not1(std::bind2nd(std::equal_to<WCHAR>(),L'_')))),
		BaseName.end());
	// if the base name is invalid
	if(IsNameValid(BaseName)==FALSE)
	{
		// use the default base name
		BaseName=DEFAULT_ITEM_BASE_NAME;
	}
	// create the unique name
	return CreateUniqueItemName(BaseName);
}

void CFormEditor::InitialiseForm(
	const std::wstring &Name,const CSize &Size,
	OLE_COLOR BackColor,OLE_COLOR ForeColor)
{
	// the form name should be valid
	ATLASSERT(IsNameValid(Name));
	// the form name should be unique
	ATLASSERT(IsNameUnique(Name,m_Form));
	// the form should not be selected
	ATLASSERT(IsFormSelected()==FALSE);
	// the form should be empty
	ATLASSERT(m_ItemInfoPtrList.size()==0);
	// set the form name
	m_FormName=Name;
	// set the form dimensions
	SetFormDimensions(Size.cx,Size.cy);
	// set the form colors
	SetFormColors(BackColor,ForeColor);
}

void CFormEditor::InitialiseFormFromStream(
	const CComPtr<IStream> &spStream,DWORD StreamVersion)
{
	// read the form block from the passed stream
	CStreamFormBlock FormBlock;
	FormBlock.ReadFromStream(spStream,StreamVersion);
	// initialise the form
	InitialiseForm(FormBlock.m_Name,FormBlock.m_Size,
		FormBlock.m_BackColor,FormBlock.m_ForeColor);
}

void CFormEditor::SaveFormToStream(const CComPtr<IStream> &spStream)
{
	// get the form rect
	CRect FormRect(0,0,0,0);
	(void)m_Form.GetClientRect(FormRect);
	// get the form colors
	OLE_COLOR BackColor=RGB(0,0,0);
	OLE_COLOR ForeColor=RGB(0,0,0);
	m_Form.GetColors(BackColor,ForeColor);
	// create the form block
	CStreamFormBlock FormBlock;
	FormBlock.m_Name=m_FormName;
	FormBlock.m_Size=FormRect.BottomRight();
	FormBlock.m_BackColor=BackColor;
	FormBlock.m_ForeColor=ForeColor;
	// write the form block to the passed stream
	FormBlock.WriteToStream(spStream);
}

CItemInfo &CFormEditor::CreateItem(
	const std::wstring &Name,const std::wstring &Tag,
	const CRect &Rect,const CLSID &ClassId,
	const CComPtr<IStream> &spStream)
{
	// the item name should be valid if present
	ATLASSERT(Name.empty() or IsNameValid(Name));
	// the item name should be unique if present
	ATLASSERT(Name.empty() or IsNameUnique(Name));
	// the item should fit within the form
#ifdef _DEBUG
	CRect FormRect(0,0,0,0);
	(void)m_Form.GetClientRect(FormRect);
	ATLASSERT(Rect.left	  >= 0);
	ATLASSERT(Rect.top	  >= 0);
	ATLASSERT(Rect.right  <= FormRect.right);
	ATLASSERT(Rect.bottom <= FormRect.bottom);
#endif
	// if the editor is in locked mode
	if(m_LockedModeActive)
	{
		// limit the number of items which can be hosted
		if(m_ItemInfoPtrList.size() >= MAX_LOCKED_MODE_ITEMS)
		{
			throw std::exception();
		}
	}
	// create item info
	CAutoItemInfoPtr pItemInfo(CItemInfo::CreateObject());
	// set the name
	pItemInfo->m_Name=Name;
	// set the tag
	pItemInfo->m_Tag=Tag;
	// create the host window
	if(pItemInfo->m_HostWindow.Create(m_Form,CRect(0,0,0,0),_T(""),
		WS_CHILD|WS_DISABLED|WS_CLIPCHILDREN|WS_CLIPSIBLINGS)==NULL)
	{
		throw std::exception();
	}
	// get the host window IAxWinAmbientDispatch interface
	CComPtr<IAxWinAmbientDispatch> spAxWinAmbientDispatch;
	if(!SUCCEEDED(pItemInfo->m_HostWindow.QueryHost(&spAxWinAmbientDispatch)))
	{
		throw std::exception();
	}
	// set the host window ambient properties
	OLE_COLOR BackColor=RGB(0,0,0);
	OLE_COLOR ForeColor=RGB(0,0,0);
	m_Form.GetColors(BackColor,ForeColor);
	// background color
	if(!SUCCEEDED(spAxWinAmbientDispatch->put_BackColor(BackColor)))
	{
		throw std::exception();
	}
	// foreground color
	if(!SUCCEEDED(spAxWinAmbientDispatch->put_ForeColor(ForeColor)))
	{
		throw std::exception();
	}
	// windowless activation
	if(!SUCCEEDED(spAxWinAmbientDispatch->
		put_AllowWindowlessActivation(VARIANT_FALSE)))
	{
		throw std::exception();
	}
	// user mode
	if(!SUCCEEDED(spAxWinAmbientDispatch->put_UserMode(VARIANT_FALSE)))
	{
		throw std::exception();
	}
	// get the host window IAxWinHostWindow interface
	CComPtr<IAxWinHostWindow> spAxWinHostWindow;
	if(!SUCCEEDED(pItemInfo->m_HostWindow.QueryHost(&spAxWinHostWindow)))
	{
		throw std::exception();
	}
	// get the item class id as text
	WCHAR ClassIdText[64]=L"";
	if(StringFromGUID2(ClassId,
		ClassIdText,NUM_ELEMENTS(ClassIdText,WCHAR))==0)
	{
		throw std::exception();
	}
	// create the item
	if(!SUCCEEDED(spAxWinHostWindow->
		CreateControl(ClassIdText,pItemInfo->m_HostWindow,spStream)))
	{
		throw std::exception();
	}
	// set the host window size and position
	// this is required to correctly size some items and also set the zorder
	if(pItemInfo->m_HostWindow.SetWindowPos(
		m_ItemRootWindow,Rect,SWP_FRAMECHANGED)==FALSE)
	{
		throw std::exception();
	}
	// if the item name is empty
	if(pItemInfo->m_Name.empty())
	{
		// create a unique item name
		CComPtr<IOleObject> spOleObject;
		if(!SUCCEEDED(pItemInfo->
			m_HostWindow.QueryControl(&spOleObject)))
		{
			throw std::exception();
		}
		pItemInfo->m_Name=CreateUniqueItemName(spOleObject);
	}
	// set the drag frame colors
	m_FormDragFrame.GetColors(BackColor,ForeColor);
	pItemInfo->m_DragFrame.SetColors(BackColor,ForeColor);
	// set the tab number colors
	m_FormTabNumber.GetColors(BackColor,ForeColor);
	pItemInfo->m_TabNumber.SetColors(BackColor,ForeColor);
	// add item info to the list
	m_ItemInfoPtrList.push_back(pItemInfo);
	// make the host window visible
	(void)pItemInfo->m_HostWindow.ShowWindow(SW_SHOW);

	return *pItemInfo.Release();
}

CItemInfoPtrList CFormEditor::CreateItemsFromStream(
	const CComPtr<IStream> &spStream,DWORD StreamVersion,
	BOOL IsPasting)
{
	// read the item list header from the passed stream
	CStreamItemListHeader ItemListHeader;
	ItemListHeader.ReadFromStream(spStream,StreamVersion);

	// item info for the new items
	CItemInfoPtrList ItemInfoPtrList;
	// for each item
	for(long l=0; l<(long)ItemListHeader.m_Count; l++)
	{
		// read the item block from the passed stream
		CStreamItemBlock ItemBlock;
		ItemBlock.ReadFromStream(spStream,StreamVersion);
		// we need to do some additional tasks if pasting
		if(IsPasting)
		{
			// force a unique item name
			ItemBlock.m_Name=L"";
			// remove the tag
			ItemBlock.m_Tag=L"";
			// the top left item being inserted will be placed at
			// the top left corner of the form so all other items
			// will be reletive to this one
			CRect ItemRect=ItemBlock.m_Rect;
			ItemRect.OffsetRect(
				-ItemListHeader.m_BoundingRect.left,
				-ItemListHeader.m_BoundingRect.top);
			// get the form rect
			CRect FormRect(0,0,0,0);
			(void)m_Form.GetClientRect(FormRect);
			// ensure the item fits within the form
			ItemBlock.m_Rect=ForceRectIntoRect(FormRect,ItemRect);
		}
		// for early stream versions the item data will be read directly from
		// the passed stream, for later stream versions the item data will first
		// be copied to a temporary data stream and then read from there
		CComPtr<IStream> spDataStream=spStream;
		if(ItemBlock.m_HasPersistantData and StreamVersion > 2)
		{
			// create the data stream
			spDataStream=NULL;
			if(!SUCCEEDED(CreateStreamOnHGlobal(NULL,TRUE,&spDataStream)))
			{
				throw std::exception();
			}
			// initialise the size
			ULARGE_INTEGER Size;
			Size.QuadPart=0;
			if(!SUCCEEDED(spDataStream->SetSize(Size)))
			{
				throw std::exception();
			}
			// read the item persistant data header from the passed stream
			CStreamItemPersistantDataHeader ItemPersistantDataHeader;
			ItemPersistantDataHeader.ReadFromStream(spStream,StreamVersion);
			// copy the item data to the data stream
			if(!SUCCEEDED(spStream->CopyTo(
				spDataStream,ItemPersistantDataHeader.m_Length,NULL,NULL)))
			{
				throw std::exception();
			}
			// seek back to the start of the data stream
			LARGE_INTEGER Offset;
			Offset.QuadPart=0;
			if(!SUCCEEDED(spDataStream->Seek(Offset,STREAM_SEEK_SET,NULL)))
			{
				throw std::exception();
			}
		}
		// create the item
		CItemInfo &ItemInfo=CreateItem(ItemBlock.m_Name,ItemBlock.m_Tag,
			ItemBlock.m_Rect,ItemBlock.m_ClassId,
				ItemBlock.m_HasPersistantData ? spDataStream : NULL);
		// add item info to the list
		ItemInfoPtrList.push_back(&ItemInfo);
	}
	return ItemInfoPtrList;
}

void CFormEditor::SaveItemsToStream(const CComPtr<IStream> &spStream,
	const CItemInfoPtrList &ItemInfoPtrList)
{
	// create the item list header
	CStreamItemListHeader ItemListHeader;
	ItemListHeader.m_Count=ItemInfoPtrList.size();
	ItemListHeader.m_BoundingRect=GetBoundingRectForItems(ItemInfoPtrList);
	// write the item list header to the passed stream
	ItemListHeader.WriteToStream(spStream);

	// for each item
	for(CItemInfoPtrList::const_iterator iter=
		ItemInfoPtrList.begin(); iter!=ItemInfoPtrList.end(); iter++)
	{
		CItemInfo &ItemInfo=**iter;
		// get the item class id
		CComPtr<IOleObject> spOleObject;
		if(!SUCCEEDED(ItemInfo.m_HostWindow.QueryControl(&spOleObject)))
		{
			throw std::exception();
		}
		CLSID ClassId=CLSID_NULL;
		if(!SUCCEEDED(spOleObject->GetUserClassID(&ClassId)))
		{
			throw std::exception();
		}
		// create the item block
		CStreamItemBlock ItemBlock;
		ItemBlock.m_Name=ItemInfo.m_Name;
		ItemBlock.m_Tag=ItemInfo.m_Tag;
		ItemBlock.m_Rect=GetItemRect(ItemInfo);
		ItemBlock.m_ClassId=ClassId;
		// determine if the item supports stream persistance
		CComPtr<IPersistStream> spPersistStream;
		if(!SUCCEEDED(ItemInfo.m_HostWindow.QueryControl(&spPersistStream)))
		{
			// fall back to IPersistStreamInit
			(void)ItemInfo.m_HostWindow.QueryControl(
				IID_IPersistStreamInit,(void**)&spPersistStream);
		}
		ItemBlock.m_HasPersistantData=(spPersistStream!=NULL) ? TRUE : FALSE;
		// save the item block position
		LARGE_INTEGER Offset;
		Offset.QuadPart=0;
		ULARGE_INTEGER Position;
		Position.QuadPart=0;
		if(!SUCCEEDED(spStream->Seek(Offset,STREAM_SEEK_CUR,&Position)))
		{
			throw std::exception();
		}
		// write the item block to the passed stream
		ItemBlock.WriteToStream(spStream);
		// write the item data to the passed stream
		if(ItemBlock.m_HasPersistantData)
		{
			// create the data stream
			CComPtr<IStream> spDataStream;
			if(!SUCCEEDED(CreateStreamOnHGlobal(NULL,TRUE,&spDataStream)))
			{
				throw std::exception();
			}
			// initialise the size
			ULARGE_INTEGER Size;
			Size.QuadPart=0;
			if(!SUCCEEDED(spDataStream->SetSize(Size)))
			{
				throw std::exception();
			}
			// write the item data to the data stream
			if(SUCCEEDED(spPersistStream->Save(spDataStream,TRUE)))
			{
				// get the data stream size
				STATSTG StatStg;
				if(!SUCCEEDED(spDataStream->Stat(&StatStg,STATFLAG_NONAME)))
				{
					throw std::exception();
				}
				// create the item persistant data header
				CStreamItemPersistantDataHeader ItemPersistantDataHeader;
				ItemPersistantDataHeader.m_Length=StatStg.cbSize;
				// write the item persistant data header to the passed stream
				ItemPersistantDataHeader.WriteToStream(spStream);
				// seek back to the start of the data stream
				LARGE_INTEGER Offset;
				Offset.QuadPart=0;
				if(!SUCCEEDED(spDataStream->Seek(Offset,STREAM_SEEK_SET,NULL)))
				{
					throw std::exception();
				}
				// copy the item data to the passed stream
				if(!SUCCEEDED(
					spDataStream->CopyTo(spStream,StatStg.cbSize,NULL,NULL)))
				{
					throw std::exception();
				}
			}
			else	// failed to write the item data to the data stream
			{
				// seek back to the item block position
				Offset.QuadPart=Position.QuadPart;
				if(!SUCCEEDED(spStream->Seek(Offset,STREAM_SEEK_SET,NULL)))
				{
					throw std::exception();
				}
				// indicate the item does not support stream persistance
				ItemBlock.m_HasPersistantData=FALSE;
				ItemBlock.WriteToStream(spStream);
			}
		}
	}
}

void CFormEditor::ResetScript()
{
	// reset the script
	if(!SUCCEEDED(m_spScript->Reset()))
	{
		throw std::exception();
	}
}

void CFormEditor::LoadScriptFromStream(
	const CComPtr<IStream> &spStream,DWORD StreamVersion)
{
	// load the script from the passed stream
	if(!SUCCEEDED(m_spScript->LoadFromStream(spStream,StreamVersion)))
	{
		throw std::exception();
	}
}

void CFormEditor::SaveScriptToStream(const CComPtr<IStream> &spStream)
{
	// save the script to the passed stream
	if(!SUCCEEDED(m_spScript->SaveToStream(spStream)))
	{
		throw std::exception();
	}
}

CItemInfoPtrList CFormEditor::InternalLoad(
	const CComPtr<IStream> &spStream,StreamType Type)
{
	// perform pre processing based on the stream type
	if(Type==StreamTypeFull)
	{
		// delete all items
		DeleteAllItems();
		// clear the undo and redo storage
		ClearUndoAndRedoStorage();
		// reset the scrollbars
		(void)SetScrollPos(SB_HORZ,0);
		(void)SetScrollPos(SB_VERT,0);
		// reset the form position
		(void)m_Form.SetWindowPos(NULL,
			m_FormDragFrame.GetFrameSize(),m_FormDragFrame.GetFrameSize(),
				0,0,SWP_NOZORDER|SWP_NOSIZE|SWP_NOCOPYBITS/*prevent flicker*/);
		// reset the script
		ResetScript();
	}
	else if(Type==StreamTypeClipboard)
	{
	}
	else if(Type==StreamTypeUndoRedo)
	{
		// delete all items
		DeleteAllItems();
	}
	// read in the header
	CStreamHeader StreamHeader;
	StreamHeader.ReadFromStream(spStream);
	// the stream version must be recognisable
	if(StreamHeader.m_Version > CStreamHeader().m_Version)
	{
		throw std::exception();
	}
	// read in the form
	if(Type==StreamTypeFull or Type==StreamTypeUndoRedo)
	{
		InitialiseFormFromStream(spStream,StreamHeader.m_Version);
	}
	// read in the item list
	BOOL IsPasting=(Type==StreamTypeClipboard) ? TRUE : FALSE;
	CItemInfoPtrList ItemInfoPtrList=
		CreateItemsFromStream(spStream,StreamHeader.m_Version,IsPasting);
	// read in the script
	if(Type==StreamTypeFull)
	{
		LoadScriptFromStream(spStream,StreamHeader.m_Version);
	}
	// read in the footer
	CStreamFooter StreamFooter;
	StreamFooter.ReadFromStream(spStream);

	return ItemInfoPtrList;
}

void CFormEditor::InternalSave(
	const CComPtr<IStream> &spStream,StreamType Type)
{
	// write out the header
	CStreamHeader StreamHeader;
	StreamHeader.WriteToStream(spStream);
	// write out the form
	if(Type==StreamTypeFull or Type==StreamTypeUndoRedo)
	{
		SaveFormToStream(spStream);
	}
	// write out the item list
	if(Type==StreamTypeClipboard)
	{
		// selected items
		SaveItemsToStream(spStream,GetSelectedItems());
	}
	else
	{
		// all items
		SaveItemsToStream(spStream,m_ItemInfoPtrList);
	}
	// write out the script
	if(Type==StreamTypeFull)
	{
		SaveScriptToStream(spStream);
	}
	// write out the footer
	CStreamFooter StreamFooter;
	StreamFooter.WriteToStream(spStream);
}

BOOL CFormEditor::InternalValidateScript()
{
	// validate the script
	BOOL IsValid=TRUE;
	// create the script control
	CComPtr<MSScriptControl::IScriptControl> spScriptControl;
	if(!SUCCEEDED(spScriptControl.CoCreateInstance(
		__uuidof(MSScriptControl::ScriptControl))))
	{
		throw std::exception();
	}
	// set the language
	if(!SUCCEEDED(spScriptControl->put_Language(CComBSTR(L"VBScript"))))
	{
		throw std::exception();
	}
	// get the script text
	CComBSTR Text;
	if(!SUCCEEDED(m_spScript->get_Text(&Text)))
	{
		throw std::exception();
	}
	// load the script text into the script control
	if(!SUCCEEDED(spScriptControl->AddCode(Text)))
	{
		IsValid=FALSE;
		// get error information
		CComPtr<MSScriptControl::IScriptError> spError;
		if(!SUCCEEDED(spScriptControl->get_Error(&spError)))
		{
			throw std::exception();
		}
		// source
		CComBSTR Source;
		if(!SUCCEEDED(spError->get_Source(&Source)))
		{
			throw std::exception();
		}
		// line
		long Line=0;
		if(!SUCCEEDED(spError->get_Line(&Line)))
		{
			throw std::exception();
		}
		// number
		long Number=0;
		if(!SUCCEEDED(spError->get_Number(&Number)))
		{
			throw std::exception();
		}
		// description
		CComBSTR Description;
		if(!SUCCEEDED(spError->get_Description(&Description)))
		{
			throw std::exception();
		}
		// edit the error in the script
		if(!SUCCEEDED(m_spScript->EditLine(Line-1)))
		{
			throw std::exception();
		}
		// fire the script error event
		(void)Fire_EditScriptError(Source,Number,Description);
	}
	return IsValid;
}

void CFormEditor::MoveSelectedItems(MoveType Type)
{
	// get the form rect
	CRect FormRect(0,0,0,0);
	(void)m_Form.GetClientRect(FormRect);

	// get the selected items bounding rectangle
	CRect BoundingRect=GetBoundingRectForSelectedItems();
	// offset the bounding rectangle
	switch(Type)
	{
	case MoveTypeLeft:	// left
		BoundingRect.OffsetRect(-1,0);
		break;
	case MoveTypeRight:	// right
		BoundingRect.OffsetRect(1,0);
		break;
	case MoveTypeUp:	// up
		BoundingRect.OffsetRect(0,-1);
		break;
	case MoveTypeDown:	// down
		BoundingRect.OffsetRect(0,1);
		break;
	default:			// unknown
		ATLASSERT(FALSE);
		break;
	}
	// ensure the bounding rectangle stays within the form
	BoundingRect=ForceRectIntoRect(FormRect,BoundingRect);
	// determine the final valid offset
	CSize FinalOffset=BoundingRect.TopLeft()-
		GetBoundingRectForSelectedItems().TopLeft();
	// move all selected items
	CItemInfoPtrList ItemInfoPtrList=GetSelectedItems();
	for(CItemInfoPtrList::const_iterator iter=
		ItemInfoPtrList.begin(); iter!=ItemInfoPtrList.end(); iter++)
	{
		CItemInfo &ItemInfo=**iter;
		// get the item rect
		CRect ItemRect=GetItemRect(ItemInfo);
		// offset the item rect
		ItemRect.OffsetRect(FinalOffset);
		// move the item
		(void)ItemInfo.m_HostWindow.
			SetWindowPos(NULL,ItemRect,SWP_NOZORDER|SWP_NOSIZE);
	}
}

void CFormEditor::AlignSelectedItems(AlignType Type)
{
	// get the selected items bounding rectangle
	CRect BoundingRect=GetBoundingRectForSelectedItems();
	// align all selected items
	CItemInfoPtrList ItemInfoPtrList=GetSelectedItems();
	for(CItemInfoPtrList::const_iterator iter=
		ItemInfoPtrList.begin(); iter!=ItemInfoPtrList.end(); iter++)
	{
		CItemInfo &ItemInfo=**iter;
		// get the item rect
		CRect ItemRect=GetItemRect(ItemInfo);
		// offset the item rect
		switch(Type)
		{
		case AlignTypeLeft:		// left
			ItemRect.OffsetRect(
				BoundingRect.left-ItemRect.left,0);
			break;
		case AlignTypeRight:	// right
			ItemRect.OffsetRect(
				BoundingRect.right-ItemRect.right,0);
			break;
		case AlignTypeTop:		// top
			ItemRect.OffsetRect(
				0,BoundingRect.top-ItemRect.top);
			break;
		case AlignTypeBottom:	// bottom
			ItemRect.OffsetRect(
				0,BoundingRect.bottom-ItemRect.bottom);
			break;
		default:				// unknown
			ATLASSERT(FALSE);
			break;
		}
		// align the item
		(void)ItemInfo.m_HostWindow.
			SetWindowPos(NULL,ItemRect,SWP_NOZORDER|SWP_NOSIZE);
	}
}

void CFormEditor::AlignSelectedItemsToForm(FormAlignType Type)
{
	// get the form rect
	CRect FormRect(0,0,0,0);
	(void)m_Form.GetClientRect(FormRect);

	// get the selected items bounding rectangle
	CRect BoundingRect=GetBoundingRectForSelectedItems();
	// get the bounding rectangle aligned position
	CPoint AlignedPos=CPoint(
		(FormRect.Width()-BoundingRect.Width())/2,
			(FormRect.Height()-BoundingRect.Height())/2);
	// determine the bounding rectangle offset
	CSize Offset(0,0);
	if(Type==FormAlignTypeHCenter or Type==FormAlignTypeBoth)
	{
		// horizontal center
		Offset.cx=AlignedPos.x-BoundingRect.left;
	}
	if(Type==FormAlignTypeVCenter or Type==FormAlignTypeBoth)
	{
		// vertical center
		Offset.cy=AlignedPos.y-BoundingRect.top;
	}
	// align all selected items
	CItemInfoPtrList ItemInfoPtrList=GetSelectedItems();
	for(CItemInfoPtrList::const_iterator iter=
		ItemInfoPtrList.begin(); iter!=ItemInfoPtrList.end(); iter++)
	{
		CItemInfo &ItemInfo=**iter;
		// get the item rect
		CRect ItemRect=GetItemRect(ItemInfo);
		// offset the item rect
		ItemRect.OffsetRect(Offset);
		// align the item
		(void)ItemInfo.m_HostWindow.
			SetWindowPos(NULL,ItemRect,SWP_NOZORDER|SWP_NOSIZE);
	}
}

void CFormEditor::SizeSelectedItemsToLargest(SizeType Type)
{
	// get the form rect
	CRect FormRect(0,0,0,0);
	(void)m_Form.GetClientRect(FormRect);

	// the width of the largest selected item
	long LargestWidth=0;
	// the height of the largest selected item
	long LargestHeight=0;
	// the width and height of the overall largest selected item
	CSize LargestOverall(0,0);

	// scan selected items for the largest dimensions
	CItemInfoPtrList ItemInfoPtrList=GetSelectedItems();
	for(CItemInfoPtrList::const_iterator iter=
		ItemInfoPtrList.begin(); iter!=ItemInfoPtrList.end(); iter++)
	{
		CItemInfo &ItemInfo=**iter;
		// get the item rect
		CRect ItemRect=GetItemRect(ItemInfo);
		// update the largest width
		LargestWidth=max(LargestWidth,ItemRect.Width());
		// update the largest height
		LargestHeight=max(LargestHeight,ItemRect.Height());
		// update the overall largest
		if(ItemRect.Width()*ItemRect.Height() >
			LargestOverall.cx*LargestOverall.cy)
		{
			LargestOverall=	// new overall largest
				CSize(ItemRect.Width(),ItemRect.Height());
		}
	}
	// size all selected items
	for(iter=ItemInfoPtrList.begin();
		iter!=ItemInfoPtrList.end(); iter++)
	{
		CItemInfo &ItemInfo=**iter;
		// get the item rect
		CRect ItemRect=GetItemRect(ItemInfo);
		// size the item rect
		switch(Type)
		{
		case SizeTypeWidth:		// width
			ItemRect.right=ItemRect.left+LargestWidth;
			break;
		case SizeTypeHeight:	// height
			ItemRect.bottom=ItemRect.top+LargestHeight;
			break;
		case SizeTypeBoth:		// width and height
			ItemRect.right=ItemRect.left+LargestOverall.cx;
			ItemRect.bottom=ItemRect.top+LargestOverall.cy;
			break;
		default:				// unknown
			ATLASSERT(FALSE);
		}
		// ensure the item stays within the form
		ItemRect=ForceRectIntoRect(FormRect,ItemRect);
		// size the item
		(void)ItemInfo.m_HostWindow.SetWindowPos(
			NULL,ItemRect,SWP_NOZORDER/* may have to move */);
	}
}

void CFormEditor::SizeSelectedItemsToSmallest(SizeType Type)
{
	// get the form rect
	CRect FormRect(0,0,0,0);
	(void)m_Form.GetClientRect(FormRect);

	// the width of the smallest selected item
	long SmallestWidth=FormRect.Width();
	// the height of the smallest selected item
	long SmallestHeight=FormRect.Height();
	// the width and height of the overall smallest selected item
	CSize SmallestOverall(SmallestWidth,SmallestHeight);

	// scan selected items for the smallest dimensions
	CItemInfoPtrList ItemInfoPtrList=GetSelectedItems();
	for(CItemInfoPtrList::const_iterator iter=
		ItemInfoPtrList.begin(); iter!=ItemInfoPtrList.end(); iter++)
	{
		CItemInfo &ItemInfo=**iter;
		// get the item rect
		CRect ItemRect=GetItemRect(ItemInfo);
		// update the smallest width
		SmallestWidth=min(SmallestWidth,ItemRect.Width());
		// update the smallest height
		SmallestHeight=min(SmallestHeight,ItemRect.Height());
		// update the overall smallest
		if(ItemRect.Width()*ItemRect.Height() <
			SmallestOverall.cx*SmallestOverall.cy)
		{
			SmallestOverall=	// new overall smallest
				CSize(ItemRect.Width(),ItemRect.Height());
		}
	}
	// size all selected items
	for(iter=ItemInfoPtrList.begin();
		iter!=ItemInfoPtrList.end(); iter++)
	{
		CItemInfo &ItemInfo=**iter;
		// get the item rect
		CRect ItemRect=GetItemRect(ItemInfo);
		// size the item rect
		switch(Type)
		{
		case SizeTypeWidth:		// width
			ItemRect.right=ItemRect.left+SmallestWidth;
			break;
		case SizeTypeHeight:	// height
			ItemRect.bottom=ItemRect.top+SmallestHeight;
			break;
		case SizeTypeBoth:		// width and height
			ItemRect.right=ItemRect.left+SmallestOverall.cx;
			ItemRect.bottom=ItemRect.top+SmallestOverall.cy;
			break;
		default:				// unknown
			ATLASSERT(FALSE);
		}
		// ensure the item stays within the form
		ItemRect=ForceRectIntoRect(FormRect,ItemRect);
		// size the item
		(void)ItemInfo.m_HostWindow.SetWindowPos(
			NULL,ItemRect,SWP_NOZORDER/* may have to move */);
	}
}

void CFormEditor::SpaceSelectedItemsHorizontally()
{
	// there should be at least two items selected
	ATLASSERT(GetSelectedItemCount() >= 2);

	// get the form rect
	CRect FormRect(0,0,0,0);
	(void)m_Form.GetClientRect(FormRect);

	// get a list of selected items
	CItemInfoPtrList ItemInfoPtrList=GetSelectedItems();
	// copy the list to a temporary vector
	CItemInfoPtrVector ItemInfoPtrVector;
	std::copy(ItemInfoPtrList.begin(),ItemInfoPtrList.end(),
		std::back_insert_iterator<CItemInfoPtrVector>(ItemInfoPtrVector));
	// sort the vector by horizontal item position
	std::sort(ItemInfoPtrVector.begin(),ItemInfoPtrVector.end(),
		IsItemHorzontalPositionLess);
	// get the left most item rect
	CRect LeftMostItemRect=
		GetItemRect(*ItemInfoPtrVector[0]);
	// get the right most item rect
	CRect RightMostItemRect=
		GetItemRect(*ItemInfoPtrVector[ItemInfoPtrVector.size()-1]);

	// space all selected items
	for(long l=1; l<=(long)(ItemInfoPtrVector.size()-2); l++)
	{
		CItemInfo &ItemInfo=*ItemInfoPtrVector[l];
		// determine the required item horizontal position
		float FreeWidth=(float)(RightMostItemRect.left-LeftMostItemRect.left);
		float Spacing=FreeWidth/(ItemInfoPtrVector.size()-1);
		float Position=LeftMostItemRect.left+(l*Spacing);
		// get the item rect
		CRect ItemRect=GetItemRect(ItemInfo);
		// offset the item rect
		ItemRect.OffsetRect((int)Position-ItemRect.left,0);
		// ensure the item stays within the form
		ItemRect=ForceRectIntoRect(FormRect,ItemRect);
		// space the item
		(void)ItemInfo.m_HostWindow.SetWindowPos(
			NULL,ItemRect,SWP_NOZORDER|SWP_NOSIZE);
	}
}

void CFormEditor::SpaceSelectedItemsVertically()
{
	// there should be at least two items selected
	ATLASSERT(GetSelectedItemCount() >= 2);

	// get the form rect
	CRect FormRect(0,0,0,0);
	(void)m_Form.GetClientRect(FormRect);

	// get a list of selected items
	CItemInfoPtrList ItemInfoPtrList=GetSelectedItems();
	// copy the list to a temporary vector
	CItemInfoPtrVector ItemInfoPtrVector;
	std::copy(ItemInfoPtrList.begin(),ItemInfoPtrList.end(),
		std::back_insert_iterator<CItemInfoPtrVector>(ItemInfoPtrVector));
	// sort the vector by vertical item position
	std::sort(ItemInfoPtrVector.begin(),ItemInfoPtrVector.end(),
		IsItemVerticalPositionLess);
	// get the top most item rect
	CRect TopMostItemRect=
		GetItemRect(*ItemInfoPtrVector[0]);
	// get the bottom most item rect
	CRect BottomMostItemRect=
		GetItemRect(*ItemInfoPtrVector[ItemInfoPtrVector.size()-1]);

	// space all selected items
	for(long l=1; l<=(long)(ItemInfoPtrVector.size()-2); l++)
	{
		CItemInfo &ItemInfo=*ItemInfoPtrVector[l];
		// determine the required item vertical position
		float FreeHeight=(float)(BottomMostItemRect.top-TopMostItemRect.top);
		float Spacing=FreeHeight/(ItemInfoPtrVector.size()-1);
		float Position=TopMostItemRect.top+(l*Spacing);
		// get the item rect
		CRect ItemRect=GetItemRect(ItemInfo);
		// offset the item rect
		ItemRect.OffsetRect(0,(int)Position-ItemRect.top);
		// ensure the item stays within the form
		ItemRect=ForceRectIntoRect(FormRect,ItemRect);
		// space the item
		(void)ItemInfo.m_HostWindow.SetWindowPos(
			NULL,ItemRect,SWP_NOZORDER|SWP_NOSIZE);
	}
}

BOOL CFormEditor::DoesItemRequirePropertyPages(CItemInfo &ItemInfo)
{
	// get the items IUnknown interface
	CComPtr<IUnknown> spUnknown;
	if(!SUCCEEDED(ItemInfo.m_HostWindow.QueryControl(&spUnknown)))
	{
		throw std::exception();
	}
	// get the items ISpecifyPropertyPages interface
	CComQIPtr<ISpecifyPropertyPages> spSpecifyPropertyPages(spUnknown);
	if(spSpecifyPropertyPages==NULL)
	{
		return FALSE;	// no property pages
	}
	// get a list of property pages required by the item
	CAUUID Pages={0,NULL};
	if(!SUCCEEDED(spSpecifyPropertyPages->GetPages(&Pages)))
	{
		throw std::exception();
	}
	CAutoCoTaskMem<GUID> CoPages(Pages.pElems);
	if(Pages.cElems==0)
	{
		return FALSE;	// no property pages
	}
	return TRUE;		// the item requires one or more property pages
}

void CFormEditor::ShowFormProperties()
{
	// create the form properties accessor
	CComPtr<IFormPropertiesAccessor> spFormPropertiesAccessor;
	if(!SUCCEEDED(
		CFormPropertiesAccessor::CreateInstance(&spFormPropertiesAccessor)))
	{
		throw std::exception();
	}
	// set the form editor
	CComQIPtr<IFormEditor> spFormEditor(GetUnknown());
	if(spFormEditor==NULL)
	{
		throw std::exception();
	}
	if(!SUCCEEDED(spFormPropertiesAccessor->put_FormEditor(spFormEditor)))
	{
		throw std::exception();
	}
	// create the property page array
	const DWORD NumPages=2;
	CAutoCoTaskMem<GUID> CoPages((GUID*)CoTaskMemAlloc(NumPages*sizeof(GUID)));
	if(CoPages==NULL)
	{
		throw std::exception();
	}
	CAUUID Pages={ NumPages,CoPages };
	Pages.pElems[0]=CLSID_StockColorPage;
	Pages.pElems[1]=__uuidof(DDPROPPAGEALLLib::PropPageAll);
	// get the form properties accessor IUnknown interface
	CComPtr<IUnknown> spUnknown=spFormPropertiesAccessor;
	// show the form property frame
	if(!SUCCEEDED(OleCreatePropertyFrame(*this,0,0,L"Form",1,&spUnknown.p,
		Pages.cElems,Pages.pElems,LOCALE_USER_DEFAULT,0,NULL)))
	{
		throw std::exception();
	}
}

void CFormEditor::ShowSelectedItemProperties()
{
	// get item info for the selected item
	ATLASSERT(GetSelectedItemCount()==1);
	CItemInfo &ItemInfo=*GetSelectedItems().front();

	// initialise an empty array of property page class ids
	CAUUID Pages={ 0,(GUID*)CoTaskMemAlloc(0) };
	if(Pages.pElems==NULL)
	{
		throw std::exception();
	}
	CAutoCoTaskMem<GUID> CoPages(Pages.pElems);
	// get the items IUnknown interface
	CComPtr<IUnknown> spUnknown;
	if(!SUCCEEDED(ItemInfo.m_HostWindow.QueryControl(&spUnknown)))
	{
		throw std::exception();
	}
	// get the items IOleObject interface
	CComQIPtr<IOleObject> spOleObject(spUnknown);
	if(spOleObject==NULL)
	{
		throw std::exception();
	}
	// get the item name
	CAutoCoTaskMem<WCHAR> Name;
	if(!SUCCEEDED(spOleObject->GetUserType(USERCLASSTYPE_SHORT,&Name)))
	{
		throw std::exception();
	}
	// get a list of property pages required by the item
	if(DoesItemRequirePropertyPages(ItemInfo))
	{
		// get the items ISpecifyPropertyPages interface
		CComQIPtr<ISpecifyPropertyPages> spSpecifyPropertyPages(spUnknown);
		if(spSpecifyPropertyPages==NULL)
		{
			throw std::exception();
		}
		// get a list of property pages required by the item
		if(!SUCCEEDED(spSpecifyPropertyPages->GetPages(&Pages)))
		{
			throw std::exception();
		}
		CoPages=CAutoCoTaskMem<GUID>(Pages.pElems);
	}
	// make room for the extended property pages
	Pages.cElems+=2;
	Pages.pElems=(GUID*)CoTaskMemRealloc(
		CoPages.Release(),(Pages.cElems)*sizeof(GUID));
	if(Pages.pElems==NULL)
	{
		// todo : this will leak CoTaskMem from the above release
		throw std::exception();
	}
	CoPages=CAutoCoTaskMem<GUID>(Pages.pElems);
	// add the extended property pages
	Pages.pElems[Pages.cElems-2]=__uuidof(DDPROPPAGEALLLib::PropPageAll);
	Pages.pElems[Pages.cElems-1]=CLSID_PropPageExtended;
	// show the item property frame
	if(!SUCCEEDED(OleCreatePropertyFrame(*this,0,0,Name,1,&spUnknown.p,
		Pages.cElems,Pages.pElems,LOCALE_USER_DEFAULT,0,NULL)))
	{
		throw std::exception();
	}
}

void CFormEditor::AddExposedObject(
	const std::wstring &Name,const CComPtr<IDispatch> &spDispatch)
{
	// the object name should be valid
	ATLASSERT(IsNameValid(Name));
	// the object name should be unique
	ATLASSERT(IsNameUnique(Name));
	// add the object to the exposed objects map
	m_ExposedObjects[Name]=spDispatch;
}

BOOL CFormEditor::IsEventHandled(
	const std::wstring &Source,const std::wstring &Event)
{
	// determine if the event is handled by the script
	VARIANT_BOOL Handled=VARIANT_FALSE;
	if(!SUCCEEDED(m_spScript->IsEventHandled(
		CComBSTR(Source.c_str()),CComBSTR(Event.c_str()),&Handled)))
	{
		throw std::exception();
	}
	return VB2B(Handled);
}

void CFormEditor::AddEventHandler(const CEventInfo &EventInfo)
{
	// add the event handler to the script
	if(!SUCCEEDED(m_spScript->AddEventHandler(
		CComBSTR(EventInfo.m_SourceName.c_str()),
		CComBSTR(EventInfo.m_EventName.c_str()),
		CComBSTR(EventInfo.m_ParameterNames.c_str()))))
	{
		throw std::exception();
	}
}

void CFormEditor::EditEventHandler(
	const std::wstring &Source,const std::wstring &Event)
{
	// edit the event handler in the script
	if(!SUCCEEDED(m_spScript->EditEventHandler(
		CComBSTR(Source.c_str()),CComBSTR(Event.c_str()))))
	{
		throw std::exception();
	}
}

CMenuHandle CFormEditor::CreateEventMenu(
	const std::wstring &SourceName,
	const CComPtr<IDispatch> &spDispatch,
	CAvailableMenuItemIdSet &AvailableMenuItemIdSet,
	CMenuItemIdToEventInfoMap &MenuItemIdToEventInfoMap)
{
	// type info for the event interface
	CComPtr<ITypeInfo> spTypeInfo;
	try
	{
		// get type info for the coclass
		spTypeInfo=GetImplementingCoClassTypeInfo(spDispatch);
		// get type info for the event interface
		spTypeInfo=GetDefaultSourceInterfaceTypeInfo(spTypeInfo);
	}
	catch(std::exception &)
	{
		// return an empty menu since GetImplementingCoClassTypeInfo() and
		// GetDefaultSourceInterfaceTypeInfo() may fail for valid reasons
		return CreateEmptyPopupMenu(IDS_EMPTY);
	}
	// create the event menu
	return CreateEventMenu(SourceName,spTypeInfo,
		AvailableMenuItemIdSet,MenuItemIdToEventInfoMap);
}

CMenuHandle CFormEditor::CreateEventMenu(
	const std::wstring &SourceName,
	const CComPtr<ITypeInfo> &spTypeInfo,
	CAvailableMenuItemIdSet &AvailableMenuItemIdSet,
	CMenuItemIdToEventInfoMap &MenuItemIdToEventInfoMap)
{
	USES_CONVERSION;

	// create the event menu
	CMenu Menu;
	if(Menu.CreatePopupMenu()==FALSE)
	{
		throw std::exception();
	}
	// get the number of events on the interface
	CAutoTypeAttrPtr pTypeAttr(spTypeInfo);
	long EventCount=pTypeAttr->cFuncs;
	// add all events to the event menu
	for(long l=0; l<EventCount; l++)
	{
		// get the event name
		std::wstring EventName=GetFunctionName(l,spTypeInfo);
		// determine if the event is handled by the script
		BOOL Handled=IsEventHandled(SourceName,EventName);
		// get the next available menu item id
		if(AvailableMenuItemIdSet.size()==0)
		{
			throw std::exception();
		}
		DWORD Id=*AvailableMenuItemIdSet.begin();
		// update the set
		AvailableMenuItemIdSet.erase(Id);
		// insert the new menu item
		if(Menu.AppendMenu(MF_STRING|Handled ? MF_CHECKED : MF_UNCHECKED,
			Id,W2CT(EventName.c_str()))==FALSE)
		{
			throw std::exception();
		}
		// create event info
		CEventInfo EventInfo;
		EventInfo.m_SourceName=SourceName;
		EventInfo.m_EventName=EventName;
		EventInfo.m_ParameterNames=
			GetCommaSeperatedFunctionParameterNames(l,spTypeInfo);
		// update the menu item id to event info map
		MenuItemIdToEventInfoMap[Id]=EventInfo;
	}
	// return the event menu
	return Menu.GetMenuItemCount() ? Menu.Detach() :
		// or an empty menu if the event menu is empty
		CreateEmptyPopupMenu(IDS_EMPTY);
}

CMenuHandle CFormEditor::CreateFormEventMenu(
	CAvailableMenuItemIdSet &AvailableMenuItemIdSet,
	CMenuItemIdToEventInfoMap &MenuItemIdToEventInfoMap)
{
	// load in the type library
	CComPtr<ITypeLib> spTypeLib;
	if(!SUCCEEDED(LoadRegTypeLib(LIBID_DDFORMSLib,
		TlbVerMaj,TlbVerMin,LOCALE_USER_DEFAULT,&spTypeLib)))
	{
		throw std::exception();
	}
	// get type info for the form viewer script events interface
	CComPtr<ITypeInfo> spTypeInfo;
	if(!SUCCEEDED(spTypeLib->GetTypeInfoOfGuid(
		DIID__IViewableFormEvents,&spTypeInfo)))
	{
		throw std::exception();
	}
	// create the form event menu
	return CreateEventMenu(m_FormName,spTypeInfo,
		AvailableMenuItemIdSet,MenuItemIdToEventInfoMap);
}

CMenuHandle CFormEditor::CreateItemEventMenu(
	CItemInfo &ItemInfo,
	CAvailableMenuItemIdSet &AvailableMenuItemIdSet,
	CMenuItemIdToEventInfoMap &MenuItemIdToEventInfoMap)
{
	// get the items IDispatch interface
	CComPtr<IDispatch> spDispatch;
	if(!SUCCEEDED(ItemInfo.m_HostWindow.QueryControl(&spDispatch)))
	{
		throw std::exception();
	}
	// create the item event menu
	return CreateEventMenu(ItemInfo.m_Name,spDispatch,
		AvailableMenuItemIdSet,MenuItemIdToEventInfoMap);
}

CMenuHandle CFormEditor::CreateExposedObjectEventMenu(
	const std::wstring &Name,
	CAvailableMenuItemIdSet &AvailableMenuItemIdSet,
	CMenuItemIdToEventInfoMap &MenuItemIdToEventInfoMap)
{
	// create the exposed object event menu
	return CreateEventMenu(Name,m_ExposedObjects[Name],
		AvailableMenuItemIdSet,MenuItemIdToEventInfoMap);
}

CMenuHandle CFormEditor::CreateExposedObjectsMenu(
	CAvailableMenuItemIdSet &AvailableMenuItemIdSet,
	CMenuItemIdToEventInfoMap &MenuItemIdToEventInfoMap)
{
	USES_CONVERSION;

	// create the exposed objects menu
	CMenu Menu;
	if(Menu.CreatePopupMenu()==FALSE)
	{
		throw std::exception();
	}
	// add all exposed objects to the exposed objects menu
	for(CObjectNameToIDispatchMap::const_iterator iter=m_ExposedObjects.begin();
		iter!=m_ExposedObjects.end(); iter++)
	{
		// get the exposed object name
		std::wstring Name=iter->first;
		// get the next available menu item id
		if(AvailableMenuItemIdSet.size()==0)
		{
			throw std::exception();
		}
		DWORD Id=*AvailableMenuItemIdSet.begin();
		// update the set
		AvailableMenuItemIdSet.erase(Id);
		// insert the new menu item
		if(Menu.AppendMenu(MF_STRING,Id,W2CT(Name.c_str()))==FALSE)
		{
			throw std::exception();
		}
		// insert the event menu
		SetPopupMenu((HMENU)Menu,Id,
			CreateExposedObjectEventMenu(Name,
				AvailableMenuItemIdSet,MenuItemIdToEventInfoMap));
	}
	// return the exposed objects menu
	return Menu.Detach();
}

void CFormEditor::DFNS_BeginMove()
{
	// get the editor state
	CFormEditorState State=GetState();

	// hide all drag frames
	HideAllDragFrames();
	(void)UpdateWindow();
	// modify the form window style to allow us to draw over everything
	(void)m_Form.UpdateWindow();
	(void)m_Form.ModifyStyle(WS_CLIPCHILDREN,0);

	// initialise the drag rects
	CItemInfoPtrList ItemInfoPtrList=GetSelectedItems();
	for(CItemInfoPtrList::const_iterator iter=
		ItemInfoPtrList.begin(); iter!=ItemInfoPtrList.end(); iter++)
	{
		CItemInfo &ItemInfo=**iter;
		// initialise the drag rect
		ItemInfo.m_LastDragRect=CRect(0,0,0,0);
	}
	// set the busy flag
	SetBusy(TRUE);
	FireStateChangedEvent(State);	// state changed
}

void CFormEditor::DFNS_Move(long cx,long cy)
{
	// if the alt key is down the grid settings are ignored
	BOOL IgnoreGrid=
		(GetKeyState(VK_MENU) & 0x80000000) ? TRUE : FALSE;
	// get the form rect
	CRect FormRect(0,0,0,0);
	(void)m_Form.GetClientRect(FormRect);

	// get the selected items bounding rectangle
	CRect BoundingRect=GetBoundingRectForSelectedItems();
	// offset the bounding rectangle
	BoundingRect.OffsetRect(cx,cy);
	// snap the bounding rectangle to the grid
	if(IgnoreGrid==FALSE)
	{
		BoundingRect=SnapRectToGrid(BoundingRect);
	}
	// ensure the bounding rectangle stays within the form
	BoundingRect=ForceRectIntoRect(FormRect,BoundingRect);
	// determine the final valid offset
	CSize FinalOffset=BoundingRect.TopLeft()-
		GetBoundingRectForSelectedItems().TopLeft();

	// update the drag rects
	CItemInfoPtrList ItemInfoPtrList=GetSelectedItems();
	for(CItemInfoPtrList::const_iterator iter=
		ItemInfoPtrList.begin(); iter!=ItemInfoPtrList.end(); iter++)
	{
		CItemInfo &ItemInfo=**iter;
		// get the new drag rect
		CRect DragRect=GetItemRect(ItemInfo);
		DragRect.OffsetRect(FinalOffset);
		// update the drag rect
		CSize Size(DRAG_RECT_SIZE,DRAG_RECT_SIZE);
		DrawDragRect(CClientDC(m_Form),
			DragRect,Size,ItemInfo.m_LastDragRect,Size);
		// save the new drag rect
		ItemInfo.m_LastDragRect=DragRect;
	}
}

void CFormEditor::DFNS_EndMove()
{
	// get the editor state
	CFormEditorState State=GetState();
	// update the undo storage
	UpdateUndoStorage();
	// clean up the drag rects
	CItemInfoPtrList ItemInfoPtrList=GetSelectedItems();
	for(CItemInfoPtrList::const_iterator iter=
		ItemInfoPtrList.begin(); iter!=ItemInfoPtrList.end(); iter++)
	{
		CItemInfo &ItemInfo=**iter;
		// remove the drag rect from the form
		CSize Size(DRAG_RECT_SIZE,DRAG_RECT_SIZE);
		DrawDragRect(CClientDC(m_Form),
			CRect(0,0,0,0),Size,ItemInfo.m_LastDragRect,Size);
	}
	// move the items
	for(iter=ItemInfoPtrList.begin(); iter!=ItemInfoPtrList.end(); iter++)
	{
		CItemInfo &ItemInfo=**iter;
		// if the drag rect is empty the user has not moved the item
		if(ItemInfo.m_LastDragRect!=CRect(0,0,0,0))
		{
			// move the item
			(void)ItemInfo.m_HostWindow.SetWindowPos(
				NULL,ItemInfo.m_LastDragRect,SWP_NOZORDER|SWP_NOSIZE);
		}
	}
	// resore the form window style
	(void)m_Form.ModifyStyle(0,WS_CLIPCHILDREN);
	// restore and reposition all drag frames
	RestoreAndRepositionAllDragFrames();
	// set the modified flag
	SetModified(TRUE);
	// reset the busy flag
	SetBusy(FALSE);
	FireStateChangedEvent(State);	// state changed
}

void CFormEditor::DFNS_BeginSize()
{
	// if the form is selected
	if(IsFormSelected())
	{
		// simulate the notification
		DFNS_BeginSizeForm();
		return;
	}
	// get the editor state
	CFormEditorState State=GetState();

	// get item info for the selected item
	ATLASSERT(GetSelectedItemCount()==1);
	CItemInfo &ItemInfo=*GetSelectedItems().front();

	// hide all drag frames
	HideAllDragFrames();
	(void)UpdateWindow();
	// modify the form window style to allow us to draw over everything
	(void)m_Form.UpdateWindow();
	(void)m_Form.ModifyStyle(WS_CLIPCHILDREN,0);

	// initialise the drag rect
	ItemInfo.m_LastDragRect=CRect(0,0,0,0);
	// set the busy flag
	SetBusy(TRUE);
	FireStateChangedEvent(State);	// state changed
}

void CFormEditor::DFNS_Size(CDragFrame::DragHandle Handle,long cx,long cy)
{
	// if the form is selected
	if(IsFormSelected())
	{
		// simulate the notification
		DFNS_SizeForm(Handle,cx,cy);
		return;
	}
	// get item info for the selected item
	ATLASSERT(GetSelectedItemCount()==1);
	CItemInfo &ItemInfo=*GetSelectedItems().front();

	// if the alt key is down the grid settings are ignored
	BOOL IgnoreGrid=
		(GetKeyState(VK_MENU) & 0x80000000) ? TRUE : FALSE;
	// get the form rect
	CRect FormRect(0,0,0,0);
	(void)m_Form.GetClientRect(FormRect);

	// get the item rect
	CRect ItemRect=GetItemRect(ItemInfo);
	// left edge size
	if(	Handle==CDragFrame::DragHandleBottomLeft	or
		Handle==CDragFrame::DragHandleLeft			or
		Handle==CDragFrame::DragHandleTopLeft)
	{
		// get the top left corner
		CPoint TopLeft=ItemRect.TopLeft();
		// apply the horizontal offset
		TopLeft.x+=cx;
		if(IgnoreGrid==FALSE)	// snap the top left corner to the grid
		{
			TopLeft=SnapPosToGrid(TopLeft);
		}
		ItemRect.left=TopLeft.x;
		// ensure the item width is not too small
		if(ItemRect.Width() < MIN_ITEM_WIDTH)
		{
			ItemRect.left=ItemRect.right-MIN_ITEM_WIDTH;
		}
		// ensure the item stays within the form
		(void)ItemRect.IntersectRect(FormRect,ItemRect);
	}
	// top edge size
	if(	Handle==CDragFrame::DragHandleTopLeft		or
		Handle==CDragFrame::DragHandleTop			or
		Handle==CDragFrame::DragHandleTopRight)
	{
		// get the top left corner
		CPoint TopLeft=ItemRect.TopLeft();
		// apply the vertical offset
		TopLeft.y+=cy;
		if(IgnoreGrid==FALSE)	// snap the top left corner to the grid
		{
			TopLeft=SnapPosToGrid(TopLeft);
		}
		ItemRect.top=TopLeft.y;
		// ensure the item height is not too small
		if(ItemRect.Height() < MIN_ITEM_HEIGHT)
		{
			ItemRect.top=ItemRect.bottom-MIN_ITEM_HEIGHT;
		}
		// ensure the item stays within the form
		(void)ItemRect.IntersectRect(FormRect,ItemRect);
	}
	// right edge size
	if(	Handle==CDragFrame::DragHandleTopRight		or
		Handle==CDragFrame::DragHandleRight			or
		Handle==CDragFrame::DragHandleBottomRight)
	{
		// get the bottom right corner
		CPoint BottomRight=ItemRect.BottomRight();
		// apply the horizontal offset
		BottomRight.x+=cx;
		if(IgnoreGrid==FALSE)	// snap the bottom right corner to the grid
		{
			BottomRight=SnapPosToGrid(BottomRight);
		}
		ItemRect.right=BottomRight.x;
		// ensure the item width is not too small
		if(ItemRect.Width() < MIN_ITEM_WIDTH)
		{
			ItemRect.right=ItemRect.left+MIN_ITEM_WIDTH;
		}
		// ensure the item stays within the form
		(void)ItemRect.IntersectRect(FormRect,ItemRect);
	}
	// bottom edge size
	if(	Handle==CDragFrame::DragHandleBottomRight	or
		Handle==CDragFrame::DragHandleBottom		or
		Handle==CDragFrame::DragHandleBottomLeft)
	{
		// get the bottom right corner
		CPoint BottomRight=ItemRect.BottomRight();
		// apply the vertical offset
		BottomRight.y+=cy;
		if(IgnoreGrid==FALSE)	// snap the bottom right corner to the grid
		{
			BottomRight=SnapPosToGrid(BottomRight);
		}
		ItemRect.bottom=BottomRight.y;
		// ensure the item height is not too small
		if(ItemRect.Height() < MIN_ITEM_HEIGHT)
		{
			ItemRect.bottom=ItemRect.top+MIN_ITEM_HEIGHT;
		}
		// ensure the item stays within the form
		(void)ItemRect.IntersectRect(FormRect,ItemRect);
	}
	// get the new drag rect
	CRect DragRect=ItemRect;
	// update the drag rect
	CSize Size(DRAG_RECT_SIZE,DRAG_RECT_SIZE);
	DrawDragRect(CClientDC(m_Form),
		DragRect,Size,ItemInfo.m_LastDragRect,Size);
	// save the new drag rect
	ItemInfo.m_LastDragRect=DragRect;
}

void CFormEditor::DFNS_EndSize()
{
	// if the form is selected
	if(IsFormSelected())
	{
		// simulate the notification
		DFNS_EndSizeForm();
		return;
	}
	// get the editor state
	CFormEditorState State=GetState();

	// get item info for the selected item
	ATLASSERT(GetSelectedItemCount()==1);
	CItemInfo &ItemInfo=*GetSelectedItems().front();

	// update the undo storage
	UpdateUndoStorage();
	// remove the drag rect from the form
	CSize Size(DRAG_RECT_SIZE,DRAG_RECT_SIZE);
	DrawDragRect(CClientDC(m_Form),
		CRect(0,0,0,0),Size,ItemInfo.m_LastDragRect,Size);
	// if the drag rect is empty the user has not sized the item
	if(ItemInfo.m_LastDragRect!=CRect(0,0,0,0))
	{
		// size the item
		(void)ItemInfo.m_HostWindow.SetWindowPos(
			NULL,ItemInfo.m_LastDragRect,SWP_NOZORDER);
	}
	// resore the form window style
	(void)m_Form.ModifyStyle(0,WS_CLIPCHILDREN);
	// restore and reposition all drag frames
	RestoreAndRepositionAllDragFrames();
	// set the modified flag
	SetModified(TRUE);
	// reset the busy flag
	SetBusy(FALSE);
	FireStateChangedEvent(State);	// state changed
}

void CFormEditor::DFNS_ContextMenu()
{
	// show the context menu
	// note that PostMessage() must be used here rather than SendMessage()
	// since the drag frame which this notification originated from could
	// be destroyed by the context menu handler
	(void)PostMessage(WM_CONTEXTMENU_INDIRECT);
}

void CFormEditor::DFNS_BeginSizeForm()
{
	// get the editor state
	CFormEditorState State=GetState();

	// hide all drag frames
	HideAllDragFrames();
	(void)UpdateWindow();
	// modify the window style to allow us to draw over everything
	(void)UpdateWindow();
	(void)ModifyStyle(WS_CLIPCHILDREN,0);

	// initialise the drag rect
	m_LastFormDragRect=CRect(0,0,0,0);
	// set the busy flag
	SetBusy(TRUE);
	FireStateChangedEvent(State);	// state changed
}

void CFormEditor::DFNS_SizeForm(CDragFrame::DragHandle Handle,long cx,long cy)
{
	// check the drag handle is valid
	ATLASSERT(
		Handle==CDragFrame::DragHandleRight			or
		Handle==CDragFrame::DragHandleBottomRight	or
		Handle==CDragFrame::DragHandleBottom);
	// if the alt key is down the grid settings are ignored
	BOOL IgnoreGrid=
		(GetKeyState(VK_MENU) & 0x80000000) ? TRUE : FALSE;
	// get the form rect
	CRect FormRect(0,0,0,0);
	(void)m_Form.GetClientRect(FormRect);

	// get the minimum form dimensions
	long MinWidth=max(
		MIN_FORM_WIDTH,GetBoundingRectForAllItems().right);
	long MinHeight=max(
		MIN_FORM_HEIGHT,GetBoundingRectForAllItems().bottom);

	// get the bottom right corner
	CPoint BottomRight=FormRect.BottomRight();
	// apply the horizontal offset
	if(	Handle==CDragFrame::DragHandleRight or
		Handle==CDragFrame::DragHandleBottomRight)
	{
		BottomRight+=CPoint(cx,0);
	}
	// apply the vertical offset
	if(	Handle==CDragFrame::DragHandleBottomRight or
		Handle==CDragFrame::DragHandleBottom)
	{
		BottomRight+=CPoint(0,cy);
	}
	// snap the bottom right corner to the grid
	if(IgnoreGrid==FALSE)
	{
		BottomRight=SnapPosToGrid(BottomRight);
	}
	// update the form rect
	FormRect.right=BottomRight.x;
	FormRect.bottom=BottomRight.y;
	// ensure the width is in range
	FormRect.right=min(FormRect.right,MAX_FORM_WIDTH);
	FormRect.right=max(FormRect.right,MinWidth);
	// ensure the height is in range
	FormRect.bottom=min(FormRect.bottom,MAX_FORM_HEIGHT);
	FormRect.bottom=max(FormRect.bottom,MinHeight);

	// get the new drag rect
	CRect DragRect=FormRect;
	DragRect.OffsetRect(	// offset due to the drag frame
		m_FormDragFrame.GetFrameSize(),m_FormDragFrame.GetFrameSize());
	DragRect.OffsetRect(	// offset due to the scroll position
		-GetScrollPos(SB_HORZ),-GetScrollPos(SB_VERT));
	// update the drag rect
	CSize Size(DRAG_RECT_SIZE,DRAG_RECT_SIZE);
	DrawDragRect(CClientDC(*this),
		DragRect,Size,m_LastFormDragRect,Size);
	// save the new drag rect
	m_LastFormDragRect=DragRect;
}

void CFormEditor::DFNS_EndSizeForm()
{
	// get the editor state
	CFormEditorState State=GetState();
	// update the undo storage
	UpdateUndoStorage();
	// remove the drag rect
	CSize Size(DRAG_RECT_SIZE,DRAG_RECT_SIZE);
	DrawDragRect(CClientDC(*this),
		CRect(0,0,0,0),Size,m_LastFormDragRect,Size);
	// if the drag rect is empty the user has not sized the form
	if(m_LastFormDragRect!=CRect(0,0,0,0))
	{
		// size the form
		SetFormDimensions(
			m_LastFormDragRect.Width(),m_LastFormDragRect.Height());
	}
	// resore the window style
	(void)ModifyStyle(0,WS_CLIPCHILDREN);
	// restore and reposition all drag frames
	RestoreAndRepositionAllDragFrames();
	// set the modified flag
	SetModified(TRUE);
	// reset the busy flag
	SetBusy(FALSE);
	FireStateChangedEvent(State);	// state changed
}

HRESULT CFormEditor::OnDraw(ATL_DRAWINFO &di)
{
	// ATL wizard generated
IMP_BEGIN
	// dc wrapper
	CDCHandle dc(di.hdcDraw);
	// bounding rectangle
	CRect Rect=*(RECT*)di.prcBounds;

	// some containers will only provide a window for us to draw on using
	// this function - ie the control is not created

	if(IsWindow()==FALSE)	// skip if we are created
	{
		// draw rectangle
		(void)dc.Rectangle(Rect);
		// show text
		LPCTSTR pszText=_T("FormEditor");
		(void)dc.SetTextAlign(TA_CENTER|TA_BASELINE);
		(void)dc.TextOut(
			(Rect.left+Rect.right)/2,(Rect.top+Rect.bottom)/2,
				pszText,lstrlen(pszText));
	}
	else
	{
		// fill in the background
		COLORREF RGBBackColor=RGB(0,0,0);
		if(!SUCCEEDED(OleTranslateColor(m_BackColor,NULL,&RGBBackColor)))
		{
			throw std::exception();
		}
		dc.FillSolidRect(Rect,RGBBackColor);
		// update all drag frames
		if(IsFormSelected())
		{
			// form drag frame
			(void)m_FormDragFrame.Invalidate(FALSE);
		}
		// item drag frames
		CItemInfoPtrList ItemInfoPtrList=GetSelectedItems();
		for(CItemInfoPtrList::const_iterator iter=
			ItemInfoPtrList.begin(); iter!=ItemInfoPtrList.end(); iter++)
		{
			CItemInfo &ItemInfo=**iter;
			// update the drag frame
			(void)ItemInfo.m_DragFrame.Invalidate(FALSE);
		}
	}
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::OnAmbientPropertyChange(DISPID dispid)
{
IMP_BEGIN
	// call the base class
	HRESULT hr=IOleControlImpl<CFormEditor>::OnAmbientPropertyChange(dispid);
	ATLASSERT(hr==S_OK);	// S_OK is the only valid return value
	// if the user mode ambient property has changed
	if((dispid==DISPID_AMBIENT_USERMODE or dispid==DISPID_UNKNOWN) and
		// the editor may not have been created by the container if it
		// prefers to create a window only for us to draw on in design mode
		IsWindow())
	{
		// the editor should not be busy
		ATLASSERT(m_Busy==FALSE);

		// hide the form tab number in run mode
		(void)m_FormTabNumber.ShowWindow(InRunMode() ? SW_HIDE : SW_SHOW);
		// disable the editor in design mode
		(void)EnableWindow(InDesignMode() ? FALSE : TRUE);
	}
IMP_END
	return S_OK;	// must always return S_OK
}

LRESULT CFormEditor::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
TRY
	// set the window style
	if(m_BorderVisible==VARIANT_FALSE)
	{
		(void)ModifyStyleEx(WS_EX_CLIENTEDGE,0);
	}
	// set the scrollbar ranges
	long DragFrameSize=m_FormDragFrame.GetFrameSize();
	if(SetScrollRange(
		SB_HORZ,0,MAX_FORM_WIDTH+(2*DragFrameSize),FALSE)==FALSE)
	{
		throw std::exception();
	}
	if(SetScrollRange(
		SB_VERT,0,MAX_FORM_HEIGHT+(2*DragFrameSize),FALSE)==FALSE)
	{
		throw std::exception();
	}
	// set the timer
	if(SetTimer(TIMER_ID,TIMER_ELAPSE)==0)
	{
		throw std::exception();
	}
	// enable control containment
	if(AtlAxWinInit()==FALSE)
	{
		throw std::exception();
	}
	// create the script
	if(m_spScript==NULL)
	{
		if(!SUCCEEDED(CScript::CreateInstance(&m_spScript)))
		{
			throw std::exception();
		}
	}
	// the form dimensions and colors should be saved as the default
	m_Form.GetDimensions(m_DefaultFormWidth,m_DefaultFormHeight);
	m_Form.GetColors(m_DefaultFormBackColor,m_DefaultFormForeColor);
	// create the form
	CRect FormRect(0,0,m_DefaultFormWidth,m_DefaultFormHeight);
	FormRect.OffsetRect(DragFrameSize,DragFrameSize);
	if(m_Form.Create(*this,FormRect,_T("EditableForm"),
		WS_CHILD|WS_VISIBLE|WS_CLIPCHILDREN|WS_DISABLED,WS_EX_CONTROLPARENT)==NULL)
	{
		throw std::exception();
	}
	// create the form tab number
	if(m_FormTabNumber.Create(m_Form,m_Form,0)==FALSE)
	{
		throw std::exception();
	}
	m_FormTabNumber.SetTabNumber(0);
	// create the item root window
	m_ItemRootWindow.m_hWnd=NULL;	// allow the wrapper to be reused
	if(m_ItemRootWindow.Create(_T("STATIC"),
		m_Form,CRect(0,0,0,0),_T("ItemRootWindow"),WS_CHILD|WS_DISABLED,0)==NULL)
	{
		throw std::exception();
	}
	// register the custom clipboard format
	m_ClipboardFormatId=RegisterClipboardFormat(CLIPBOARD_FORMAT_NAME);
	if(m_ClipboardFormatId==0)
	{
		throw std::exception();
	}
	// create the undo storage
	if(m_UndoStorage.IsCreated()==FALSE)
	{
		m_UndoStorage.Create();
	}
	// create the redo storage
	if(m_RedoStorage.IsCreated()==FALSE)
	{
		m_RedoStorage.Create();
	}
	// select the form
	SelectForm();

	// hide the form tab number in run mode
	if(InRunMode())
	{
		(void)m_FormTabNumber.ShowWindow(SW_HIDE);
	}
	// disable the editor in design mode
	if(InDesignMode())
	{
		(void)EnableWindow(FALSE);
	}
	// initial state
	FireStateChangedEvent(CFormEditorState(),TRUE);
CATCH_ALL
	return Caught ? -1 : 0;	// fail creation if an exception was thrown
}

LRESULT CFormEditor::OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
TRY
	// the editor should not be busy
	ATLASSERT(m_Busy==FALSE);

	// kill the timer
	(void)KillTimer(TIMER_ID);
	// clean up on deactivation
	// this is required since the editor state and all properties
	// must still be valid if queried in the deactive state
	DeleteAllItems();
	// make the next activation like a first time activation
	if(m_UndoStorage.IsCreated())
	{
		ClearUndoStorage();
	}
	if(m_RedoStorage.IsCreated())
	{
		ClearRedoStorage();
	}
	SetModified(FALSE);
	// deactivated state
	FireStateChangedEvent(CFormEditorState(),TRUE);
CATCH_ALL
	return 0;
}

LRESULT CFormEditor::OnEraseBkgnd(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	// to prevent flicker do not erase the background
	return TRUE;
}

LRESULT CFormEditor::OnHScroll(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
TRY
	// get our client rect
	CRect ClientRect(0,0,0,0);
	(void)GetClientRect(ClientRect);

	// get the minimum and maximum scroll positions
	int MinPos=0;
	int MaxPos=0;
	(void)GetScrollRange(SB_HORZ,&MinPos,&MaxPos);

	// get the new scroll position
	long ScrollPos=GetScrollPos(SB_HORZ);
	switch(LOWORD(wParam))
	{
	case SB_ENDSCROLL:
		// ignored until later
		break;
	case SB_LEFT:
		ScrollPos=MinPos;
		break;
	case SB_RIGHT:
		ScrollPos=MaxPos;
		break;
	case SB_LINELEFT:
		ScrollPos-=HSCROLL_LINE_SIZE;
		break;
	case SB_LINERIGHT:
		ScrollPos+=HSCROLL_LINE_SIZE;
		break;
	case SB_PAGELEFT:
		ScrollPos-=ClientRect.Width();
		break;
	case SB_PAGERIGHT:
		ScrollPos+=ClientRect.Width();
		break;
	case SB_THUMBPOSITION:
	case SB_THUMBTRACK:
		ScrollPos=HIWORD(wParam);
		break;
	default:
		ATLASSERT(FALSE);	// unexpected
		break;
	}

	if(LOWORD(wParam)!=SB_ENDSCROLL)
	{
		// hide all drag frames
		HideAllDragFrames();
		// hide all tab numbers
		if(m_TabOrderingModeActive)
		{
			HideAllTabNumbers();
		}
		// check the new scroll position is in range
		ScrollPos=min(ScrollPos,MaxPos);
		ScrollPos=max(ScrollPos,MinPos);
		// update the scrollbar
		(void)SetScrollPos(SB_HORZ,ScrollPos);

		// scroll the form horizontally
		CRect FormRect(0,0,0,0);
		(void)m_Form.GetWindowRect(FormRect);	// window rect
		(void)ScreenToClient(FormRect);			// reletive to our client
		(void)m_Form.SetWindowPos(NULL,
			(-ScrollPos)+m_FormDragFrame.GetFrameSize(),FormRect.top,
				0,0,SWP_NOZORDER|SWP_NOSIZE);
	}
	else
	{
		// restore and reposition all drag frames
		RestoreAndRepositionAllDragFrames();
		// restore and reposition all tab numbers
		if(m_TabOrderingModeActive)
		{
			RestoreAndRepositionAllTabNumbers();
		}
	}
CATCH_ALL
	return 0;
}

LRESULT CFormEditor::OnVScroll(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
TRY
	// get our client rect
	CRect ClientRect(0,0,0,0);
	(void)GetClientRect(ClientRect);

	// get the minimum and maximum scroll positions
	int MinPos=0;
	int MaxPos=0;
	(void)GetScrollRange(SB_VERT,&MinPos,&MaxPos);

	// get the new scroll position
	long ScrollPos=GetScrollPos(SB_VERT);
	switch(LOWORD(wParam))
	{
	case SB_ENDSCROLL:
		// ignored until later
		break;
	case SB_TOP:
		ScrollPos=MinPos;
		break;
	case SB_BOTTOM:
		ScrollPos=MaxPos;
		break;
	case SB_LINEUP:
		ScrollPos-=VSCROLL_LINE_SIZE;
		break;
	case SB_LINEDOWN:
		ScrollPos+=VSCROLL_LINE_SIZE;
		break;
	case SB_PAGEUP:
		ScrollPos-=ClientRect.Height();
		break;
	case SB_PAGEDOWN:
		ScrollPos+=ClientRect.Height();
		break;
	case SB_THUMBPOSITION:
	case SB_THUMBTRACK:
		ScrollPos=HIWORD(wParam);
		break;
	default:
		ATLASSERT(FALSE);	// unexpected
		break;
	}

	if(LOWORD(wParam)!=SB_ENDSCROLL)
	{
		// hide all drag frames
		HideAllDragFrames();
		// hide all tab numbers
		if(m_TabOrderingModeActive)
		{
			HideAllTabNumbers();
		}
		// check the new scroll position is in range
		ScrollPos=min(ScrollPos,MaxPos);
		ScrollPos=max(ScrollPos,MinPos);
		// update the scrollbar
		(void)SetScrollPos(SB_VERT,ScrollPos);

		// scroll the form vertically
		CRect FormRect(0,0,0,0);
		(void)m_Form.GetWindowRect(FormRect);	// window rect
		(void)ScreenToClient(FormRect);			// reletive to our client
		(void)m_Form.SetWindowPos(NULL,
			FormRect.left,(-ScrollPos)+m_FormDragFrame.GetFrameSize(),
				0,0,SWP_NOZORDER|SWP_NOSIZE);
	}
	else
	{
		// restore and reposition all drag frames
		RestoreAndRepositionAllDragFrames();
		// restore and reposition all tab numbers
		if(m_TabOrderingModeActive)
		{
			RestoreAndRepositionAllTabNumbers();
		}
	}
CATCH_ALL
	return 0;
}

LRESULT CFormEditor::OnLButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
TRY
	// apply special processing if the editor is in tab ordering mode
	if(m_TabOrderingModeActive)
	{
		// see which item was clicked
		CItemInfo *pItemInfo=HitTest(ClientPosToFormPos(
			CPoint(GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam))));
		if(pItemInfo!=NULL)
		{
			// update the item tab order
			UpdateItemTabOrder(*pItemInfo);
		}
		else
		{
			// switch the editor back from tab ordering mode
			EndSetTabOrder();
			SelectForm();
		}
		return 0;
	}
	// get the editor state
	CFormEditorState State=GetState();
	// unselect the form
	UnselectForm();
	// if the ctrl key is not down unselect all items
	if(!(wParam & MK_CONTROL))
	{
		UnselectAllItems();
	}
	// see which item was clicked
	CItemInfo *pItemInfo=HitTest(ClientPosToFormPos(
		CPoint(GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam))));
	if(pItemInfo!=NULL)
	{
		// select the item
		SelectItem(*pItemInfo);
		// activate the item drag frame to allow immediate actions
		pItemInfo->m_DragFrame.ExternalActivate();
	}
	else
	{
		// initialise the selection box settings
		ATLASSERT(m_SelectionBoxActive==FALSE);
		m_SelectionBoxActive=TRUE;
		m_SelectionBoxAnchorPos=
			CPoint(GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam));
		m_LastSelectionBoxDragRect=CRect(0,0,0,0);
		// capture the cursor
		(void)SetCapture();
		// modify the window style to allow us to draw over everything
		(void)UpdateWindow();
		(void)ModifyStyle(WS_CLIPCHILDREN,0);
		// set the busy flag
		SetBusy(TRUE);
	}
	FireStateChangedEvent(State);	// state may have changed
CATCH_ALL
	return 0;
}

LRESULT CFormEditor::OnLButtonUP(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
TRY
	// get the editor state
	CFormEditorState State=GetState();
	if(m_SelectionBoxActive)
	{
		// remove the selection box drag rect
		CSize Size(DRAG_RECT_SIZE,DRAG_RECT_SIZE);
		DrawDragRect(CClientDC(*this),
			m_LastSelectionBoxDragRect,Size,CRect(0,0,0,0),Size);
		// select all items contained within the selection box
		SelectAllItemsInRect(
			ClientRectToFormRect(m_LastSelectionBoxDragRect));
		// if no items were selected select the form
		if(GetSelectedItemCount()==0)
		{
			SelectForm();
		}
		// clean up the selection box settings
		m_SelectionBoxActive=FALSE;
		// release the cursor
		// we need to be careful here since ReleaseCapture() calls
		// OnCaptureChanged() which in turn calls us back just incase
		// the capture was taken from under our feet
		if(GetCapture()==*this)
		{
			(void)ReleaseCapture();
		}
		// restore the window style
		(void)ModifyStyle(0,WS_CLIPCHILDREN);
		// reset the busy flag
		SetBusy(FALSE);
	}
	FireStateChangedEvent(State);	// state may have changed
CATCH_ALL
	return 0;
}

LRESULT CFormEditor::OnRButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
TRY
	// do nothing if the editor is busy
	if(m_Busy)
	{
		return 0;
	}
	// get the editor state
	CFormEditorState State=GetState();
	// see which item was clicked
	CItemInfo *pItemInfo=HitTest(ClientPosToFormPos(
		CPoint(GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam))));
	if(pItemInfo!=NULL)
	{
		// unselect the form
		UnselectForm();
		// unselect all items
		UnselectAllItems();
		// select the item
		SelectItem(*pItemInfo);
	}
	else
	{
		// unselect all items
		UnselectAllItems();
		// select the form
		SelectForm();
	}
	FireStateChangedEvent(State);	// state may have changed
CATCH_ALL
	return 0;
}

LRESULT CFormEditor::OnRButtonUp(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
TRY
	// show the context menu
	(void)PostMessage(WM_CONTEXTMENU_INDIRECT);
CATCH_ALL
	return 0;
}

LRESULT CFormEditor::OnMouseMove(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
TRY
	if(m_SelectionBoxActive)
	{
		// get the new selection box drag rect
		CRect DragRect(m_SelectionBoxAnchorPos,
			CPoint(GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam)));
		DragRect.NormalizeRect();
		// update the selection box drag rect
		CSize Size(DRAG_RECT_SIZE,DRAG_RECT_SIZE);
		DrawDragRect(CClientDC(*this),
			DragRect,Size,m_LastSelectionBoxDragRect,Size);
		// save the new selection box drag rect
		m_LastSelectionBoxDragRect=DragRect;
	}
CATCH_ALL
	return 0;
}

LRESULT CFormEditor::OnCaptureChanged(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
TRY
	// simulate a left button up message
	BOOL Dummy=FALSE;
	(void)OnLButtonUP(WM_LBUTTONUP,0,0,Dummy);
CATCH_ALL
	return 0;
}

LRESULT CFormEditor::OnContextMenuIndirect(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
TRY
	// show the context menu
	(void)SendMessage(WM_CONTEXTMENU,0,-1);
CATCH_ALL
	return 0;
}

LRESULT CFormEditor::OnContextMenu(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
TRY
	// do nothing if the editor is busy
	if(m_Busy)
	{
		return 0;
	}
	// get the editor state
	CFormEditorState State=GetState();
	// load in the context menu
	CMenu ParentMenu;
	if(ParentMenu.LoadMenu(IDR_CTXMENU_FORMEDITOR)==FALSE)
	{
		throw std::exception();
	}
	CMenuHandle hMenu=ParentMenu.GetSubMenu(0);
	if(hMenu==NULL)
	{
		throw std::exception();
	}
	// enable menu items which require a selection
	if(GetSelectedItemCount()==0)
	{
		// cut
		(void)hMenu.EnableMenuItem(
			ID_CUT,MF_BYCOMMAND|MF_GRAYED);
		// copy
		(void)hMenu.EnableMenuItem(
			ID_COPY,MF_BYCOMMAND|MF_GRAYED);
		// delete
		(void)hMenu.EnableMenuItem(
			ID_DELETE,MF_BYCOMMAND|MF_GRAYED);
	}
	// enable menu items which require a single selection
	if(GetSelectedItemCount() > 1)
	{
		// events
		(void)hMenu.EnableMenuItem(
			ID_EVENTS,MF_BYCOMMAND|MF_GRAYED);
		// properties
		(void)hMenu.EnableMenuItem(
			ID_PROPERTIES,MF_BYCOMMAND|MF_GRAYED);
	}
	// enable menu items which require a selection
	if(GetSelectedItemCount()==0 or
		// but not everything should be selected
		GetSelectedItemCount()==m_ItemInfoPtrList.size())
	{
		// send to back
		(void)hMenu.EnableMenuItem(
			ID_SENDTOBACK,MF_BYCOMMAND|MF_GRAYED);
		// bring to front
		(void)hMenu.EnableMenuItem(
			ID_BRINGTOFRONT,MF_BYCOMMAND|MF_GRAYED);
	}
	// enable menu items which require the clipboard format to be available
	if(m_CanPaste==FALSE)
	{
		// paste
		(void)hMenu.EnableMenuItem(
			ID_PASTE,MF_BYCOMMAND|MF_GRAYED);
	}
	// enable menu items which require exposed objects
	if(m_ExposedObjects.size()==0)
	{
		// exposed objects
		(void)hMenu.EnableMenuItem(
			ID_EXPOSED_OBJECTS,MF_BYCOMMAND|MF_GRAYED);
	}

	// create the available menu item id set
	CAvailableMenuItemIdSet AvailableMenuItemIdSet;
	for(long l=1; l<=1024; l++)
	{
		AvailableMenuItemIdSet.insert(l);
	}
	// remove used menu item ids
	AvailableMenuItemIdSet.erase(ID_CUT);
	AvailableMenuItemIdSet.erase(ID_COPY);
	AvailableMenuItemIdSet.erase(ID_PASTE);
	AvailableMenuItemIdSet.erase(ID_DELETE);
	AvailableMenuItemIdSet.erase(ID_SENDTOBACK);
	AvailableMenuItemIdSet.erase(ID_BRINGTOFRONT);
	AvailableMenuItemIdSet.erase(ID_EVENTS);
	AvailableMenuItemIdSet.erase(ID_EXPOSED_OBJECTS);
	AvailableMenuItemIdSet.erase(ID_INSERT_ACTIVEX);
	AvailableMenuItemIdSet.erase(ID_PROPERTIES);
	// create the menu item id to event info map
	CMenuItemIdToEventInfoMap MenuItemIdToEventInfoMap;

	// if the form is selected
	if(IsFormSelected())
	{
		// create the form event menu
		CMenu Menu=CreateFormEventMenu(
			AvailableMenuItemIdSet,MenuItemIdToEventInfoMap);
		SetPopupMenu(hMenu,ID_EVENTS,(HMENU)Menu);
		(void)Menu.Detach();	// detach on success
	}
	// if there is a single item selected
	else if(GetSelectedItemCount()==1)
	{
		// get item info for the selected item
		CItemInfo &ItemInfo=*GetSelectedItems().front();
		// create the item event menu
		CMenu Menu=CreateItemEventMenu(ItemInfo,
			AvailableMenuItemIdSet,MenuItemIdToEventInfoMap);
		SetPopupMenu(hMenu,ID_EVENTS,(HMENU)Menu);
		(void)Menu.Detach();	// detach on success
	}
	// if there are exposed objects
	if(m_ExposedObjects.size())
	{
		// create the exposed objects menu
		CMenu Menu=CreateExposedObjectsMenu(
			AvailableMenuItemIdSet,MenuItemIdToEventInfoMap);
		SetPopupMenu(hMenu,ID_EXPOSED_OBJECTS,(HMENU)Menu);
		(void)Menu.Detach();	// detach on success
	}

	// get the cursor position
	CPoint CursorPos(0,0);
	(void)GetCursorPos(&CursorPos);
	// show the menu
	long Command=hMenu.TrackPopupMenu(
		TPM_NONOTIFY|TPM_RETURNCMD|TPM_LEFTBUTTON|TPM_RIGHTBUTTON,
			CursorPos.x,CursorPos.y,*this);
	// process the command
	switch(Command)
	{
	// cut
	case ID_CUT:
		(void)Cut();			// delegate to IFormEditor
		break;
	// copy
	case ID_COPY:
		(void)Copy();			// delegate to IFormEditor
		break;
	// paste
	case ID_PASTE:
		(void)Paste();			// delegate to IFormEditor
		break;
	// delete
	case ID_DELETE:
		(void)Delete();			// delegate to IFormEditor
		break;
	// send to back
	case ID_SENDTOBACK:
		(void)SendToBack();		// delegate to IFormEditor
		break;
	// bring to front
	case ID_BRINGTOFRONT:
		(void)BringToFront();	// delegate to IFormEditor
		break;
	// properties
	case ID_PROPERTIES:
		(void)Properties();		// delegate to IFormEditor
		break;
	// unknown
	default:
		break;
	}
	// if the command is a request to add or edit an event handler
	if(MenuItemIdToEventInfoMap.
		find(Command)!=MenuItemIdToEventInfoMap.end())
	{
		// get event info
		CEventInfo EventInfo=MenuItemIdToEventInfoMap[Command];
		// determine if the event is handled by the script
		BOOL Handled=IsEventHandled(
			EventInfo.m_SourceName,EventInfo.m_EventName);
		// add the event handler
		if(Handled==FALSE)
		{
			AddEventHandler(EventInfo);
		}
		// edit the event handler
		EditEventHandler(
			EventInfo.m_SourceName,EventInfo.m_EventName);
		// fire the relevant event
		if(Handled)
		{
			(void)Fire_EditExistingEventHandler();
		}
		else
		{
			(void)Fire_EditNewEventHandler();
		}
	}
	// if the command is a request to insert an item
	if(Command==ID_INSERT_ACTIVEX)
	{
		// show the insert item dialog
		CInsertItemDlg Dlg;
		if(Dlg.DoModal()==IDOK)
		{
			// delegate to IFormEditor
			CComPtr<IDispatch> spDispatch;
			(void)Insert(CComBSTR(Dlg.m_ProgId.c_str()),&spDispatch);
		}
	}
	FireStateChangedEvent(State);	// state may have changed
CATCH_ALL
	return 0;
}

LRESULT CFormEditor::OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
TRY
	// get the editor state
	CFormEditorState State=GetState();
	// determine if the clipboard format is available
	m_CanPaste=IsClipboardFormatAvailable(m_ClipboardFormatId);
	FireStateChangedEvent(State);	// state may have changed
CATCH_ALL
	return 0;
}

LRESULT CFormEditor::OnGetExtendedPropPageData(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
TRY
	// get item info for the selected item
	ATLASSERT(GetSelectedItemCount()==1);
	CItemInfo &ItemInfo=*GetSelectedItems().front();
	// check parameters
	if(lParam==NULL)
	{
		throw std::exception();
	}
	// return the extended property page data
	CExtendedPropPageData *pData=(CExtendedPropPageData *)lParam;
	pData->m_Name=ItemInfo.m_Name;
CATCH_ALL
	return Caught ? FALSE : TRUE;	// return TRUE on success
}

LRESULT CFormEditor::OnSetExtendedPropPageData(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
TRY
	// get item info for the selected item
	ATLASSERT(GetSelectedItemCount()==1);
	CItemInfo &ItemInfo=*GetSelectedItems().front();
	// check parameters
	if(lParam==NULL)
	{
		throw std::exception();
	}
	// set the extended property page data
	CExtendedPropPageData *pData=(CExtendedPropPageData *)lParam;
	// the name must be valid
	if(IsNameValid(pData->m_Name)==FALSE)
	{
		throw std::exception();
	}
	// the name must be unique
	if(IsNameUnique(pData->m_Name,ItemInfo.m_HostWindow)==FALSE)
	{
		throw std::exception();
	}
	// set the name
	ItemInfo.m_Name=pData->m_Name;
CATCH_ALL
	return Caught ? FALSE : TRUE;	// return TRUE on success
}

// begin standard method implementation block
#define METHOD_BEGIN										\
	if(IsWindow()==FALSE)									\
	{														\
		throw CHResult(E_FAIL);								\
	}														\
	if(m_Busy)												\
	{														\
		throw CHResult(E_FAIL);								\
	}														\
	CFormEditorState State=GetState();						\

// end standard method implementation block
#define METHOD_END											\
	FireStateChangedEvent(State);

// begin standard property put implementation block
#define PROPPUT_BEGIN										\
	if(m_Busy)												\
	{														\
		throw CHResult(E_FAIL);								\
	}														\
	CFormEditorState State=GetState();

// end standard property put implementation block
#define PROPPUT_END											\
	FireStateChangedEvent(State);

// begin standard property get implementation block
#define PROPGET_BEGIN										\
	if(m_Busy)												\
	{														\
		throw CHResult(E_FAIL);								\
	}														\
	CFormEditorState State=GetState();

// end standard property get implementation block
#define PROPGET_END											\
	FireStateChangedEvent(State);

// unselects everything
#define UNSELECT_EVERYTHING									\
	UnselectForm();											\
	UnselectAllItems();

// selects an item ensuring nothing else is selected
#define SELECT_SINGLE_ITEM(ii)								\
	UNSELECT_EVERYTHING;									\
	SelectItem(ii);

// selects the form ensuring nothing else is selected
#define SELECT_FORM											\
	UnselectAllItems();										\
	SelectForm();

// ensures something is selected (with a preference on items)
#define SELECT_SOMETHING									\
	if(GetSelectedItemCount()==0)							\
	{														\
		if(m_ItemInfoPtrList.size()!=0)						\
		{													\
			SELECT_SINGLE_ITEM(*m_ItemInfoPtrList.back());	\
		}													\
		else												\
		{													\
			SELECT_FORM;									\
		}													\
	}

STDMETHODIMP CFormEditor::About()
{
// this is allowed not matter what state the editor is in
IMP_BEGIN
	// show the about box
	CSimpleDialog<IDD_ABOUT_FORMEDITOR> AboutDlg;
	(void)AboutDlg.DoModal();
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::SelectAll()
{
IMP_BEGIN
METHOD_BEGIN
	// select all items
	if(m_ItemInfoPtrList.size()!=0)
	{
		UnselectForm();
		SelectAllItems();
	}
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::SelectNone()
{
IMP_BEGIN
METHOD_BEGIN
	// unselect all items
	if(GetSelectedItemCount()!=0)
	{
		UnselectAllItems();
		SelectForm();
	}
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::New()
{
IMP_BEGIN
METHOD_BEGIN
	// delegate to NewEx()
	return NewEx(
		m_DefaultFormWidth,m_DefaultFormHeight,
			m_DefaultFormBackColor,m_DefaultFormForeColor);
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::NewEx(
	long Width,long Height,OLE_COLOR BackColor,OLE_COLOR ForeColor)
{
IMP_BEGIN
METHOD_BEGIN
	// check parameters
	if(Width < MIN_FORM_WIDTH or Width > MAX_FORM_WIDTH)
	{
		throw CHResult(E_INVALIDARG);
	}
	if(Height < MIN_FORM_HEIGHT or Height > MAX_FORM_HEIGHT)
	{
		throw CHResult(E_INVALIDARG);
	}
	UNSELECT_EVERYTHING;
	// delete all items
	DeleteAllItems();
	// clear the undo and redo storage
	ClearUndoAndRedoStorage();
	// reset the scrollbars
	(void)SetScrollPos(SB_HORZ,0);
	(void)SetScrollPos(SB_VERT,0);
	// reset the form position
	(void)m_Form.SetWindowPos(NULL,
		m_FormDragFrame.GetFrameSize(),m_FormDragFrame.GetFrameSize(),
			0,0,SWP_NOZORDER|SWP_NOSIZE|SWP_NOCOPYBITS/*prevent flicker*/);
	// reset the script
	ResetScript();
	// initialise a new form
	InitialiseForm(
		DEFAULT_FORM_NAME,CSize(Width,Height),BackColor,ForeColor);
	SELECT_FORM;
	// reset the modified flag
	SetModified(FALSE);
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::ShowInsertDialog(BSTR *pProgID)
{
IMP_BEGIN
METHOD_BEGIN
	// check parameters
	if(pProgID==NULL)
	{
		throw CHResult(E_POINTER);
	}
	// initialise the return value to an empty string
	*pProgID=NULL;
	// show the insert item dialog
	CInsertItemDlg Dlg;
	if(Dlg.DoModal()==IDOK)
	{
		*pProgID=CComBSTR(Dlg.m_ProgId.c_str()).Detach();
	}
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::Insert(BSTR ProgID,IDispatch **ppDispatch)
{
IMP_BEGIN
METHOD_BEGIN
	// delegate to InsertEx
	return InsertEx(ProgID,0,0,DEFAULT_ITEM_WIDTH,DEFAULT_ITEM_HEIGHT,ppDispatch);
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::InsertEx(
	BSTR ProgID,long Left,long Top,long Width,long Height,IDispatch **ppDispatch)
{
IMP_BEGIN
METHOD_BEGIN
	// check parameters
	if(Width < MIN_ITEM_WIDTH)
	{
		throw CHResult(E_INVALIDARG);
	}
	if(Height < MIN_ITEM_HEIGHT)
	{
		throw CHResult(E_INVALIDARG);
	}
	if(ppDispatch==NULL)
	{
		throw CHResult(E_POINTER);
	}
	// get the form rect
	CRect FormRect(0,0,0,0);
	(void)m_Form.GetClientRect(FormRect);
	// get the item rect
	CRect ItemRect(Left,Top,Left+Width,Top+Height);
	// check the item fits within the form
	CRect IntersectRect(0,0,0,0);
	(void)IntersectRect.IntersectRect(FormRect,ItemRect);
	if(IntersectRect!=ItemRect)
	{
		throw CHResult(E_INVALIDARG);
	}
	// update the undo storage
	UpdateUndoStorage();
	// insert the new item
	CItemInfo &ItemInfo=InsertItem(BSTR2W(ProgID),ItemRect);
	// get the new items IDispatch interface
	CComPtr<IDispatch> spDispatch;
	if(!SUCCEEDED(ItemInfo.m_HostWindow.QueryControl(&spDispatch)))
	{
		throw CHResult(E_FAIL);
	}
	*ppDispatch=spDispatch.Detach();
	// select the new item
	SELECT_SINGLE_ITEM(ItemInfo);
	// set the modified flag
	SetModified(TRUE);
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::Delete()
{
IMP_BEGIN
METHOD_BEGIN
	// check there are items selected
	if(GetSelectedItemCount()==0)
	{
		throw CHResult(E_FAIL);
	}
	// update the undo storage
	UpdateUndoStorage();
	// delete selected items
	DeleteSelectedItems();
	SELECT_SOMETHING;
	// set the modified flag
	SetModified(TRUE);
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::Cut()
{
IMP_BEGIN
METHOD_BEGIN
	// check there are items selected
	if(GetSelectedItemCount()==0)
	{
		throw CHResult(E_FAIL);
	}
	// update the undo storage
	UpdateUndoStorage();
	// cut selected items to the clipboard
	CutSelectedItemsToClipboard();
	SELECT_SOMETHING;
	// set the modified flag
	SetModified(TRUE);
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::Copy()
{
IMP_BEGIN
METHOD_BEGIN
	// check there are items selected
	if(GetSelectedItemCount()==0)
	{
		throw CHResult(E_FAIL);
	}
	// copy selected items to the clipboard
	CopySelectedItemsToClipboard();
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::Paste()
{
IMP_BEGIN
METHOD_BEGIN
	// check the clipboard format is available
	if(m_CanPaste==FALSE)
	{
		throw CHResult(E_FAIL);
	}
	// update the undo storage
	UpdateUndoStorage();
	// paste items from the clipboard
	UNSELECT_EVERYTHING;
	SelectItems(PasteItemsFromClipboard());
	// set the modified flag
	SetModified(TRUE);
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::Undo()
{
IMP_BEGIN
METHOD_BEGIN
	// check there is something to undo
	if(m_UndoStorage.GetElementCount()==0)
	{
		throw CHResult(E_FAIL);
	}
	// undo
	UNSELECT_EVERYTHING;
	UndoLastChange();
	SELECT_FORM;
	// set the modified flag
	SetModified(TRUE);
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::Redo()
{
IMP_BEGIN
METHOD_BEGIN
	// check there is something to redo
	if(m_RedoStorage.GetElementCount()==0)
	{
		throw CHResult(E_FAIL);
	}
	// redo
	UNSELECT_EVERYTHING;
	RedoLastUndo();
	SELECT_FORM;
	// set the modified flag
	SetModified(TRUE);
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::SetTabOrder()
{
IMP_BEGIN
METHOD_BEGIN
	// check there are items to set the tab order of
	if(m_ItemInfoPtrList.size()==0)
	{
		throw CHResult(E_FAIL);
	}
	// switch the editor into tab ordering mode
	UNSELECT_EVERYTHING;
	BeginSetTabOrder();
	// the modified flag and undo storage are updated internally
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::RefreshState()
{
IMP_BEGIN
METHOD_BEGIN
	// refresh the state
	FireStateChangedEvent(GetState(),TRUE);
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::LoadFromFile(BSTR FileName)
{
IMP_BEGIN
METHOD_BEGIN
	// open the storage
	CComPtr<IStorage> spStorage;
	if(!SUCCEEDED(StgOpenStorage(BSTR2W(FileName),NULL,
		STGM_DIRECT|STGM_READ|STGM_SHARE_EXCLUSIVE,
			NULL,0,&spStorage)))
	{
		throw CHResult(E_FAIL);
	}
	// open the stream
	CComPtr<IStream> spStream;
	if(!SUCCEEDED(spStorage->OpenStream(STREAM_NAME,NULL,
		STGM_DIRECT|STGM_READ|STGM_SHARE_EXCLUSIVE,
			0,&spStream)))
	{
		throw CHResult(E_FAIL);
	}
	// load the form
	UNSELECT_EVERYTHING;
	(void)InternalLoad(spStream,StreamTypeFull);
	SELECT_FORM;
	// reset the modified flag
	SetModified(FALSE);
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::SaveToFile(BSTR FileName)
{
IMP_BEGIN
METHOD_BEGIN
	// create the storage
	CComPtr<IStorage> spStorage;
	if(!SUCCEEDED(StgCreateDocfile(BSTR2W(FileName),
		STGM_DIRECT|STGM_WRITE|STGM_SHARE_EXCLUSIVE|STGM_CREATE,
			0,&spStorage)))
	{
		throw CHResult(E_FAIL);
	}
	// create the stream
	CComPtr<IStream> spStream;
	if(!SUCCEEDED(spStorage->CreateStream(STREAM_NAME,
		STGM_DIRECT|STGM_WRITE|STGM_SHARE_EXCLUSIVE|STGM_CREATE,
			0,0,&spStream)))
	{
		throw CHResult(E_FAIL);
	}
	// save the form
	InternalSave(spStream,StreamTypeFull);
	// reset the modified flag
	SetModified(FALSE);
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::ValidateScript(VARIANT_BOOL *pRet)
{
IMP_BEGIN
METHOD_BEGIN
	// check parameters
	if(pRet==NULL)
	{
		throw CHResult(E_POINTER);
	}
	// validate the script
	*pRet=B2VB(InternalValidateScript());
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::MoveLeft()
{
IMP_BEGIN
METHOD_BEGIN
	// check there are items selected
	if(GetSelectedItemCount()==0)
	{
		throw CHResult(E_FAIL);
	}
	// update the undo storage
	UpdateUndoStorage();
	// move selected items left
	HideAllDragFrames();
	MoveSelectedItems(MoveTypeLeft);
	RestoreAndRepositionAllDragFrames();
	// set the modified flag
	SetModified(TRUE);
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::MoveRight()
{
IMP_BEGIN
METHOD_BEGIN
	// check there are items selected
	if(GetSelectedItemCount()==0)
	{
		throw CHResult(E_FAIL);
	}
	// update the undo storage
	UpdateUndoStorage();
	// move selected items right
	HideAllDragFrames();
	MoveSelectedItems(MoveTypeRight);
	RestoreAndRepositionAllDragFrames();
	// set the modified flag
	SetModified(TRUE);
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::MoveUp()
{
IMP_BEGIN
METHOD_BEGIN
	// check there are items selected
	if(GetSelectedItemCount()==0)
	{
		throw CHResult(E_FAIL);
	}
	// update the undo storage
	UpdateUndoStorage();
	// move selected items up
	HideAllDragFrames();
	MoveSelectedItems(MoveTypeUp);
	RestoreAndRepositionAllDragFrames();
	// set the modified flag
	SetModified(TRUE);
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::MoveDown()
{
IMP_BEGIN
METHOD_BEGIN
	// check there are items selected
	if(GetSelectedItemCount()==0)
	{
		throw CHResult(E_FAIL);
	}
	// update the undo storage
	UpdateUndoStorage();
	// move selected items down
	HideAllDragFrames();
	MoveSelectedItems(MoveTypeDown);
	RestoreAndRepositionAllDragFrames();
	// set the modified flag
	SetModified(TRUE);
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::AlignLeft()
{
IMP_BEGIN
METHOD_BEGIN
	// check there are enough items selected
	if(GetSelectedItemCount() < 2)
	{
		throw CHResult(E_FAIL);
	}
	// update the undo storage
	UpdateUndoStorage();
	// align selected items to the left most
	HideAllDragFrames();
	AlignSelectedItems(AlignTypeLeft);
	RestoreAndRepositionAllDragFrames();
	// set the modified flag
	SetModified(TRUE);
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::AlignRight()
{
IMP_BEGIN
METHOD_BEGIN
	// check there are enough items selected
	if(GetSelectedItemCount() < 2)
	{
		throw CHResult(E_FAIL);
	}
	// update the undo storage
	UpdateUndoStorage();
	// align selected items to the right most
	HideAllDragFrames();
	AlignSelectedItems(AlignTypeRight);
	RestoreAndRepositionAllDragFrames();
	// set the modified flag
	SetModified(TRUE);
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::AlignTop()
{
IMP_BEGIN
METHOD_BEGIN
	// check there are enough items selected
	if(GetSelectedItemCount() < 2)
	{
		throw CHResult(E_FAIL);
	}
	// update the undo storage
	UpdateUndoStorage();
	// align selected items to the top most
	HideAllDragFrames();
	AlignSelectedItems(AlignTypeTop);
	RestoreAndRepositionAllDragFrames();
	// set the modified flag
	SetModified(TRUE);
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::AlignBottom()
{
IMP_BEGIN
METHOD_BEGIN
	// check there are enough items selected
	if(GetSelectedItemCount() < 2)
	{
		throw CHResult(E_FAIL);
	}
	// update the undo storage
	UpdateUndoStorage();
	// align selected items to the bottom most
	HideAllDragFrames();
	AlignSelectedItems(AlignTypeBottom);
	RestoreAndRepositionAllDragFrames();
	// set the modified flag
	SetModified(TRUE);
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::FormAlignHCenter()
{
IMP_BEGIN
METHOD_BEGIN
	// check there are items selected
	if(GetSelectedItemCount()==0)
	{
		throw CHResult(E_FAIL);
	}
	// update the undo storage
	UpdateUndoStorage();
	// align selected items to the form horizontal center
	HideAllDragFrames();
	AlignSelectedItemsToForm(FormAlignTypeHCenter);
	RestoreAndRepositionAllDragFrames();
	// set the modified flag
	SetModified(TRUE);
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::FormAlignVCenter()
{
IMP_BEGIN
METHOD_BEGIN
	// check there are items selected
	if(GetSelectedItemCount()==0)
	{
		throw CHResult(E_FAIL);
	}
	// update the undo storage
	UpdateUndoStorage();
	// align selected items to the form vertical center
	HideAllDragFrames();
	AlignSelectedItemsToForm(FormAlignTypeVCenter);
	RestoreAndRepositionAllDragFrames();
	// set the modified flag
	SetModified(TRUE);
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::FormAlignBoth()
{
IMP_BEGIN
METHOD_BEGIN
	// check there are items selected
	if(GetSelectedItemCount()==0)
	{
		throw CHResult(E_FAIL);
	}
	// update the undo storage
	UpdateUndoStorage();
	// align selected items to the form horizontal and vertical center
	HideAllDragFrames();
	AlignSelectedItemsToForm(FormAlignTypeBoth);
	RestoreAndRepositionAllDragFrames();
	// set the modified flag
	SetModified(TRUE);
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::SizeWidthToLargest()
{
IMP_BEGIN
METHOD_BEGIN
	// check there are enough items selected
	if(GetSelectedItemCount() < 2)
	{
		throw CHResult(E_FAIL);
	}
	// update the undo storage
	UpdateUndoStorage();
	// size selected items to the largest width
	HideAllDragFrames();
	SizeSelectedItemsToLargest(SizeTypeWidth);
	RestoreAndRepositionAllDragFrames();
	// set the modified flag
	SetModified(TRUE);
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::SizeHeightToLargest()
{
IMP_BEGIN
METHOD_BEGIN
	// check there are enough items selected
	if(GetSelectedItemCount() < 2)
	{
		throw CHResult(E_FAIL);
	}
	// update the undo storage
	UpdateUndoStorage();
	// size selected items to the largest height
	HideAllDragFrames();
	SizeSelectedItemsToLargest(SizeTypeHeight);
	RestoreAndRepositionAllDragFrames();
	// set the modified flag
	SetModified(TRUE);
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::SizeToLargest()
{
IMP_BEGIN
METHOD_BEGIN
	// check there are enough items selected
	if(GetSelectedItemCount() < 2)
	{
		throw CHResult(E_FAIL);
	}
	// update the undo storage
	UpdateUndoStorage();
	// size selected items to the largest overall
	HideAllDragFrames();
	SizeSelectedItemsToLargest(SizeTypeBoth);
	RestoreAndRepositionAllDragFrames();
	// set the modified flag
	SetModified(TRUE);
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::SizeWidthToSmallest()
{
IMP_BEGIN
METHOD_BEGIN
	// check there are enough items selected
	if(GetSelectedItemCount() < 2)
	{
		throw CHResult(E_FAIL);
	}
	// update the undo storage
	UpdateUndoStorage();
	// size selected items to the smallest width
	HideAllDragFrames();
	SizeSelectedItemsToSmallest(SizeTypeWidth);
	RestoreAndRepositionAllDragFrames();
	// set the modified flag
	SetModified(TRUE);
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::SizeHeightToSmallest()
{
IMP_BEGIN
METHOD_BEGIN
	// check there are enough items selected
	if(GetSelectedItemCount() < 2)
	{
		throw CHResult(E_FAIL);
	}
	// update the undo storage
	UpdateUndoStorage();
	// size selected items to the smallest height
	HideAllDragFrames();
	SizeSelectedItemsToSmallest(SizeTypeHeight);
	RestoreAndRepositionAllDragFrames();
	// set the modified flag
	SetModified(TRUE);
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::SizeToSmallest()
{
IMP_BEGIN
METHOD_BEGIN
	// check there are enough items selected
	if(GetSelectedItemCount() < 2)
	{
		throw CHResult(E_FAIL);
	}
	// update the undo storage
	UpdateUndoStorage();
	// size selected items to the smallest overall
	HideAllDragFrames();
	SizeSelectedItemsToSmallest(SizeTypeBoth);
	RestoreAndRepositionAllDragFrames();
	// set the modified flag
	SetModified(TRUE);
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::SpaceHorizontally()
{
IMP_BEGIN
METHOD_BEGIN
	// check there are enough items selected
	if(GetSelectedItemCount() < 3)
	{
		throw CHResult(E_FAIL);
	}
	// update the undo storage
	UpdateUndoStorage();
	// space selected items horizontally
	HideAllDragFrames();
	SpaceSelectedItemsHorizontally();
	RestoreAndRepositionAllDragFrames();
	// set the modified flag
	SetModified(TRUE);
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::SpaceVertically()
{
IMP_BEGIN
METHOD_BEGIN
	// check there are enough items selected
	if(GetSelectedItemCount() < 3)
	{
		throw CHResult(E_FAIL);
	}
	// update the undo storage
	UpdateUndoStorage();
	// space selected items vertically
	HideAllDragFrames();
	SpaceSelectedItemsVertically();
	RestoreAndRepositionAllDragFrames();
	// set the modified flag
	SetModified(TRUE);
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::Properties()
{
IMP_BEGIN
METHOD_BEGIN
	// check there is a valid selection
	if(IsFormSelected()==FALSE and GetSelectedItemCount()!=1)
	{
		throw CHResult(E_FAIL);
	}
	// update the undo storage
	UpdateUndoStorage();
	// if the form is selected
	if(IsFormSelected())
	{
		// show property pages for the form
		ShowFormProperties();
	}
	// if there is a single item selected
	else if(GetSelectedItemCount()==1)
	{
		// show property pages for the selected item
		ShowSelectedItemProperties();
	}
	// set the modified flag
	SetModified(TRUE);
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::Expose(BSTR Name, IDispatch *pDispatch)
{
IMP_BEGIN
METHOD_BEGIN
	// check parameters
	std::wstring ObjectName=BSTR2W(Name);
	if(IsNameValid(ObjectName)==FALSE)
	{
		// the name should be valid
		throw CHResult(E_INVALIDARG);
	}
	if(IsNameUnique(ObjectName)==FALSE)
	{
		// the name should be unique
		throw CHResult(E_INVALIDARG);
	}
	if(pDispatch==NULL)
	{
		throw CHResult(E_POINTER);
	}
	// expose the object
	AddExposedObject(ObjectName,pDispatch);
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::DDUnlock(
	long Data1_High, long Data1_Low, long Data2_High, long Data2_Low)
{
IMP_BEGIN
METHOD_BEGIN
	// check if the editor can be unlocked
	if(CAN_UNLOCK==FALSE)
	{
		throw CHResult(E_FAIL);
	}
	// create data1
	unsigned long Data1[2]={ Data1_High,Data1_Low };
	// create data2
	unsigned long Data2[2]={ Data2_High,Data2_Low };
	// decipher data2
	unsigned long Data3[2]={ 0,0 };
	unsigned long UnlockKey[4]={ UNLOCK_KEY };
	decipher(Data2,Data3,UnlockKey);
	// data1 should match data3
	if(memcmp(Data1,Data3,sizeof(Data1)))
	{
		throw CHResult(E_FAIL);
	}
	// unlock
	m_LockedModeActive=FALSE;
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::get_Script(IScript2 **ppScript2)
{
IMP_BEGIN
PROPGET_BEGIN
	// check parameters
	if(ppScript2==NULL)
	{
		throw CHResult(E_POINTER);
	}
	// get the script
	CComQIPtr<IScript2> spScript2(m_spScript);
	if(spScript2==NULL)
	{
		throw CHResult(E_FAIL);
	}
	*ppScript2=spScript2.Detach();
PROPGET_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::get_BorderVisible(VARIANT_BOOL *pVal)
{
IMP_BEGIN
PROPGET_BEGIN
	// check parameters
	if(pVal==NULL)
	{
		throw CHResult(E_POINTER);
	}
	// get the border visible flag
	*pVal=m_BorderVisible;
PROPGET_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::put_BorderVisible(VARIANT_BOOL newVal)
{
IMP_BEGIN
PROPPUT_BEGIN
	if(IsWindow())	// refresh
	{
		if(newVal)
		{
			// show the border
			(void)ModifyStyleEx(0,WS_EX_CLIENTEDGE);
		}
		else
		{
			// hide the border
			(void)ModifyStyleEx(WS_EX_CLIENTEDGE,0);
		}
		(void)SetWindowPos(NULL,0,0,0,0,
			SWP_NOZORDER|SWP_NOMOVE|SWP_NOSIZE|SWP_FRAMECHANGED);
	}
	// set the border visible flag
	m_BorderVisible=newVal;
	PROPERTY_CHANGED(DISPID_BORDERVISIBLE);
PROPPUT_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::get_BackColor(OLE_COLOR *pVal)
{
IMP_BEGIN
PROPGET_BEGIN
	// check parameters
	if(pVal==NULL)
	{
		throw CHResult(E_POINTER);
	}
	// get the background color
	*pVal=m_BackColor;
PROPGET_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::put_BackColor(OLE_COLOR newVal)
{
IMP_BEGIN
PROPPUT_BEGIN
	if(IsWindow())	// refresh
	{
		(void)Invalidate();
	}
	// set the background color
	m_BackColor=newVal;
	PROPERTY_CHANGED(DISPID_BACKCOLOR);
PROPPUT_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::get_FormWidth(long *pVal)
{
IMP_BEGIN
PROPGET_BEGIN
	// check parameters
	if(pVal==NULL)
	{
		throw CHResult(E_POINTER);
	}
	// get the form width
	long Width=0,Height=0;
	m_Form.GetDimensions(Width,Height);
	*pVal=Width;
PROPGET_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::put_FormWidth(long newVal)
{
IMP_BEGIN
PROPPUT_BEGIN
	// check parameters
	if(newVal < MIN_FORM_WIDTH or newVal > MAX_FORM_WIDTH)
	{
		throw CHResult(E_INVALIDARG);
	}
	// get the current form dimensions
	long Width=0,Height=0;
	m_Form.GetDimensions(Width,Height);
	if(IsWindow())
	{
		// check the width is not too small
		if(newVal < GetBoundingRectForAllItems().right)
		{
			throw CHResult(E_INVALIDARG);
		}
		// update the undo storage
		UpdateUndoStorage();
		// set the form width
		UNSELECT_EVERYTHING;
		SetFormDimensions(newVal,Height);
		SELECT_FORM;
		// set the modified flag
		SetModified(TRUE);
	}
	else
	{
		// set the form width
		SetFormDimensions(newVal,Height);
	}
PROPPUT_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::get_FormHeight(long *pVal)
{
IMP_BEGIN
PROPGET_BEGIN
	// check parameters
	if(pVal==NULL)
	{
		throw CHResult(E_POINTER);
	}
	// get the form height
	long Width=0,Height=0;
	m_Form.GetDimensions(Width,Height);
	*pVal=Height;
PROPGET_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::put_FormHeight(long newVal)
{
IMP_BEGIN
PROPPUT_BEGIN
	// check parameters
	if(newVal < MIN_FORM_HEIGHT or newVal > MAX_FORM_HEIGHT)
	{
		throw CHResult(E_INVALIDARG);
	}
	// get the current form dimensions
	long Width=0,Height=0;
	m_Form.GetDimensions(Width,Height);
	if(IsWindow())
	{
		// check the height is not too small
		if(newVal < GetBoundingRectForAllItems().bottom)
		{
			throw CHResult(E_INVALIDARG);
		}
		// update the undo storage
		UpdateUndoStorage();
		// set the form height
		UNSELECT_EVERYTHING;
		SetFormDimensions(Width,newVal);
		SELECT_FORM;
		// set the modified flag
		SetModified(TRUE);
	}
	else
	{
		// set the form height
		SetFormDimensions(Width,newVal);
	}
PROPPUT_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::get_FormBackColor(OLE_COLOR *pVal)
{
IMP_BEGIN
PROPGET_BEGIN
	// check parameters
	if(pVal==NULL)
	{
		throw CHResult(E_POINTER);
	}
	// get the form background color
	OLE_COLOR BackColor=RGB(0,0,0),ForeColor=RGB(0,0,0);
	m_Form.GetColors(BackColor,ForeColor);
	*pVal=BackColor;
PROPGET_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::put_FormBackColor(OLE_COLOR newVal)
{
IMP_BEGIN
PROPPUT_BEGIN
	// get the current form colors
	OLE_COLOR BackColor=RGB(0,0,0),ForeColor=RGB(0,0,0);
	m_Form.GetColors(BackColor,ForeColor);
	if(IsWindow())
	{
		// update the undo storage
		UpdateUndoStorage();
		// set the form background color
		SetFormColors(newVal,ForeColor);
		// set the modified flag
		SetModified(TRUE);
	}
	else
	{
		// set the form background color
		SetFormColors(newVal,ForeColor);
	}
PROPPUT_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::get_FormForeColor(OLE_COLOR *pVal)
{
IMP_BEGIN
PROPGET_BEGIN
	// check parameters
	if(pVal==NULL)
	{
		throw CHResult(E_POINTER);
	}
	// get the form foreground color
	OLE_COLOR BackColor=RGB(0,0,0),ForeColor=RGB(0,0,0);
	m_Form.GetColors(BackColor,ForeColor);
	*pVal=ForeColor;
PROPGET_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::put_FormForeColor(OLE_COLOR newVal)
{
IMP_BEGIN
PROPPUT_BEGIN
	// get the current form colors
	OLE_COLOR BackColor=RGB(0,0,0),ForeColor=RGB(0,0,0);
	m_Form.GetColors(BackColor,ForeColor);
	if(IsWindow())
	{
		// update the undo storage
		UpdateUndoStorage();
		// set the form foreground color
		SetFormColors(BackColor,newVal);
		// set the modified flag
		SetModified(TRUE);
	}
	else
	{
		// set the form foreground color
		SetFormColors(BackColor,newVal);
	}
PROPPUT_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::get_GridWidth(long *pVal)
{
IMP_BEGIN
PROPGET_BEGIN
	// check parameters
	if(pVal==NULL)
	{
		throw CHResult(E_POINTER);
	}
	// get the grid width
	long Width=0,Height=0;
	m_Form.GetGrid(Width,Height);
	*pVal=Width;
PROPGET_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::put_GridWidth(long newVal)
{
IMP_BEGIN
PROPPUT_BEGIN
	// check parameters
	if(newVal < MIN_GRID_WIDTH)
	{
		throw CHResult(E_INVALIDARG);
	}
	// set the grid width
	long Width=0,Height=0;
	m_Form.GetGrid(Width,Height);
	m_Form.SetGrid(newVal,Height);
	PROPERTY_CHANGED(DISPID_GRIDWIDTH);
PROPPUT_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::get_GridHeight(long *pVal)
{
IMP_BEGIN
PROPGET_BEGIN
	// check parameters
	if(pVal==NULL)
	{
		throw CHResult(E_POINTER);
	}
	// get the grid height
	long Width=0,Height=0;
	m_Form.GetGrid(Width,Height);
	*pVal=Height;
PROPGET_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::put_GridHeight(long newVal)
{
IMP_BEGIN
PROPPUT_BEGIN
	// check parameters
	if(newVal < MIN_GRID_HEIGHT)
	{
		throw CHResult(E_INVALIDARG);
	}
	// set the grid height
	long Width=0,Height=0;
	m_Form.GetGrid(Width,Height);
	m_Form.SetGrid(Width,newVal);
	PROPERTY_CHANGED(DISPID_GRIDHEIGHT);
PROPPUT_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::get_GridVisible(VARIANT_BOOL *pVal)
{
IMP_BEGIN
PROPGET_BEGIN
	// check parameters
	if(pVal==NULL)
	{
		throw CHResult(E_POINTER);
	}
	// get the grid visibility
	*pVal=B2VB(m_Form.IsGridVisible());
PROPGET_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::put_GridVisible(VARIANT_BOOL newVal)
{
IMP_BEGIN
PROPPUT_BEGIN
	// set the grid visibility
	m_Form.ShowGrid(VB2B(newVal));
	PROPERTY_CHANGED(DISPID_GRIDVISIBLE);
PROPPUT_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::get_DragFrameBackColor(OLE_COLOR *pVal)
{
IMP_BEGIN
PROPGET_BEGIN
	// check parameters
	if(pVal==NULL)
	{
		throw CHResult(E_POINTER);
	}
	// get the drag frame background color
	OLE_COLOR BackColor=RGB(0,0,0),ForeColor=RGB(0,0,0);
	m_FormDragFrame.GetColors(BackColor,ForeColor);
	*pVal=BackColor;
PROPGET_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::put_DragFrameBackColor(OLE_COLOR newVal)
{
IMP_BEGIN
PROPPUT_BEGIN
	// set the drag frame background color
	OLE_COLOR BackColor=RGB(0,0,0),ForeColor=RGB(0,0,0);
	m_FormDragFrame.GetColors(BackColor,ForeColor);
	m_FormDragFrame.SetColors(newVal,ForeColor);
	// update all item drag frames
	for(CItemInfoPtrList::const_iterator iter=
		m_ItemInfoPtrList.begin(); iter!=m_ItemInfoPtrList.end(); iter++)
	{
		(*iter)->m_DragFrame.SetColors(newVal,ForeColor);
	}
	PROPERTY_CHANGED(DISPID_DRAGFRAMEBACKCOLOR);
PROPPUT_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::get_DragFrameForeColor(OLE_COLOR *pVal)
{
IMP_BEGIN
PROPGET_BEGIN
	// check parameters
	if(pVal==NULL)
	{
		throw CHResult(E_POINTER);
	}
	// get the drag frame foreground color
	OLE_COLOR BackColor=RGB(0,0,0),ForeColor=RGB(0,0,0);
	m_FormDragFrame.GetColors(BackColor,ForeColor);
	*pVal=ForeColor;
PROPGET_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::put_DragFrameForeColor(OLE_COLOR newVal)
{
IMP_BEGIN
PROPPUT_BEGIN
	// set the drag frame foreground color
	OLE_COLOR BackColor=RGB(0,0,0),ForeColor=RGB(0,0,0);
	m_FormDragFrame.GetColors(BackColor,ForeColor);
	m_FormDragFrame.SetColors(BackColor,newVal);
	// update all item drag frames
	for(CItemInfoPtrList::const_iterator iter=
		m_ItemInfoPtrList.begin(); iter!=m_ItemInfoPtrList.end(); iter++)
	{
		(*iter)->m_DragFrame.SetColors(BackColor,newVal);
	}
	PROPERTY_CHANGED(DISPID_DRAGFRAMEFORECOLOR);
PROPPUT_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::get_TabNumberBackColor(OLE_COLOR *pVal)
{
IMP_BEGIN
PROPGET_BEGIN
	// check parameters
	if(pVal==NULL)
	{
		throw CHResult(E_POINTER);
	}
	// get the tab number background color
	OLE_COLOR BackColor=RGB(0,0,0),ForeColor=RGB(0,0,0);
	m_FormTabNumber.GetColors(BackColor,ForeColor);
	*pVal=BackColor;
PROPGET_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::put_TabNumberBackColor(OLE_COLOR newVal)
{
IMP_BEGIN
PROPPUT_BEGIN
	// set the tab number background color
	OLE_COLOR BackColor=RGB(0,0,0),ForeColor=RGB(0,0,0);
	m_FormTabNumber.GetColors(BackColor,ForeColor);
	m_FormTabNumber.SetColors(newVal,ForeColor);
	// update all item tab numbers
	for(CItemInfoPtrList::const_iterator iter=
		m_ItemInfoPtrList.begin(); iter!=m_ItemInfoPtrList.end(); iter++)
	{
		(*iter)->m_TabNumber.SetColors(newVal,ForeColor);
	}
	PROPERTY_CHANGED(DISPID_TABNUMBERBACKCOLOR);
PROPPUT_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::get_TabNumberForeColor(OLE_COLOR *pVal)
{
IMP_BEGIN
PROPGET_BEGIN
	// check parameters
	if(pVal==NULL)
	{
		throw CHResult(E_POINTER);
	}
	// get the tab number foreground color
	OLE_COLOR BackColor=RGB(0,0,0),ForeColor=RGB(0,0,0);
	m_FormTabNumber.GetColors(BackColor,ForeColor);
	*pVal=ForeColor;
PROPGET_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::put_TabNumberForeColor(OLE_COLOR newVal)
{
IMP_BEGIN
PROPPUT_BEGIN
	// set the tab number foreground color
	OLE_COLOR BackColor=RGB(0,0,0),ForeColor=RGB(0,0,0);
	m_FormTabNumber.GetColors(BackColor,ForeColor);
	m_FormTabNumber.SetColors(BackColor,newVal);
	// update all item tab numbers
	for(CItemInfoPtrList::const_iterator iter=
		m_ItemInfoPtrList.begin(); iter!=m_ItemInfoPtrList.end(); iter++)
	{
		(*iter)->m_DragFrame.SetColors(BackColor,newVal);
	}
	PROPERTY_CHANGED(DISPID_TABNUMBERFORECOLOR);
PROPPUT_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::SendToBack()
{
IMP_BEGIN
METHOD_BEGIN
	// check there are items selected
	if(GetSelectedItemCount()==0)
	{
		throw CHResult(E_FAIL);
	}
	// update the undo storage
	UpdateUndoStorage();
	// get a list of selected items
	CItemInfoPtrList ItemInfoPtrList=GetSelectedItems();
	// send selected items to the back
	for(CItemInfoPtrList::reverse_iterator iter=
		ItemInfoPtrList.rbegin(); iter!=ItemInfoPtrList.rend(); iter++)
	{
		CItemInfo &ItemInfo=**iter;
		// get the item position
		CRect ItemRect(0,0,0,0);
		long TabNumber=0;
		GetItemPosition(ItemInfo,ItemRect,TabNumber);
		// update the tab number
		TabNumber=1;
		// set the item position
		HideAllDragFrames();
		SetItemPosition(ItemInfo,ItemRect,TabNumber);
		RestoreAndRepositionAllDragFrames();
	}
	// set the modified flag
	SetModified(TRUE);
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::BringToFront()
{
IMP_BEGIN
METHOD_BEGIN
	// check there are items selected
	if(GetSelectedItemCount()==0)
	{
		throw CHResult(E_FAIL);
	}
	// update the undo storage
	UpdateUndoStorage();
	// get a list of selected items
	CItemInfoPtrList ItemInfoPtrList=GetSelectedItems();
	// bring selected items to the front
	for(CItemInfoPtrList::iterator iter=
		ItemInfoPtrList.begin(); iter!=ItemInfoPtrList.end(); iter++)
	{
		CItemInfo &ItemInfo=**iter;
		// get the item position
		CRect ItemRect(0,0,0,0);
		long TabNumber=0;
		GetItemPosition(ItemInfo,ItemRect,TabNumber);
		// update the tab number
		TabNumber=m_ItemInfoPtrList.size();
		// set the item position
		HideAllDragFrames();
		SetItemPosition(ItemInfo,ItemRect,TabNumber);
		RestoreAndRepositionAllDragFrames();
	}
	// set the modified flag
	SetModified(TRUE);
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::GetItemDetails(
	IDispatch *pDispatch, IFormEditorItemDetails **ppFormEditorItemDetails)
{
IMP_BEGIN
METHOD_BEGIN
	// check parameters
	if(pDispatch==NULL)
	{
		throw CHResult(E_POINTER);
	}
	if(ppFormEditorItemDetails==NULL)
	{
		throw CHResult(E_POINTER);
	}
	// check the item exists
	if(std::find_if(m_ItemInfoPtrList.begin(),m_ItemInfoPtrList.end(),
		std::bind2nd(std::ptr_fun(IsItemIDispatchEqual),pDispatch))==
			m_ItemInfoPtrList.end())
	{
		throw CHResult(E_INVALIDARG);
	}
	// create the item details
	CComObject<CFormEditorItemDetails> *pItemDetails=NULL;
	if(!SUCCEEDED(
		CComObject<CFormEditorItemDetails>::CreateInstance(&pItemDetails)))
	{
		throw CHResult(E_FAIL);
	}
	CComPtr<IUnknown> spUnknown(pItemDetails->GetUnknown());
	// set the item
	pItemDetails->m_spItem=pDispatch;
	// set the form editor
	CComQIPtr<IFormEditor2> spFormEditor2(GetUnknown());
	if(spFormEditor2==NULL)
	{
		throw CHResult(E_FAIL);
	}
	pItemDetails->m_spFormEditor2=spFormEditor2;
	// get the item details
	CComQIPtr<IFormEditorItemDetails> spFormEditorItemDetails(spUnknown);
	if(spFormEditorItemDetails==NULL)
	{
		throw CHResult(E_FAIL);
	}
	*ppFormEditorItemDetails=spFormEditorItemDetails.Detach();
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::GetItemClassId(IDispatch *pDispatch,BSTR *pClassId)
{
IMP_BEGIN
METHOD_BEGIN
	// check parameters
	if(pDispatch==NULL)
	{
		throw CHResult(E_POINTER);
	}
	if(pClassId==NULL)
	{
		throw CHResult(E_POINTER);
	}
	// check the item exists
	CItemInfoPtrList::const_iterator iter=
		std::find_if(m_ItemInfoPtrList.begin(),m_ItemInfoPtrList.end(),
			std::bind2nd(std::ptr_fun(IsItemIDispatchEqual),pDispatch));
	if(iter==m_ItemInfoPtrList.end())
	{
		throw CHResult(E_INVALIDARG);
	}
	CItemInfo &ItemInfo=**iter;
	// get the item class id
	CComPtr<IOleObject> spOleObject;
	if(!SUCCEEDED(ItemInfo.m_HostWindow.QueryControl(&spOleObject)))
	{
		throw CHResult(E_FAIL);
	}
	CLSID ClassId=CLSID_NULL;
	if(!SUCCEEDED(spOleObject->GetUserClassID(&ClassId)))
	{
		throw CHResult(E_FAIL);
	}
	// convert to text
	WCHAR ClassIdText[64]=L"";
	if(StringFromGUID2(ClassId,
		ClassIdText,NUM_ELEMENTS(ClassIdText,WCHAR))==0)
	{
		throw CHResult(E_FAIL);
	}
	// get the item class id
	*pClassId=CComBSTR(ClassIdText).Detach();
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::GetItemName(IDispatch *pDispatch,BSTR *pName)
{
IMP_BEGIN
METHOD_BEGIN
	// check parameters
	if(pDispatch==NULL)
	{
		throw CHResult(E_POINTER);
	}
	if(pName==NULL)
	{
		throw CHResult(E_POINTER);
	}
	// check the item exists
	CItemInfoPtrList::const_iterator iter=
		std::find_if(m_ItemInfoPtrList.begin(),m_ItemInfoPtrList.end(),
			std::bind2nd(std::ptr_fun(IsItemIDispatchEqual),pDispatch));
	if(iter==m_ItemInfoPtrList.end())
	{
		throw CHResult(E_INVALIDARG);
	}
	CItemInfo &ItemInfo=**iter;
	// get the item name
	*pName=CComBSTR(ItemInfo.m_Name.c_str()).Detach();
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::SetItemName(IDispatch *pDispatch,BSTR Name)
{
IMP_BEGIN
METHOD_BEGIN
	// check parameters
	if(pDispatch==NULL)
	{
		throw CHResult(E_POINTER);
	}
	// check the item exists
	CItemInfoPtrList::const_iterator iter=
		std::find_if(m_ItemInfoPtrList.begin(),m_ItemInfoPtrList.end(),
			std::bind2nd(std::ptr_fun(IsItemIDispatchEqual),pDispatch));
	if(iter==m_ItemInfoPtrList.end())
	{
		throw CHResult(E_INVALIDARG);
	}
	CItemInfo &ItemInfo=**iter;
	// the name should not contain any embedded NULL characters
	std::wstring ItemName=BSTR2W(Name);
	if(ItemName.length()!=SysStringLen(Name))
	{
		throw CHResult(E_INVALIDARG);
	}
	// the name should be valid
	if(IsNameValid(ItemName)==FALSE)
	{
		throw CHResult(E_INVALIDARG);
	}
	// the name should be unique
	if(IsNameUnique(ItemName,ItemInfo.m_HostWindow)==FALSE)
	{
		throw CHResult(E_INVALIDARG);
	}
	// update the undo storage
	UpdateUndoStorage();
	// set the item name
	ItemInfo.m_Name=ItemName;
	// set the modified flag
	SetModified(TRUE);
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::GetItemTag(IDispatch *pDispatch,BSTR *pTag)
{
IMP_BEGIN
METHOD_BEGIN
	// check parameters
	if(pDispatch==NULL)
	{
		throw CHResult(E_POINTER);
	}
	if(pTag==NULL)
	{
		throw CHResult(E_POINTER);
	}
	// check the item exists
	CItemInfoPtrList::const_iterator iter=
		std::find_if(m_ItemInfoPtrList.begin(),m_ItemInfoPtrList.end(),
			std::bind2nd(std::ptr_fun(IsItemIDispatchEqual),pDispatch));
	if(iter==m_ItemInfoPtrList.end())
	{
		throw CHResult(E_INVALIDARG);
	}
	CItemInfo &ItemInfo=**iter;
	// get the item tag
	*pTag=CComBSTR(ItemInfo.m_Tag.c_str()).Detach();
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::SetItemTag(IDispatch *pDispatch,BSTR Tag)
{
IMP_BEGIN
METHOD_BEGIN
	// check parameters
	if(pDispatch==NULL)
	{
		throw CHResult(E_POINTER);
	}
	// check the item exists
	CItemInfoPtrList::const_iterator iter=
		std::find_if(m_ItemInfoPtrList.begin(),m_ItemInfoPtrList.end(),
			std::bind2nd(std::ptr_fun(IsItemIDispatchEqual),pDispatch));
	if(iter==m_ItemInfoPtrList.end())
	{
		throw CHResult(E_INVALIDARG);
	}
	CItemInfo &ItemInfo=**iter;
	// the tag should not contain any embedded NULL characters
	std::wstring ItemTag=BSTR2W(Tag);
	if(ItemTag.length()!=SysStringLen(Tag))
	{
		throw CHResult(E_INVALIDARG);
	}
	// update the undo storage
	UpdateUndoStorage();
	// set the item tag
	ItemInfo.m_Tag=ItemTag;
	// set the modified flag
	SetModified(TRUE);
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::GetItemPosition(IDispatch *pDispatch,
	long *pLeft,long *pTop,long *pWidth,long *pHeight,long *pTabNumber)
{
IMP_BEGIN
METHOD_BEGIN
	// check parameters
	if(pDispatch==NULL)
	{
		throw CHResult(E_POINTER);
	}
	if(pLeft==NULL)
	{
		throw CHResult(E_POINTER);
	}
	if(pTop==NULL)
	{
		throw CHResult(E_POINTER);
	}
	if(pWidth==NULL)
	{
		throw CHResult(E_POINTER);
	}
	if(pHeight==NULL)
	{
		throw CHResult(E_POINTER);
	}
	if(pTabNumber==NULL)
	{
		throw CHResult(E_POINTER);
	}
	// check the item exists
	CItemInfoPtrList::const_iterator iter=
		std::find_if(m_ItemInfoPtrList.begin(),m_ItemInfoPtrList.end(),
			std::bind2nd(std::ptr_fun(IsItemIDispatchEqual),pDispatch));
	if(iter==m_ItemInfoPtrList.end())
	{
		throw CHResult(E_INVALIDARG);
	}
	CItemInfo &ItemInfo=**iter;
	// get the item position
	CRect ItemRect(0,0,0,0);
	GetItemPosition(ItemInfo,ItemRect,*pTabNumber);
	*pLeft=ItemRect.left;
	*pTop=ItemRect.top;
	*pWidth=ItemRect.Width();
	*pHeight=ItemRect.Height();
	// the tab number will be set on return from GetItemPosition()
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::SetItemPosition(IDispatch *pDispatch,
	long Left,long Top,long Width,long Height,long TabNumber)
{
IMP_BEGIN
METHOD_BEGIN
	// check parameters
	if(pDispatch==NULL)
	{
		throw CHResult(E_POINTER);
	}
	if(Width < MIN_ITEM_WIDTH)
	{
		throw CHResult(E_INVALIDARG);
	}
	if(Height < MIN_ITEM_HEIGHT)
	{
		throw CHResult(E_INVALIDARG);
	}
	// get the form rect
	CRect FormRect(0,0,0,0);
	(void)m_Form.GetClientRect(FormRect);
	// get the item rect
	CRect ItemRect(Left,Top,Left+Width,Top+Height);
	// check the item fits within the form
	CRect IntersectRect(0,0,0,0);
	(void)IntersectRect.IntersectRect(FormRect,ItemRect);
	if(IntersectRect!=ItemRect)
	{
		throw CHResult(E_INVALIDARG);
	}
	if(TabNumber < 1 or TabNumber > (long)m_ItemInfoPtrList.size())
	{
		throw CHResult(E_INVALIDARG);
	}
	// check the item exists
	CItemInfoPtrList::const_iterator iter=
		std::find_if(m_ItemInfoPtrList.begin(),m_ItemInfoPtrList.end(),
			std::bind2nd(std::ptr_fun(IsItemIDispatchEqual),pDispatch));
	if(iter==m_ItemInfoPtrList.end())
	{
		throw CHResult(E_INVALIDARG);
	}
	CItemInfo &ItemInfo=**iter;
	// update the undo storage
	UpdateUndoStorage();
	// set the item position
	HideAllDragFrames();
	SetItemPosition(ItemInfo,ItemRect,TabNumber);
	RestoreAndRepositionAllDragFrames();
	// set the modified flag
	SetModified(TRUE);
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::IsItemSelected(
	IDispatch *pDispatch,VARIANT_BOOL *pIsSelected)
{
IMP_BEGIN
METHOD_BEGIN
	// check parameters
	if(pDispatch==NULL)
	{
		throw CHResult(E_POINTER);
	}
	if(pIsSelected==NULL)
	{
		throw CHResult(E_POINTER);
	}
	// check the item exists
	CItemInfoPtrList::const_iterator iter=
		std::find_if(m_ItemInfoPtrList.begin(),m_ItemInfoPtrList.end(),
			std::bind2nd(std::ptr_fun(IsItemIDispatchEqual),pDispatch));
	if(iter==m_ItemInfoPtrList.end())
	{
		throw CHResult(E_INVALIDARG);
	}
	CItemInfo &ItemInfo=**iter;
	// determine if the item is selected
	*pIsSelected=B2VB(IsItemSelected(ItemInfo));
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::SelectItem(
	IDispatch *pDispatch,VARIANT_BOOL KeepCurrentSelection)
{
IMP_BEGIN
METHOD_BEGIN
	// check parameters
	if(pDispatch==NULL)
	{
		throw CHResult(E_POINTER);
	}
	// check the item exists
	CItemInfoPtrList::const_iterator iter=
		std::find_if(m_ItemInfoPtrList.begin(),m_ItemInfoPtrList.end(),
			std::bind2nd(std::ptr_fun(IsItemIDispatchEqual),pDispatch));
	if(iter==m_ItemInfoPtrList.end())
	{
		throw CHResult(E_INVALIDARG);
	}
	CItemInfo &ItemInfo=**iter;
	// unselect the form
	UnselectForm();
	// if the current selection should not be maintained unselect all items
	if(KeepCurrentSelection==VARIANT_FALSE)
	{
		UnselectAllItems();
	}
	// select the item
	SelectItem(ItemInfo);
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::UnselectItem(IDispatch *pDispatch)
{
IMP_BEGIN
METHOD_BEGIN
	// check parameters
	if(pDispatch==NULL)
	{
		throw CHResult(E_POINTER);
	}
	// check the item exists
	CItemInfoPtrList::const_iterator iter=
		std::find_if(m_ItemInfoPtrList.begin(),m_ItemInfoPtrList.end(),
			std::bind2nd(std::ptr_fun(IsItemIDispatchEqual),pDispatch));
	if(iter==m_ItemInfoPtrList.end())
	{
		throw CHResult(E_INVALIDARG);
	}
	CItemInfo &ItemInfo=**iter;
	// unselect the item
	UnselectItem(ItemInfo);
	// if no other items are selected select the form
	if(GetSelectedItemCount()==0)
	{
		SelectForm();
	}
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::IsItemDeleted(
	IDispatch *pDispatch,VARIANT_BOOL *pIsDeleted)
{
IMP_BEGIN
METHOD_BEGIN
	// check parameters
	if(pDispatch==NULL)
	{
		throw CHResult(E_POINTER);
	}
	if(pIsDeleted==NULL)
	{
		throw CHResult(E_POINTER);
	}
	// check the item exists
	CItemInfoPtrList::const_iterator iter=
		std::find_if(m_ItemInfoPtrList.begin(),m_ItemInfoPtrList.end(),
			std::bind2nd(std::ptr_fun(IsItemIDispatchEqual),pDispatch));
	// determine if the item is deleted
	if(iter==m_ItemInfoPtrList.end())
	{
		*pIsDeleted=VARIANT_TRUE;	// deleted
	}
	else
	{
		*pIsDeleted=VARIANT_FALSE;	// not deleted
	}
METHOD_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::get_Items(
	IFormEditorItemCollection **ppFormEditorItemCollection)
{
IMP_BEGIN
PROPGET_BEGIN
	// check parameters
	if(ppFormEditorItemCollection==NULL)
	{
		throw CHResult(E_POINTER);
	}
	// create the item collection
	CComObject<CFormEditorItemCollection> *pItems=NULL;
	if(!SUCCEEDED(
		CComObject<CFormEditorItemCollection>::CreateInstance(&pItems)))
	{
		throw CHResult(E_FAIL);
	}
	CComPtr<IUnknown> spUnknown(pItems->GetUnknown());
	// add all items to the item collection
	for(CItemInfoPtrList::const_iterator iter=
		m_ItemInfoPtrList.begin(); iter!=m_ItemInfoPtrList.end(); iter++)
	{
		CItemInfo &ItemInfo=**iter;
		// get the items IDispatch interface
		CComPtr<IDispatch> spDispatch;
		if(!SUCCEEDED(ItemInfo.m_HostWindow.QueryControl(&spDispatch)))
		{
			throw CHResult(E_FAIL);
		}
		// update the item collection
		pItems->m_coll.push_back(spDispatch);
	}
	// get the item collection
	CComQIPtr<IFormEditorItemCollection> spFormEditorItemCollection(spUnknown);
	if(spFormEditorItemCollection==NULL)
	{
		throw CHResult(E_FAIL);
	}
	*ppFormEditorItemCollection=spFormEditorItemCollection.Detach();
PROPGET_END
IMP_END
	return RetVal;
}

STDMETHODIMP CFormEditor::get_SelectedItems(
	IFormEditorItemCollection **ppFormEditorItemCollection)
{
IMP_BEGIN
PROPGET_BEGIN
	// check parameters
	if(ppFormEditorItemCollection==NULL)
	{
		throw CHResult(E_POINTER);
	}
	// create the item collection
	CComObject<CFormEditorItemCollection> *pItems=NULL;
	if(!SUCCEEDED(
		CComObject<CFormEditorItemCollection>::CreateInstance(&pItems)))
	{
		throw CHResult(E_FAIL);
	}
	CComPtr<IUnknown> spUnknown(pItems->GetUnknown());
	// add all selected items to the item collection
	CItemInfoPtrList ItemInfoPtrList=GetSelectedItems();
	for(CItemInfoPtrList::const_iterator iter=
		ItemInfoPtrList.begin(); iter!=ItemInfoPtrList.end(); iter++)
	{
		CItemInfo &ItemInfo=**iter;
		// get the items IDispatch interface
		CComPtr<IDispatch> spDispatch;
		if(!SUCCEEDED(ItemInfo.m_HostWindow.QueryControl(&spDispatch)))
		{
			throw CHResult(E_FAIL);
		}
		// update the item collection
		pItems->m_coll.push_back(spDispatch);
	}
	// get the item collection
	CComQIPtr<IFormEditorItemCollection> spFormEditorItemCollection(spUnknown);
	if(spFormEditorItemCollection==NULL)
	{
		throw CHResult(E_FAIL);
	}
	*ppFormEditorItemCollection=spFormEditorItemCollection.Detach();
PROPGET_END
IMP_END
	return RetVal;
}

} // namespace AxFormEditor

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 The Code Project Open License (CPOL)


Written By
Web Developer
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions