Click here to Skip to main content
15,881,173 members
Articles / Desktop Programming / MFC

CodePlotter 1.6 - Add and edit diagrams in your code with this 'Visio-like' tool

Rate me:
Please Sign up or sign in to vote.
4.91/5 (160 votes)
27 Sep 200310 min read 363.3K   3.8K   171  
A Visual Studio addin for creating and editing ASCII diagrams in source files
// TextDiagram.cpp: implementation of the CTextDiagram class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "TextDiagram.h"

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

// rects
#define CHAR_TOP '-'
#define CHAR_RIGHT '|'
#define CHAR_BOTTOM '-'
#define CHAR_LEFT '|'

// connections
#define CHAR_HORZ '-'
#define CHAR_VERT '|'
#define CHAR_END '*'
#define CHAR_CROSSOVER '+'
#define CHAR_START '#' 

// misc
#define ENDL '\n'
#define SPACE ' '

const int MAXITERATIONS = 100;

//////////////////////////////////////////////////////////////////////

CTDRect::CTDRect(const CRect& rect, LPCTSTR szText) : CRect(rect), m_sText(szText)
{
}

CTDRect::CTDRect()
{ 
	SetRectEmpty(); 
}

CTDRect::~CTDRect()
{
}

const CTDRect& CTDRect::operator=(const CTDRect& rect)
{
	CopyRect(rect);
	SetText(rect.GetText());

	return *this;
}

BOOL CTDRect::operator==(const CTDRect& rect)
{
	return EqualRect(rect);
}

BOOL CTDRect::operator!=(const CTDRect& rect)
{
	return !EqualRect(rect);
}

int CTDRect::GetSide(CPoint point)
{
	if (point.x == left)
		return RECT_LEFT;

	else if (point.x == right)
		return RECT_RIGHT;

	else if (point.y == top)
		return RECT_TOP;

	else if (point.y == bottom)
		return RECT_BOTTOM;

	else
		return NONE;
}

//////////////////////////////////////////////////////////////////////

CTDConnection::CTDConnection(int nRectFrom, int nRectTo, int nSideFrom) :
	m_nRectFrom(nRectFrom), m_nRectTo(nRectTo), m_nSideFrom(nSideFrom), m_ptFrom(0, 0)
{
}

CTDConnection::CTDConnection() : m_nRectFrom(0), m_nRectTo(0), m_ptFrom(0, 0)
{
}

CTDConnection::~CTDConnection()
{
}

const CTDConnection& CTDConnection::operator=(const CTDConnection& conn)
{
	m_nRectFrom = conn.m_nRectFrom;
	m_nRectTo = conn.m_nRectTo;
	m_nSideFrom = conn.m_nSideFrom;
	m_ptFrom = conn.m_ptFrom;
	m_aSegments.Copy(conn.m_aSegments);

	return *this;
}

BOOL CTDConnection::operator==(const CTDConnection& conn)
{
	// note: we don't rect indices because these can change
	// instead we check the actual character positions
	if (m_nSideFrom != conn.m_nSideFrom)
		return FALSE;

	if (m_ptFrom != conn.m_ptFrom)
		return FALSE;

	if (NumSegments() != conn.NumSegments())
		return FALSE;

	for (int nSegment = 0; nSegment < NumSegments(); nSegment++)
	{
		if (m_aSegments[nSegment] != conn.m_aSegments[nSegment])
		return FALSE;
	}

	return TRUE;
}

BOOL CTDConnection::operator!=(const CTDConnection& conn)
{
	return (!(*this == conn));
}

CPoint CTDConnection::GetEndPos(int nNextSegment) const
{
	CPoint ptEnd = GetSegmentPos(NumSegments() - 1);
	int nDir = GetEndDirection(nNextSegment);

	// nNextSegment
	switch (nDir)
	{
	case CONN_UP:
	case CONN_DOWN:
		ptEnd.y += nNextSegment;
		break;
		
	case CONN_RIGHT:
	case CONN_LEFT:
		ptEnd.x += nNextSegment;
		break;
	}

	return ptEnd;
}

CPoint CTDConnection::GetSegmentPos(int nSegment) const
{
	if (nSegment < 0)
		return GetStartPos();

	int nDir = GetStartDirection();
	CPoint ptEnd = GetStartPos();

	int nLastSegment = min(nSegment, NumSegments() - 1);

	for (nSegment = 0; nSegment <= nLastSegment; nSegment++)
	{
		switch (nDir)
		{
		case CONN_UP:
		case CONN_DOWN:
			ptEnd.y += m_aSegments[nSegment];
			nDir = CONN_LEFT; // same effect as CONN_RIGHT
			break;

		case CONN_RIGHT:
		case CONN_LEFT:
			ptEnd.x += m_aSegments[nSegment];
			nDir = CONN_UP; // same effect as CONN_DOWN
			break;
		}
	}

	return ptEnd;
}

int CTDConnection::GetLength() const
{
	int nLength = 0;
	int nSegment = NumSegments();

	while (nSegment--)
		nLength += abs(Segment(nSegment));

	return nLength;
}

int CTDConnection::GetEndDirection(int nNextSegment) const
{
	int nDir = GetStartDirection();

	for (int nSegment = 1; nSegment < NumSegments(); nSegment++)
	{
		switch (nDir)
		{
		case CONN_UP:
		case CONN_DOWN:
			nDir = m_aSegments[nSegment] < 0 ? CONN_LEFT : CONN_RIGHT;
			break;

		case CONN_RIGHT:
		case CONN_LEFT:
			nDir = m_aSegments[nSegment] < 0 ? CONN_UP : CONN_DOWN;
			break;
		}
	}

	// nNextSegment
	if (nNextSegment && NumSegments())
	{
		switch (nDir)
		{
		case CONN_UP:
		case CONN_DOWN:
			nDir = nNextSegment < 0 ? CONN_LEFT : CONN_RIGHT;
			break;

		case CONN_RIGHT:
		case CONN_LEFT:
			nDir = nNextSegment < 0 ? CONN_UP : CONN_DOWN;
			break;
		}
	}

	return nDir;
}

CRect CTDConnection::GetBoundingRect() const
{
	CRect rBounds(m_ptFrom, CSize(0, 0));

	for (int nSegment = 1; nSegment < NumSegments(); nSegment++)
	{
		CPoint ptEnd = GetSegmentPos(nSegment);

		if (rBounds.left > ptEnd.x)
			rBounds.left = ptEnd.x;

		if (rBounds.right < ptEnd.x)
			rBounds.right = ptEnd.x;

		if (rBounds.top > ptEnd.y)
			rBounds.top = ptEnd.y;

		if (rBounds.bottom < ptEnd.y)
			rBounds.bottom = ptEnd.y;
	}

	return rBounds;
}

int CTDConnection::GetStartDirection() const
{
	switch (m_nSideFrom)
	{
	case RECT_TOP:
		return CONN_UP;

	case RECT_RIGHT:
		return CONN_RIGHT;

	case RECT_BOTTOM:
		return CONN_DOWN;

	case RECT_LEFT:
		return CONN_LEFT;
	}

	return -1;
}

void CTDConnection::Increment(int& nSegment)
{
	ASSERT (nSegment);
	nSegment += (nSegment < 0) ? -1 : 1;
}

void CTDConnection::Decrement(int& nSegment)
{
	ASSERT (nSegment);
	nSegment -= (nSegment < 0) ? -1 : 1;
}

void CTDConnection::AddSegment(int nSegment)
{
	m_aSegments.Add(nSegment);
}

void CTDConnection::IncrementLastSegment()
{
	ASSERT (NumSegments());

	if (NumSegments())
		Increment(m_aSegments[NumSegments() - 1]);
}

BOOL CTDConnection::PtInConn(CPoint point, BOOL bHorz) const
{
	// iterate all the segments testing each one for an overlap in the requested orientation
	int nDir = GetStartDirection();

	CPoint ptStart, ptEnd = GetStartPos();

	for (int nSegment = 0; nSegment < NumSegments(); nSegment++)
	{
		ptStart = ptEnd;

		switch (nDir)
		{
		case CONN_UP:
		case CONN_DOWN:
			ptEnd.y += m_aSegments[nSegment];

			// test
			if (!bHorz && point.x == ptEnd.x)
			{
				if ((point.y >= ptStart.y && point.y <= ptEnd.y) ||
					(point.y >= ptEnd.y && point.y <= ptStart.y))
				{
					return TRUE;
				}
			}
			nDir = CONN_LEFT; // same effect as CONN_RIGHT
			break;

		case CONN_RIGHT:
		case CONN_LEFT:
			ptEnd.x += m_aSegments[nSegment];

			// test
			if (bHorz && point.y == ptEnd.y)
			{
				if ((point.x >= ptStart.x && point.x <= ptEnd.x) ||
					(point.x >= ptEnd.x && point.x <= ptStart.x))
				{
					return TRUE;
				}
			}
			nDir = CONN_UP; // same effect as CONN_DOWN
			break;
		}
	}

	return FALSE;
}

//////////////////////////////////////////////////////////////////////

CTextDiagram::CTextDiagram(LPCTSTR szDiagram) : m_size(0, 0)
{
	SetDiagram(szDiagram);
}

CTextDiagram::~CTextDiagram()
{

}

void CTextDiagram::ResetDiagram()
{
	m_sTitle.Empty();
	m_size.cx = m_size.cy = 0;

	m_aRects.RemoveAll();
	m_aConns.RemoveAll();
}

void CTextDiagram::SetDiagram(const CTextDiagram& diagram)
{
	*this = diagram;
}

void CTextDiagram::SetDiagram(LPCTSTR szDiagram)
{
	ResetDiagram();

	// convert text into array of lines
	CString sDiagram(szDiagram);
	CStringArray diagram; 

	while (!sDiagram.IsEmpty())
	{
		int nFind = sDiagram.Find(ENDL);
		CString sLine;

		if (nFind != -1)
		{
			sLine = sDiagram.Left(nFind);
			sDiagram = sDiagram.Mid(nFind + 1);
		}
		else
		{
			sLine = sDiagram;
			sDiagram.Empty();
		}

		sLine.TrimRight();
		ExpandTabs(sLine);

		diagram.Add(sLine);
	}

	// then to diagram
	ProcessDiagram(diagram);
}

BOOL CTextDiagram::GetDiagram(CString& sDiagram, BOOL bVerify) const
{
	if (bVerify && !VerifyDiagram())
		return FALSE;

	CStringArray diagram;
	BuildDiagram(diagram);

	sDiagram.Empty();

	for (int nLine = 0; nLine < diagram.GetSize(); nLine++)
	{
		sDiagram += diagram.GetAt(nLine);
		sDiagram += "\r\n";
	}

	return TRUE;
}

void CTextDiagram::BuildDiagram(CStringArray& diagram) const
{
	diagram.RemoveAll();

	if (m_size.cx && m_size.cy)
	{
		// initialize the diagram to match the current size
		for (int nY = 0; nY < m_size.cy + 1; nY++)
			diagram.Add(CString(SPACE, m_size.cx + 1));

		// draw each rect
		CTDRect rect;
		int nRect = 0;

		while (GetRect(nRect, rect))
		{
			DrawRect(diagram, rect);
			nRect++;
		}

		// draw each conn
		CTDConnection conn;
		int nConn = 0;

		while (GetConnection(nConn, conn))
		{
			DrawConn(diagram, conn);
			nConn++;
		}

		// add title at the end because this alters the y-coordinates
		AddTitle(diagram);
	}
}

void CTextDiagram::AddTitle(CStringArray& diagram) const
{
	CStringArray aTitle;
	CString sTitle(m_sTitle);

	sTitle.TrimRight(); // remove trailing carriage returns

	if (!sTitle.IsEmpty())
	{
		CString sLine(": ");
		int nChar = 0, nLineChar = 0;

		// walk the characters till we hit a new line character
		while (nChar < sTitle.GetLength())
		{
			char c = sTitle[nChar];
			
			// new line?
			if (c == '\r')
			{
				sLine.TrimRight();

				if (aTitle.GetSize() || sLine.GetLength() > 2)
					aTitle.Add(sLine);

				sLine = ": ";
				nLineChar = 0;
				nChar++; // jump '\n' too
			}
			else
			{
				sLine += c;
				nLineChar++;
			}

			nChar++;
		}

		// add whatever's left
		sLine.TrimRight();

		if (sLine.GetLength() > 2)
			aTitle.Add(sLine);
	}

	// insert a blank line above if we have a none empty title
	if (aTitle.GetSize())
		aTitle.InsertAt(0, "");

	diagram.InsertAt(0, &aTitle);
}

void CTextDiagram::DrawRect(CStringArray& diagram, const CTDRect& rect) const
{
	// right
	DrawLine(diagram, CPoint(rect.right, rect.top), CPoint(rect.right, rect.bottom));

	// left
	DrawLine(diagram, CPoint(rect.left, rect.top), CPoint(rect.left, rect.bottom));

	// top
	DrawLine(diagram, CPoint(rect.left, rect.top), CPoint(rect.right, rect.top));

	// bottom
	DrawLine(diagram, CPoint(rect.left, rect.bottom), CPoint(rect.right, rect.bottom));

	// corners
	SetChar(diagram, rect.left, rect.top, SPACE);
	SetChar(diagram, rect.right, rect.top, SPACE);
	SetChar(diagram, rect.right, rect.bottom, SPACE);
	SetChar(diagram, rect.left, rect.bottom, SPACE);

	// text
	int nLen = rect.GetTextLen(), nX = rect.left + 1, nY = rect.top + 1;
	LPCTSTR szText = rect.GetText();

	for (int nChar = 0; nChar < nLen && nY < rect.bottom; nChar++)
	{
		char c = szText[nChar];

		// new line?
		if (c == '\r')
		{
			nX = rect.left + 1;
			nY++;
			nChar++; // jump '\n'
		}
		else if (nX >= rect.right)
		{
			nX = rect.left + 1;
			nY++;
			nChar--;
		}
		else
		{
			SetChar(diagram, nX, nY, c);
			nX++;
		}
	}
}

void CTextDiagram::DrawConn(CStringArray& diagram, const CTDConnection& conn) const
{
	CPoint ptEnd, ptStart = conn.GetStartPos();

	for (int nSegment = 0; nSegment < conn.NumSegments(); nSegment++)
	{
		ptEnd = conn.GetSegmentPos(nSegment);

		DrawLine(diagram, ptStart, ptEnd, &conn);
		ptStart = ptEnd;
	}

	// replace start with '+'
	ptStart = conn.GetStartPos();
	SetChar(diagram, ptStart.x, ptStart.y, CHAR_START);

	// replace end with '*'
	ptEnd = conn.GetEndPos();
	SetChar(diagram, ptEnd.x, ptEnd.y, CHAR_END);
}

void CTextDiagram::DrawLine(CStringArray& diagram, CPoint ptFrom, CPoint ptTo, const CTDConnection* pConn) const
{
	// must be vert or horz
	ASSERT (ptFrom.x == ptTo.x || ptFrom.y == ptTo.y);

	int nXLen = abs(ptFrom.x - ptTo.x);
	int nYLen = abs(ptFrom.y - ptTo.y);

	if (nYLen) // vert
	{
		int nFrom = (ptFrom.y > ptTo.y) ? ptTo.y : ptFrom.y;
		int nTo = (ptFrom.y > ptTo.y) ? ptFrom.y : ptTo.y;

		if (pConn && nYLen > 0)
		{
			nFrom++;

			// make sure the line is at least one char long
			// with the exception of the start and end points
			if (nTo - nFrom > 0 || ptFrom == pConn->GetStartPos() || ptTo == pConn->GetEndPos())
				nTo--;
		}

		for (int nY = nFrom; nY <= nTo; nY++)
		{
			char c = CHAR_VERT;

			if (pConn && PtInConn(ptTo.x, nY, TRUE) && !pConn->PtInConn(CPoint(ptTo.x, nY), TRUE))
				c = CHAR_CROSSOVER;

			SetChar(diagram, ptTo.x, nY, c);
		}
	}
	else if (nXLen) // horz
	{
		int nFrom = (ptFrom.x > ptTo.x) ? ptTo.x : ptFrom.x;
		int nTo = (ptFrom.x > ptTo.x) ? ptFrom.x : ptTo.x;

		for (int nX = nFrom; nX <= nTo; nX++)
		{
			char c = CHAR_HORZ;

			if (pConn && PtInConn(nX, ptTo.y, FALSE) && !pConn->PtInConn(CPoint(nX, ptTo.y), FALSE))
				c = CHAR_CROSSOVER;

			SetChar(diagram, nX, ptTo.y, c);
		}
	}
}

void CTextDiagram::ExpandTabs(CString& sLine, int nTabSize) const
{
	CString sExpanded;

	for (int nPos = 0; nPos < sLine.GetLength(); nPos++)
	{
		char c = sLine[nPos];

		if (c == '\t')
		{
			int nTabs = nTabSize - (sExpanded.GetLength() % nTabSize);

			while (nTabs--)
				sExpanded += ' ';
		}
		else
			sExpanded += c;
	}

	sLine = sExpanded;
}

char CTextDiagram::GetChar(const CStringArray& diagram, int nX, int nY) const
{
	if (nY < 0 || nY >= diagram.GetSize())
		return 0;

	CString& sLine = diagram[nY];

	if (nX < 0 || nX >= sLine.GetLength())
		return 0;

	return sLine[nX];
}

BOOL CTextDiagram::SetChar(CStringArray& diagram, int nX, int nY, char c) const
{
	if (!c)
		return FALSE;

	if (nY < 0 || nY >= diagram.GetSize())
	{
		ASSERT(0);
		return FALSE;
	}

	CString& sLine = diagram[nY];

	if (nX < 0 || nX >= sLine.GetLength())
	{
		ASSERT(0);
		return FALSE;
	}

	sLine.SetAt(nX, c);
	return TRUE;
}

int CTextDiagram::LineLength(const CStringArray& diagram, int nY) const
{
	return (nY < 0 || nY >= diagram.GetSize()) ? 0 : diagram[nY].GetLength();
}

BOOL CTextDiagram::TestChar(const CStringArray& diagram, int nX, int nY, char cTest) const
{
	return (cTest == GetChar(diagram, nX, nY));
}

void CTextDiagram::ProcessDiagram(CStringArray& diagram)
{
	if (!diagram.GetSize())
		return;

	// 1. title
	// find the first non-empty line
	int nLine = 0;
	BOOL bContinue = TRUE;
	
	while (bContinue)
	{
		CString sLine = diagram[nLine];
		sLine.TrimLeft();
		
		if (bContinue = sLine.IsEmpty())
			nLine++;
	}
	
	// title lines must begin with a ':'
	if (TestChar(diagram, 0, nLine, ':'))
	{
		while (TestChar(diagram, 0, nLine, ':'))
		{
			CString sLine = diagram[nLine].Mid(1);
			sLine.TrimLeft();
			sLine.TrimRight();
			
			m_sTitle += sLine;
			m_sTitle += "\r\n";
			
			nLine++;
		}
		
		// remove the title lines
		while (nLine--)
			diagram.RemoveAt(0);
	}

	if (!diagram.GetSize())
		return;

	// 2. check boundary conditions.
	// specifically rects either of whose left or top is zero
	BOOL bFoundHorz = FALSE, bFoundVert = FALSE;

	for (int nX = 0; nX < LineLength(diagram, 0) && !bFoundHorz; nX++)
		bFoundHorz = (RECT_TOP == RectStartChar(diagram, nX, 0));

	for (int nY = 0; nY < diagram.GetSize() && !bFoundVert; nY++)
		bFoundVert = (RECT_TOP == RectStartChar(diagram, 1, nY));

	// if we find a rect at y==0 then simply add an empty line at the start
	if (bFoundHorz)
		diagram.InsertAt(0, "");

	// if we find a rect at x==0 then prepeand a space to every line
	if (bFoundVert)
	{
		for (int nY = 0; nY < diagram.GetSize(); nY++)
			diagram[nY] = " " + diagram[nY];
	}

	// 3. rects
	for (nY = 0; nY < diagram.GetSize(); nY++)
	{
		int nLineLen = LineLength(diagram, nY);

		for (int nX = 0; nX < nLineLen; nX++)
		{
			AddRect(diagram, nX, nY);
		}
	}

	// 4. connections
	if (GetRectCount() > 1)
	{
		for (nY = 0; nY < diagram.GetSize(); nY++)
		{
			int nLineLen = LineLength(diagram, nY);

			for (int nX = 0; nX < nLineLen; nX++)
			{
				AddConn(diagram, nX, nY);
			}
		}
	}
}

BOOL CTextDiagram::TestConnStartEndChar(const CStringArray& diagram, int nX, int nY) const
{
	return (TestChar(diagram, nX, nY, CHAR_START) || TestChar(diagram, nX, nY, CHAR_END));
}

TDPOS CTextDiagram::RectGetNextChar(const CStringArray& diagram, TDPOS nCurPos, int& nX, int& nY) const
{
	if (PtInRect(nX, nY))
		return NONE;

	switch (nCurPos)
	{
	case RECT_TOP:
		// test change of direction first
		if (TestChar(diagram, nX + 1, nY + 1, CHAR_RIGHT) || TestConnStartEndChar(diagram, nX + 1, nY + 1))
		{
			nX++;
			nY++;
			return RECT_RIGHT;
		}
		else if (TestChar(diagram, nX + 1, nY, CHAR_TOP) || TestConnStartEndChar(diagram, nX + 1, nY))
		{
			nX++;
			return RECT_TOP;
		}
		break;

	case RECT_RIGHT:
		// test change of direction first
		if (TestChar(diagram, nX - 1, nY + 1, CHAR_BOTTOM) || TestConnStartEndChar(diagram, nX - 1, nY + 1))
		{
			nX--;
			nY++;
			return RECT_BOTTOM;
		}
		else if (TestChar(diagram, nX, nY + 1, CHAR_RIGHT) || TestConnStartEndChar(diagram, nX, nY + 1))
		{
			nY++;
			return RECT_RIGHT;
		}
		break;

	case RECT_BOTTOM:
		// test change of direction first
		if (TestChar(diagram, nX - 1, nY - 1, CHAR_LEFT) || TestConnStartEndChar(diagram, nX - 1, nY - 1))
		{
			nX--;
			nY--;
			return RECT_LEFT;
		}
		else if (TestChar(diagram, nX - 1, nY, CHAR_BOTTOM) || TestConnStartEndChar(diagram, nX - 1, nY))
		{
			nX--;
			return RECT_BOTTOM;
		}
		break;

	case RECT_LEFT:
		// test change of direction first
		if (TestChar(diagram, nX + 1, nY - 1, CHAR_TOP) || TestConnStartEndChar(diagram, nX + 1, nY - 1))
		{
			nX++;
			nY--;
			return RECT_TOP;
		}
		else if (TestChar(diagram, nX, nY - 1, CHAR_LEFT) || TestConnStartEndChar(diagram, nX, nY - 1))
		{
			nY--;
			return RECT_LEFT;
		}
		break;
	}

	return NONE;
}

TDPOS CTextDiagram::RectStartChar(const CStringArray& diagram, int nX, int nY) const
{
	if (PtInRect(nX, nY))
		return NONE;

	char c = GetChar(diagram, nX, nY);

	// test for top char
	if (c == CHAR_TOP || c == CHAR_START || c == CHAR_END)
	{
		// test for left char
		c = GetChar(diagram, nX - 1, nY + 1);
		
		if (c == CHAR_LEFT || c == CHAR_START || c == CHAR_END)
		{
			// finally test for corner char
			c = GetChar(diagram, nX - 1, nY);

			if (c == SPACE || c == CHAR_START || c == CHAR_END)
				return RECT_TOP;
		}
	}

	return NONE;
}

TDPOS CTextDiagram::ConnStartChar(const CStringArray& diagram, int nX, int nY) const
{
	if (!PtInRect(nX, nY))
		return NONE;

	char c = GetChar(diagram, nX, nY);

	if (c == CHAR_START)
	{
		// check we are on a rect border
		int nRect = IntersectRect(nX, nY);
		ASSERT (nRect != -1);

		CTDRect rect;
		
		if (GetRect(nRect, rect) && rect.GetSide(CPoint(nX, nY)) != NONE)
			return CONN_START;
	}

	// all else
	return NONE;
}

TDPOS CTextDiagram::ConnGetNextChar(const CStringArray& diagram, TDPOS nCurPos, int& nX, int& nY) const
{
	switch (nCurPos)
	{
	case CONN_START:
		// test normal chars first
		if (IntersectRect(nX + 1, nY) == -1 && TestChar(diagram, nX + 1, nY, CHAR_HORZ))
		{
			nX++;
			return CONN_RIGHT;
		}
		else if (IntersectRect(nX - 1, nY) == -1 && TestChar(diagram, nX - 1, nY, CHAR_HORZ))
		{
			nX--;
			return CONN_LEFT;
		}
		else if (IntersectRect(nX, nY + 1) == -1 && TestChar(diagram, nX, nY + 1, CHAR_VERT))
		{
			nY++;
			return CONN_DOWN;
		}
		else if (IntersectRect(nX, nY - 1) == -1 && TestChar(diagram, nX, nY - 1, CHAR_VERT))
		{
			nY--;
			return CONN_UP;
		}
		// then, if vertical, test for immediate change of direction
		else if (IntersectRect(nX, nY + 1) == -1 && TestChar(diagram, nX, nY + 1, CHAR_HORZ))
		{
			nY++;

			// which way does it go?
			if (TestChar(diagram, nX + 1, nY, CHAR_HORZ) || TestChar(diagram, nX + 1, nY, CHAR_CROSSOVER))
				return CONN_RIGHT;
			else
				return CONN_LEFT;
		}
		else if (IntersectRect(nX, nY - 1) == -1 && TestChar(diagram, nX, nY - 1, CHAR_HORZ))
		{
			nY--;

			// which way does it go?
			if (TestChar(diagram, nX + 1, nY, CHAR_HORZ) || TestChar(diagram, nX + 1, nY, CHAR_CROSSOVER))
				return CONN_RIGHT;
			else
				return CONN_LEFT;
		}
		break;
		
	case CONN_RIGHT:
		// 1. test for continuation
		if (TestChar(diagram, nX + 1, nY, CHAR_HORZ) || TestChar(diagram, nX + 1, nY, CHAR_CROSSOVER))
		{
			nX++;
			return CONN_RIGHT;
		}
		// 2. test for termination
		else if (TestChar(diagram, nX + 1, nY, CHAR_END))
		{
			nX++;
			return CONN_END;
		}
		// 3. test for change of direction (down)
		else if (TestChar(diagram, nX, nY + 1, CHAR_VERT) || TestChar(diagram, nX, nY + 1, CHAR_CROSSOVER))
		{
			nY++;
			return CONN_DOWN;
		}
		// 4. test for change of direction (up)
		else if (TestChar(diagram, nX, nY - 1, CHAR_VERT) || TestChar(diagram, nX, nY - 1, CHAR_CROSSOVER))
		{
			nY--;
			return CONN_UP;
		}
		// 5. test for termination (down)
		else if (TestChar(diagram, nX, nY + 1, CHAR_END))
		{
			nY++;
			return CONN_END;
		}
		// 6. test for termination (up)
		else if (TestChar(diagram, nX, nY - 1, CHAR_END))
		{
			nY--;
			return CONN_END;
		}
		break;
		
	case CONN_LEFT:
		// 1. test for continuation
		if (TestChar(diagram, nX - 1, nY, CHAR_HORZ) || TestChar(diagram, nX - 1, nY, CHAR_CROSSOVER))
		{
			nX--;
			return CONN_LEFT;
		}
		// 2. test for termination
		else if (TestChar(diagram, nX - 1, nY, CHAR_END))
		{
			nX--;
			return CONN_END;
		}
		// 3. test for change of direction (down)
		else if (TestChar(diagram, nX, nY + 1, CHAR_VERT) || TestChar(diagram, nX, nY + 1, CHAR_CROSSOVER))
		{
			nY++;
			return CONN_DOWN;
		}
		// 4. test for change of direction (up)
		else if (TestChar(diagram, nX, nY - 1, CHAR_VERT) || TestChar(diagram, nX, nY - 1, CHAR_CROSSOVER))
		{
			nY--;
			return CONN_UP;
		}
		// 5. test for termination (down)
		else if (TestChar(diagram, nX, nY + 1, CHAR_END))
		{
			nY++;
			return CONN_END;
		}
		// 6. test for termination (up)
		else if (TestChar(diagram, nX, nY - 1, CHAR_END))
		{
			nY--;
			return CONN_END;
		}
		break;
		
	case CONN_UP:
		if (TestChar(diagram, nX, nY - 1, CHAR_HORZ))
		{
			nY--;

			// which way does it go?
			if (TestChar(diagram, nX + 1, nY, CHAR_HORZ) || TestChar(diagram, nX + 1, nY, CHAR_CROSSOVER) || TestChar(diagram, nX + 1, nY, CHAR_END))
				return CONN_RIGHT;
			else
				return CONN_LEFT;
		}
		else if (TestChar(diagram, nX, nY - 1, CHAR_CROSSOVER))
		{
			nY--;
			return CONN_UP;
		}
		else if (TestChar(diagram, nX, nY - 1, CHAR_END))
		{
			nY--;
			return CONN_END;
		}
		else if (TestChar(diagram, nX, nY - 1, CHAR_VERT))
		{
			nY--;
			return CONN_UP;
		}
		break;
		
	case CONN_DOWN:
		if (TestChar(diagram, nX, nY + 1, CHAR_HORZ))
		{
			nY++;

			// which way does it go?
			if (TestChar(diagram, nX + 1, nY, CHAR_HORZ) || TestChar(diagram, nX + 1, nY, CHAR_CROSSOVER) || TestChar(diagram, nX + 1, nY, CHAR_END))
				return CONN_RIGHT;
			else
				return CONN_LEFT;
		}
		else if (TestChar(diagram, nX, nY + 1, CHAR_CROSSOVER))
		{
			nY++;
			return CONN_DOWN;
		}
		else if (TestChar(diagram, nX, nY + 1, CHAR_END))
		{
			nY++;
			return CONN_END;
		}
		else if (TestChar(diagram, nX, nY + 1, CHAR_VERT))
		{
			nY++;
			return CONN_DOWN;
		}
		break;
	}
	
	return NONE;
}

void CTextDiagram::AddConn(const CStringArray& diagram, int nX, int nY)
{
	TDPOS nPos = ConnStartChar(diagram, nX, nY);

	if (nPos == CONN_START)
	{
		CPoint ptStart(nX, nY);
		int nFromRect = IntersectRect(nX, nY);

		BOOL bDone = FALSE;
		TDPOS nPosOrg = nPos;

		int nIteration = 0; // for catching infinite loops

		while (nPos != NONE && !bDone && nIteration < MAXITERATIONS)
		{
			nIteration++;
			nPos = ConnGetNextChar(diagram, nPos, nX, nY);

			// are we done yet?
			bDone = (nPos == CONN_END);
		}

		if (bDone)
		{
			int nToRect = IntersectRect(nX, nY);

			if (nToRect != -1 && nToRect != nFromRect)
			{
				CTDRect rect;

				GetRect(nFromRect, rect);
				int nSideFrom = rect.GetSide(ptStart);

				ASSERT (nSideFrom != NONE);

				AddConnection(nFromRect, nToRect, nSideFrom);
			}
		}
	}
}

BOOL CTextDiagram::PtInRect(int nX, int nY) const
{
	return (IntersectRect(nX, nY) != -1);
}

int CTextDiagram::IntersectRect(int nX, int nY) const
{
	if (!m_aRects.GetSize())
		return -1;

	// else
	for (int nRect = 0; nRect < m_aRects.GetSize(); nRect++)
	{
		CRect rect = m_aRects[nRect];

		rect.right++; // inclusive
		rect.bottom++; // inclusive

		if (rect.PtInRect(CPoint(nX, nY)))
			return nRect;
	}

	return -1;
}

BOOL CTextDiagram::PtInConn(int nX, int nY, BOOL bHorz) const
{
	return (IntersectConn(CPoint(nX, nY), bHorz) != -1);
}

int CTextDiagram::IntersectConn(CPoint point, BOOL bHorz) const
{
	int nConn = m_aConns.GetSize();

	while (nConn--)
	{
		if (m_aConns[nConn].PtInConn(point, bHorz))
			return nConn;
	}

	return -1; // not found
}

BOOL CTextDiagram::IntersectRect(const CRect& rect, int nIgnoreRect) const
{
	if (!m_aRects.GetSize())
		return FALSE;

	// else
	CRect rTest(rect);
	rTest.right++; // inclusive
	rTest.bottom++; // inclusive

	for (int nRect = 0; nRect < m_aRects.GetSize(); nRect++)
	{
		CRect r = m_aRects[nRect];

		r.right++; // inclusive
		r.bottom++; // inclusive

		if (CRect().IntersectRect(r, rTest) && nRect != nIgnoreRect)
			return TRUE;
	}

	return FALSE;
}

void CTextDiagram::AddRect(const CStringArray& diagram, int nX, int nY)
{
	TDPOS nPos = RectStartChar(diagram, nX, nY);

	if (nPos != NONE)
	{
		CPoint ptOrg(nX, nY);
		CRect rect(nX, nY, nX, nY);

		BOOL bDone = FALSE;
		TDPOS nPosOrg = nPos;

		int nIteration = 0; // for catching infinite loops

		while (nPos != NONE && !bDone && nIteration < MAXITERATIONS)
		{
			nIteration++;
			nPos = RectGetNextChar(diagram, nPos, nX, nY);

			if (nPos != NONE)
			{
				rect.left = min(rect.left, nX);
				rect.top = min(rect.top, nY);
				rect.right = max(rect.right, nX);
				rect.bottom = max(rect.bottom, nY);

				// are we done yet?
				bDone = (nPos == nPosOrg && nX == ptOrg.x && nY == ptOrg.y);
			}
		}

		// get rect text
		if (bDone)
		{
			CString sText;

			for (nY = rect.top + 1; nY < rect.bottom; nY++)
			{
				for (nX = rect.left + 1; nX < rect.right; nX++)
				{
					sText += GetChar(diagram, nX, nY);
				}
				
				// if the last char is a space then insert a carriage return
				if (sText.Right(1) == " ")
				{
					sText.TrimRight();
					sText += "\r\n";
				}
			}

			sText.TrimRight();
			AddRect(rect, sText);
		}
	}
}

int CTextDiagram::GetRectCount() const
{
	return m_aRects.GetSize();
}

int CTextDiagram::GetConnectionCount() const
{
	return m_aConns.GetSize();
}

BOOL CTextDiagram::GetRect(int nRect, CTDRect& rect) const
{
	if (nRect < 0 || nRect >= GetRectCount())
		return FALSE;

	// else
	rect = m_aRects[nRect];
	return TRUE;
}

BOOL CTextDiagram::GetRect(int nRect, CRect& rect) const
{
	CTDRect tdRect;

	if (GetRect(nRect, tdRect))
	{
		rect = tdRect;
		return TRUE;
	}

	return FALSE;
}


BOOL CTextDiagram::DeleteRect(int nRect)
{
	if (nRect < 0 || nRect >= GetRectCount())
		return FALSE;

	// modify all connections referencing rects after this one
	// to take account of the removal
	// and remove all connections which reference this rect
	int nConn = m_aConns.GetSize();

	while (nConn--)
	{
		CTDConnection& conn = m_aConns[nConn];

		int nRectFrom = conn.RectFrom();
		int nRectTo = conn.RectTo();

		if (nRectFrom == nRect || nRectTo == nRect)
			m_aConns.RemoveAt(nConn);
		else
		{
			if (nRectFrom > nRect)
				conn.SetRectFrom(nRectFrom - 1);

			if (nRectTo > nRect)
				conn.SetRectTo(nRectTo - 1);
		}
	}

	m_aRects.RemoveAt(nRect);
	RebuildConnections();

	return TRUE;
}

BOOL CTextDiagram::DeleteConnection(int nConn)
{
	if (nConn < 0 || nConn >= GetConnectionCount())
		return FALSE;

	m_aConns.RemoveAt(nConn);
	return TRUE;
}

BOOL CTextDiagram::GetConnection(int nConn, CTDConnection& conn) const
{
	if (nConn < 0 || nConn >= GetConnectionCount())
		return FALSE;

	// else
	conn = m_aConns[nConn];
	return TRUE;
}

BOOL CTextDiagram::SetRect(int nRect, const CTDRect& rect)
{
	if (nRect < 0 || nRect >= GetRectCount())
		return FALSE;

	// validate the rect
	if (rect.left <= 0 || rect.top <= 0 || rect.Width() < 2 || rect.Height() < 2)
		return -1;

	// make sure it doesn't overlap any other rect
	// don't let it come too close either
	CRect r(rect);
	r.InflateRect(1, 1);

	if (IntersectRect(r, nRect))
		return FALSE;

	m_aRects[nRect] = rect;

	// rebuild all connections to ensure no overlaps
	RebuildConnections();

	// update the diagram size
	RecalcSize();

	return TRUE;
}

void CTextDiagram::RebuildConnections()
{
	for (int nConn = 0; nConn < m_aConns.GetSize(); nConn++)
	{
		// defensive programming
		CTDConnection conn = m_aConns[nConn];

		if (BuildPath(conn, nConn))
			m_aConns[nConn] = conn;
	}
}

int CTextDiagram::AddRect(LPCRECT pRect, LPCTSTR szText)
{
	if (!pRect)
		return NewRect();

	// validate the rect
	CRect rect(pRect);

	if (rect.left <= 0 || rect.top <= 0 || rect.Width() < 2 || rect.Height() < 2)
		return -1;

	// make sure it doesn't overlap any other rect
	// don't let it come too close either
	CRect r(rect);
	r.InflateRect(1, 1);

	if (IntersectRect(r))
		return -1;

	// don't let it overlap or touch any connections
	if (IntersectConn(rect))
		return -1;

	int nRect = m_aRects.Add(CTDRect(rect, szText));

	// rebuild all connections to ensure no overlaps
	RebuildConnections();

	// update the diagram size
	RecalcSize();

	return nRect;
}

int CTextDiagram::FindConnection(int nRectFrom, int nRectTo) const
{
	int nConn = m_aConns.GetSize();

	while (nConn--)
	{
		if (m_aConns[nConn].RectFrom() == nRectFrom && m_aConns[nConn].RectTo() == nRectTo)
			return nConn;
	}

	return -1; // not found
}

int CTextDiagram::AddConnection(int nRectFrom, int nRectTo, int nSideFrom)
{
	if (FindConnection(nRectFrom, nRectTo) != -1)
		return -1;

	CTDConnection conn(nRectFrom, nRectTo, nSideFrom);

	if (BuildPath(conn, -1))
	{
		int nConn = m_aConns.Add(conn);

		// update the diagram size
		RecalcSize();

		return nConn;
	}

	return -1;
}

BOOL CTextDiagram::SetConnection(int nConn, const CTDConnection& conn)
{
	CTDConnection connExist;

	// retrieve existing connection
	if (!GetConnection(nConn, connExist))
		return FALSE;

	// ensure requested connection is not already taken
	int nCurConn = FindConnection(conn.RectFrom(), conn.RectTo());

	if (nCurConn != -1 && nCurConn != nConn)
		return FALSE;

	// update existing connection
	connExist = conn;

	if (BuildPath(connExist, nConn))
	{
		m_aConns.SetAt(nConn, connExist);

		// update the diagram size
		RecalcSize();

		return TRUE;
	}

	return FALSE;
}

void CTextDiagram::GetRelationship(const CRect& rFrom, const CRect& rTo, int& nHorz, int& nVert)
{
	GetRelationship(rFrom.CenterPoint(), rTo.CenterPoint(), nHorz, nVert);
}

void CTextDiagram::GetRelationship(CPoint ptFrom, const CRect& rTo, int& nHorz, int& nVert)
{
	// horz
	if (ptFrom.x >= rTo.left && ptFrom.x <= rTo.right)
		nHorz = 0; // 'to' is aligned with 'from'

	else if (ptFrom.x < rTo.left)
		nHorz = 1; // 'to' is to the right of 'from'

	else
		nHorz = -1; // 'to' is to the left of 'from'

	// vert
	if (ptFrom.y >= rTo.top && ptFrom.y <= rTo.bottom)
		nVert = 0; // 'to' is aligned with 'from'

	else if (ptFrom.y < rTo.top)
		nVert = 1; // 'to' is below 'from'

	else
		nVert = -1; // 'to' is above 'from'
}

void CTextDiagram::GetRelationship(CPoint ptFrom, CPoint ptTo, int& nHorz, int& nVert)
{
	// horz
	if (ptFrom.x == ptTo.x)
		nHorz = 0; // 'to' is aligned with 'from'

	else if (ptFrom.x < ptTo.x)
		nHorz = 1; // 'to' is to the right of 'from'

	else
		nHorz = -1; // 'to' is to the left of 'from'

	// vert
	if (ptFrom.y == ptTo.y)
		nVert = 0; // 'to' is aligned with 'from'

	else if (ptFrom.y < ptTo.y)
		nVert = 1; // 'to' is below 'from'

	else
		nVert = -1; // 'to' is above 'from'
}

BOOL CTextDiagram::PickStartPoint(const CTDConnection& conn, int nIncrement, CPoint& ptStart, int nIgnoreConn)
{
	CRect rFrom;
	
	if (!GetRect(conn.RectFrom(), rFrom))
		return FALSE;

	BOOL bHorz = FALSE;

	// determine start point
	int nSideFrom = conn.SideFrom();

	switch (nSideFrom)
	{
	case RECT_LEFT:
	case RECT_RIGHT:
		{
			ptStart.x = (nSideFrom == RECT_LEFT) ? rFrom.left : rFrom.right;
			ptStart.y = rFrom.top + 1;

			while(nIncrement--)
				ptStart.y++;

			if (ptStart.y >= rFrom.bottom)
				return FALSE;

			int nIntersect = IntersectConn(ptStart, TRUE);

			return (nIntersect == -1 || nIntersect == nIgnoreConn) ? 1 : -1;
		}
		break;

	case RECT_TOP:
	case RECT_BOTTOM:
		{
			ptStart.y = (nSideFrom == RECT_TOP) ? rFrom.top : rFrom.bottom;
			ptStart.x = rFrom.left + 1;

			while(nIncrement--)
				ptStart.x++;

			if (ptStart.x >= rFrom.right)
				return FALSE;

			int nIntersect = IntersectConn(ptStart, FALSE);

			return (nIntersect == -1 || nIntersect == nIgnoreConn) ? 1 : -1;
		}
		break;
	}

	return 0;
}

BOOL CTextDiagram::BuildPath(CTDConnection& conn, int nIgnoreConn)
{
	if (conn.RectFrom() == conn.RectTo())
		return FALSE;
	
	CRect rFrom, rTo;
	
	if (!GetRect(conn.RectFrom(), rFrom) || !GetRect(conn.RectTo(), rTo))
		return FALSE;

	// reset
	conn.ResetSegments();

	// to get the best results we try every point on the start side
	// and take to shortest route as our solution
	CTDConnection connMin;
	int nIncrement = 0;
	CPoint ptFrom;
	int nPick = 0;

	while (nPick = PickStartPoint(conn, nIncrement, ptFrom, nIgnoreConn))
	{
		if (nPick == -1) // already taken
		{
			nIncrement++;
			continue;
		}

		CTDConnection connTemp(conn);
		
		connTemp.SetStartPos(ptFrom);
		BOOL bCanBacktrack = FALSE; // always false for first segment

		int nIteration = 0; // for catching infinite loops

		int nHorzRect, nVertRect;
		GetRelationship(rFrom, rTo, nHorzRect, nVertRect);

		// build the path
		// note: we can stop any path as soon as we exceed the current shortest path
		while (IntersectRect(connTemp.GetEndPos()) != connTemp.RectTo() && nIteration < MAXITERATIONS &&
				(!connMin.GetLength() || connTemp.GetLength() < connMin.GetLength()))
		{
			// start of a new segment
			int nCurSegment = 0;
			BOOL bSegmentDone = FALSE;

			// cache our current relationship
			int nHorzOrg, nVertOrg;
			GetRelationship(connTemp.GetEndPos(), rTo, nHorzOrg, nVertOrg);

			// refresh current relationship
			while (!bSegmentDone && nIteration < MAXITERATIONS)
			{
				nIteration++;

				// refresh current relationship
				CPoint ptEnd = connTemp.GetEndPos(nCurSegment);

				int nHorz, nVert;
				GetRelationship(ptEnd, rTo, nHorz, nVert);

				// special case
				if (connTemp.GetStartPos() == connTemp.GetEndPos() && !nCurSegment) // very start of the path
				{
					// always move at least one char in the direction we are pointing
					switch (connTemp.SideFrom())
					{
					case RECT_LEFT:
					case RECT_TOP:
						nCurSegment--;
						break;

					case RECT_RIGHT:
					case RECT_BOTTOM:
						nCurSegment++;
						break;

					default:
						ASSERT(0);
					}
				}
				else
				{
					// special case
					if (!nCurSegment) // start of segment
					{
						// select new direction
						int nPrevDir = connTemp.GetEndDirection();

						switch (nPrevDir)
						{
						case CONN_UP:
						case CONN_DOWN:
							if (nHorz)
								nCurSegment = nHorz;
							else
							{
								// look more closely at the rect relationship
								nCurSegment = nHorzRect ? nHorzRect : 1;
							}
							break;
							
						case CONN_RIGHT:
						case CONN_LEFT:
							if (nVert)
								nCurSegment = nVert;
							else
							{
								// look more closely at the rect relationship
								nCurSegment = nVertRect ? nVertRect : 1;
							}
							break;
						}

						continue;
					}
					// special case
					else if (abs(nCurSegment) == 1) // second move
					{
						// make sure we're *not* pointing off into the wide blue yonder
						int nDir = connTemp.GetEndDirection(nCurSegment);

						switch (nDir)
						{
						case CONN_LEFT:
							if (nHorz == 1)
							{
								bSegmentDone = TRUE;
								connTemp.AddSegment(nCurSegment);
								continue;
							}
							break;

						case CONN_UP:
							if (nVert == 1)
							{
								bSegmentDone = TRUE;
								connTemp.AddSegment(nCurSegment);
								continue;
							}
							break;

						case CONN_RIGHT:
							if (nHorz == -1)
							{
								bSegmentDone = TRUE;
								connTemp.AddSegment(nCurSegment);
								continue;
							}
							break;

						case CONN_DOWN:
							if (nVert == -1)
							{
								bSegmentDone = TRUE;
								connTemp.AddSegment(nCurSegment);
								continue;
							}
							break;

						default:
							ASSERT(0);
						}
					}

					// continue to move in same direction until we hit something
					// or we intersect vertically or horizontally with nRectTo
					int nDir = connTemp.GetEndDirection(nCurSegment);
					
					// 1. check if we've arrived
					if (IntersectRect(ptEnd) == connTemp.RectTo())
					{
						bSegmentDone = TRUE;
						connTemp.AddSegment(nCurSegment);
						continue;
					}

					// 2. hit another rect or conn?
					int nConn = IntersectConn(ptEnd, nDir == CONN_LEFT || nDir == CONN_RIGHT);

					if (PtInRect(ptEnd.x, ptEnd.y) || (nConn != -1 && nConn != nIgnoreConn))
					{
						bSegmentDone = TRUE;

						if (bCanBacktrack) // backtrack to last segment, move on and try again
						{
							connTemp.IncrementLastSegment();
						}
						else
						{
							CTDConnection::Decrement(nCurSegment);
							connTemp.AddSegment(nCurSegment);
						}
					}
					// 3. horz intersect?
					else if (!nHorz && (nHorz != nHorzOrg || nDir == CONN_LEFT || nDir == CONN_RIGHT))
					{
						ASSERT (nHorz != nVert); // can't be both

						bCanBacktrack = TRUE;
						bSegmentDone = TRUE;

						connTemp.AddSegment(nCurSegment);
					}
					// 3. vert intersect?
					else if (!nVert && (nVert != nVertOrg || nDir == CONN_UP || nDir == CONN_DOWN)) 
					{
						ASSERT (nHorz != nVert); // can't be both

						bCanBacktrack = TRUE;
						bSegmentDone = TRUE;

						connTemp.AddSegment(nCurSegment);
					}
					// 4. just keep going
					else
					{
						CTDConnection::Increment(nCurSegment);
					}
				}
			}
		}

		// check for infinite loop
		if (nIteration < MAXITERATIONS)
		{
			// cleanup any unexpected backtracks
			CTDConnection temp2(connTemp);
			connTemp.ResetSegments();

			for (int nSeg = 0; nSeg < temp2.NumSegments(); nSeg++)
			{
				int nSegment = temp2.Segment(nSeg);

				// if the next (nSeg + 1) segment is zero
				// then aggregate this with the one after that
				// and omit the zero item
				if (nSeg < temp2.NumSegments() - 1 && temp2.Segment(nSeg + 1) == 0)
				{
					if (nSeg < temp2.NumSegments() - 2)
					{
						nSegment += temp2.Segment(nSeg + 2);
						nSeg++;
					}

					nSeg++;
				}

				connTemp.AddSegment(nSegment);
			}

			// test for shortest
			if (!connMin.GetLength() || connTemp.GetLength() < connMin.GetLength())
				connMin = connTemp;
		}

		// next try
		nIncrement++;
	}

	// pick the shortest route
	conn = connMin;

	return TRUE;
}

void CTextDiagram::TraceDiagram(const CStringArray& diagram)
{
#ifdef _DEBUG
	TRACE ("\n");

	for (int nLine = 0; nLine < diagram.GetSize(); nLine++)
		TRACE("%s\n", diagram[nLine]);

	TRACE ("\n");
#endif
}

BOOL CTextDiagram::VerifyDiagram() const
{
	// to verify:
	// 1. get the current diagram as text
	// 2. load it into a temp diagram
	// 3. compare the diagram ro ourselves
	CString sDiagram;
	GetDiagram(sDiagram, FALSE);

	CTextDiagram dgmTemp(sDiagram);

	if (!(*this == dgmTemp))
	{
//		TRACE(sDiagram);
		return FALSE;
	}

	return TRUE;
}

BOOL CTextDiagram::operator==(const CTextDiagram& diagram) const
{
	if (m_aRects.GetSize() != diagram.m_aRects.GetSize())
		return FALSE;

	if (m_aConns.GetSize() != diagram.m_aConns.GetSize())
		return FALSE;

	CTextDiagram dgmTemp(diagram);

	// rects can be in any order
	for (int nRect = 0; nRect < m_aRects.GetSize(); nRect++)
	{
		for (int nRect2 = 0; nRect2 < dgmTemp.m_aRects.GetSize(); nRect2++)
		{
			if (m_aRects[nRect] == dgmTemp.m_aRects[nRect2])
			{
				dgmTemp.m_aRects.RemoveAt(nRect2);
				break; // found it
			}
		}
	}

	if (dgmTemp.m_aRects.GetSize())
		return FALSE;

	// conns can be in any order
	for (int nConn = 0; nConn < m_aConns.GetSize(); nConn++)
	{
		for (int nConn2 = 0; nConn2 < dgmTemp.m_aConns.GetSize(); nConn2++)
		{
			if (m_aConns[nConn] == dgmTemp.m_aConns[nConn2])
			{
				dgmTemp.m_aConns.RemoveAt(nConn2);
				break; // found it
			}
		}
	}

	if (dgmTemp.m_aConns.GetSize())
		return FALSE;

	return TRUE;
}

const CTextDiagram& CTextDiagram::operator=(const CTextDiagram& diagram)
{
	m_aRects.Copy(diagram.m_aRects);
	m_aConns.Copy(diagram.m_aConns);
	m_size = diagram.m_size;

	return *this;
}

void CTextDiagram::RecalcSize()
{
	CRect rSize(0, 0, 0, 0);

	for (int nRect = 0; nRect < m_aRects.GetSize(); nRect++)
	{
		rSize |= m_aRects[nRect];
	}

	for (int nConn = 0; nConn < m_aConns.GetSize(); nConn++)
	{
		rSize |= m_aConns[nConn].GetBoundingRect();
	}

	m_size.cx = rSize.right;
	m_size.cy = rSize.bottom;
}

BOOL CTextDiagram::CanAddConnection(const CTDRect& rect, int nSide)
{
	int nX, nY;

	switch (nSide)
	{
	case RECT_LEFT:
		for (nY = rect.top; nY <= rect.bottom; nY++)
		{
			if (!PtInConn(rect.left, nY, TRUE))
				return TRUE;
		}
		break;

	case RECT_RIGHT:
		for (nY = rect.top; nY <= rect.bottom; nY++)
		{
			if (!PtInConn(rect.right, nY, TRUE))
				return TRUE;
		}
		break;

	case RECT_TOP:
		for (nX = rect.left; nX <= rect.right; nX++)
		{
			if (!PtInConn(nX, rect.top, FALSE))
				return TRUE;
		}
		break;

	case RECT_BOTTOM:
		for (nX = rect.left; nX <= rect.right; nX++)
		{
			if (!PtInConn(nX, rect.bottom, FALSE))
				return TRUE;
		}
		break;
	}

	return FALSE;
}

int CTextDiagram::NewRect()
{
	// work our way out from the origin in rings until we find a spot
	int nNewRect = -1;
	int nRadius = 1;

	while (nNewRect == -1)
	{
		for (int nY = 1; nY <= nRadius && nNewRect == -1; nY++)
		{
			for (int nX = 1; nX <= nRadius && nNewRect == -1; nX++)
				nNewRect = AddRect(CRect(nX, nY, nX + 5, nY + 3));
		}

		nRadius++;
	}

	return nNewRect;
}

BOOL CTextDiagram::IntersectConn(const CRect& rect) const
{
	// the simplest though probably not the most efficient approach
	// simply to check every point in the rect
	for (int nX = rect.left; nX <= rect.right; nX++)
	{
		for (int nY = rect.top; nY <= rect.bottom; nY++)
		{
			if (PtInConn(nX, nY, TRUE) || PtInConn(nX, nY, FALSE))
				return TRUE;
		}
	}

	return FALSE;
}

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 has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Software Developer Maptek
Australia Australia
.dan.g. is a naturalised Australian and has been developing commercial windows software since 1998.

Comments and Discussions