Click here to Skip to main content
15,891,529 members
Articles / Desktop Programming / MFC

UMLEditor - revisiting the vector editor

Rate me:
Please Sign up or sign in to vote.
4.99/5 (156 votes)
5 Jul 2006Public Domain8 min read 377.3K   37.1K   326  
An UML editor with code-generation capabilities derived from CDiagramEditor.
/* ==========================================================================
	Class :			CUMLLineSegment

	Author :		Johan Rosengren, Abstrakt Mekanik AB

	Date :			2004-04-29

	Purpose :		"CUMLLineSegment" is a "CUMLEntity"-
					derived class, representing a line that can be linked 
					to other "CUMLEntity"-derived objects.

	Description :	The implementation is based on "CDiagramLine", even though 
					not derived from it. The class allows two link point, 
					"LINK_START" and "LINK_END" - the top-left and bottom-right 
					corners. 
					The links can be attached to any link point on some 
					objects, and contains offset values to the top or left 
					to make it possible to move the segment along with the 
					linked object.

	Usage :			Create with "CUMLControlFactory::CreateFromString" or 
					add an instance to the editor "StartDrawingObject".

   ========================================================================
	Changes :
		8/7 2004	Support for the Interface line type added.
		5/8 2004	Fixed bug in GetOffset - zoom should *not* be applied.
   ========================================================================
		28/8 2004	Not calling CDiagramEntity::SetRect when setting rects, 
					to keep non-normalization.
   ========================================================================*/
#include "stdafx.h"
#include "UMLLineSegment.h"
#include "UMLEntityContainer.h"
#include "DiagramEditor/DiagramLine.h"
#include "DiagramEditor/Tokenizer.h"
#include "StringHelpers.h"

#include <math.h>

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


//////////////////////////////////////////
// LineDDA callbacks from CDiagramLine
//

VOID CALLBACK HitTest( int X, int Y, LPARAM data );
VOID CALLBACK HitTestRect( int X, int Y, LPARAM data );

CUMLLineSegment::CUMLLineSegment()
/* ============================================================
	Function :		CUMLLineSegment::CUMLLineSegment
	Description :	Constructor
	Access :		Public
					
	Return :		void
	Parameters :	none

	Usage :			Create either through "CUMLControlFactory" or
					by calling "new" and adding the pointer to the
					editor via "StartDrawingObject"

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

	SetPropertyDialog( &m_dlg, CUMLLinkPropertyDialog::IDD );

	SetLinkType( LINK_START, 0 );
	SetLinkType( LINK_END, 0 );

	SetMinimumSize( CSize( -1, -1 ) );
	SetMaximumSize( CSize( -1, -1 ) );
	SetType( _T( "uml_line" ) );
	SetTitle( _T( "" ) );
	SetStyle( 0 );
	SetStartLabel( _T( "" ) );
	SetEndLabel( _T( "" ) );

	SetOffset( LINK_START, 0 );
	SetOffset( LINK_END, 0 );

}

CUMLLineSegment::~CUMLLineSegment()
/* ============================================================
	Function :		CUMLLineSegment::~CUMLLineSegment
	Description :	Destructor
	Access :		Public
					
	Return :		void
	Parameters :	none

	Usage :			Note that objects will normally be deleted by
					the container.

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

	if( m_dlg.m_hWnd )
		m_dlg.DestroyWindow();

}

CDiagramEntity* CUMLLineSegment::Clone()
/* ============================================================
	Function :		CUMLLineSegment::Clone
	Description :	Clone this object to a new object.
	Access :		Public
					
	Return :		CDiagramEntity*	-	The new object.
	Parameters :	none

	Usage :			Call to create a clone of the object. The 
					caller will have to delete the object.

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

	CUMLLineSegment* obj = new CUMLLineSegment;
	obj->Copy( this );
	return obj;

}

void CUMLLineSegment::Draw( CDC* dc, CRect rect )
/* ============================================================
	Function :		CUMLLineSegment::Draw
	Description :	Draws the object.
	Access :		Public

	Return :		void
	Parameters :	CDC* dc		-	The "CDC" to draw to. 
					CRect rect	-	The real rectangle of the 
									object.

	Usage :			The function should clean up all selected 
					objects. Note that the "CDC" is a memory "CDC", 
					so creating a memory "CDC" in this function 
					will probably not speed up the function.

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

	int mode = dc->SetBkMode( TRANSPARENT );

	if( !( GetStyle() & STYLE_INVISIBLE ) )
	{
		CPen pen;
		if( GetStyle() & STYLE_DASHED )
			pen.CreatePen( PS_DOT, 0, RGB( 0, 0, 0 ) );
		else
			pen.CreatePen( PS_SOLID, 0, RGB( 0, 0, 0 ) );

		CPen* oldpen = dc->SelectObject( &pen );

		// Draw line
		dc->MoveTo( rect.TopLeft() );
		dc->LineTo( rect.BottomRight() );

		dc->SelectObject( oldpen );
	}

	dc->SelectStockObject( BLACK_PEN );
	dc->SelectStockObject( BLACK_BRUSH );

	int cut = round( static_cast< double >( GetMarkerSize().cx ) * GetZoom() / 2 );
	int cy = round( 14.0 * GetZoom() );

	CFont font;
	font.CreateFont( -round( 12.0 * GetZoom() ), 0,0,0,FW_NORMAL,0,0,0,0,0,0,0,0, GetFont() );
	CFont* oldfont = dc->SelectObject( &font );

	/////////////////////////////////////////////////////////////
	// Draw title

	CString str = GetTitle();
	if( str.GetLength() )
	{

		CRect rectTemp( rect );
		rectTemp.NormalizeRect();
		CRect r( rect.right - cut, rect.top, rect.right - ( rectTemp.Width() + cut ), rect.bottom );
		if( IsHorizontal() )
		{
			CRect r( rect.left, rect.top - ( cy + cut ), rect.right, rect.bottom );
			r.NormalizeRect();
			dc->DrawText( str, r, DT_NOPREFIX | DT_SINGLELINE | DT_CENTER );
		}
		else
		{
			CRect r( rect.right - ( dc->GetTextExtent( str ).cx + cut * 2 ), rect.top, rect.right - cut, rect.bottom );
			r.NormalizeRect();
			dc->DrawText( str, r, DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER | DT_RIGHT );
		}

	}

	/////////////////////////////////////////////////////////////
	// Draw labels

	str = GetStartLabel();
	if( str.GetLength() )
	{
		CRect rectTemp( rect );
		if( IsHorizontal() )
		{
			rectTemp.bottom = rectTemp.top - cut;
			rectTemp.top -= cy + cut;

			if( rectTemp.left < rectTemp.right )
			{
				rectTemp.left += cut;
				rectTemp.right -= cut;
				dc->DrawText( str, rectTemp, DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER | DT_LEFT );
			}
			else
			{
				int temp = rectTemp.left;
				rectTemp.left = rectTemp.right + cut;
				rectTemp.right = temp - cut;
				dc->DrawText( str, rectTemp, DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER | DT_RIGHT );
			}

		}
		else
		{
			rectTemp.left -= dc->GetTextExtent( str ).cx + 2 * cut;
			rectTemp.right -= cut;
			if( rectTemp.top < rectTemp.bottom )
				rectTemp.bottom = rectTemp.top + cy;
			else
			{
				rectTemp.top -= cy;
				rectTemp.bottom = rectTemp.top + cy;
			}

			dc->DrawText( str, rectTemp, DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER | DT_RIGHT );
		}

	}

	str = GetSecondaryStartLabel();
	if( str.GetLength() )
	{
		CRect rectTemp( rect );
		if( IsHorizontal() )
		{
			rectTemp.bottom += cy + cut;
			rectTemp.top += cut;

			if( rectTemp.left < rectTemp.right )
			{
				rectTemp.left += cut;
				rectTemp.right -= cut;
				dc->DrawText( str, rectTemp, DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER | DT_LEFT );
			}
			else
			{
				int temp = rectTemp.left;
				rectTemp.left = rectTemp.right + cut;
				rectTemp.right = temp - cut;
				dc->DrawText( str, rectTemp, DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER | DT_RIGHT );
			}

		}
		else
		{
			rectTemp.right += dc->GetTextExtent( str ).cx + 2 * cut;
			rectTemp.left += cut;

			if( rectTemp.top < rectTemp.bottom )
				rectTemp.bottom = rectTemp.top + cy;
			else
			{
				rectTemp.top -= cy;
				rectTemp.bottom = rectTemp.top + cy;
			}

			dc->DrawText( str, rectTemp, DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER | DT_RIGHT );
		}

	}

	str = GetEndLabel();
	if( str.GetLength() )
	{
		CRect rectTemp( rect );
		if( IsHorizontal() )
		{
			rectTemp.bottom = rectTemp.top - cut;
			rectTemp.top -= cy + cut;

			if( rectTemp.left < rectTemp.right )
			{
				rectTemp.left += cut;
				rectTemp.right -= cut;
				dc->DrawText( str, rectTemp, DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER | DT_RIGHT );
			}
			else
			{
				int temp = rectTemp.left;
				rectTemp.left = rectTemp.right + cut;
				rectTemp.right = temp - cut;
				dc->DrawText( str, rectTemp, DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER | DT_LEFT );
			}

		}
		else
		{
			rectTemp.left -= dc->GetTextExtent( str ).cx + 2 * cut;
			rectTemp.right -= cut;
			if( rectTemp.top < rectTemp.bottom )
				rectTemp.top = rectTemp.bottom - cy;
			else
			{
				rectTemp.top = rectTemp.bottom;
				rectTemp.bottom = rectTemp.top + cy;
			}
			dc->DrawText( str, rectTemp, DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER | DT_RIGHT );
		}
	}

	str = GetSecondaryEndLabel();
	if( str.GetLength() )
	{
		CRect rectTemp( rect );
		if( IsHorizontal() )
		{
			rectTemp.bottom += cy + cut;
			rectTemp.top += cut;

			if( rectTemp.left < rectTemp.right )
			{
				rectTemp.left += cut;
				rectTemp.right -= cut;
				dc->DrawText( str, rectTemp, DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER | DT_RIGHT );
			}
			else
			{
				int temp = rectTemp.left;
				rectTemp.left = rectTemp.right + cut;
				rectTemp.right = temp - cut;
				dc->DrawText( str, rectTemp, DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER | DT_LEFT );
			}

		}
		else
		{
			rectTemp.right += dc->GetTextExtent( str ).cx + 2 * cut;
			rectTemp.left += cut;
			if( rectTemp.top < rectTemp.bottom )
				rectTemp.top = rectTemp.bottom - cy;
			else
			{
				rectTemp.top = rectTemp.bottom;
				rectTemp.bottom = rectTemp.top + cy;
			}
			dc->DrawText( str, rectTemp, DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER | DT_RIGHT );
		}
	}

	/////////////////////////////////////////////////////////////
	// Draw markers

	int hgt = GetMarkerSize().cy / 2;
	if( GetStyle() & STYLE_FILLED_DIAMOND )
	{
		CRect diamond;

		double x2 = GetLeft();
		double x1 = GetRight();
		double y2 = GetTop();
		double y1 = GetBottom();

		if( !( GetLinkType( LINK_END ) & LINK_ALL ) && 
			( GetLinkType( LINK_START ) & LINK_ALL ) )
		{
			x2 = GetRight();
			x1 = GetLeft();
			y2 = GetBottom();
			y1 = GetTop();
		}

		if( IsHorizontal() )
		{
			diamond.left = round( x1 * GetZoom() );
			diamond.top = round( ( y1 - hgt ) * GetZoom() );
			diamond.bottom = diamond.top + round( hgt * 2 * GetZoom() );
			if( x1 < x2 )
				diamond.right = diamond.left + round( hgt * 4 * GetZoom() );
			else
				diamond.right = diamond.left - round( hgt * 4 * GetZoom() );

			diamond.NormalizeRect();

		}
		else
		{

			diamond.top = round( y1 * GetZoom() );
			diamond.left = round( ( x1 - hgt ) * GetZoom() );
			diamond.right = diamond.left + round( hgt * 2 * GetZoom() );
			if( y1 < y2 )
				diamond.bottom = diamond.top + round( hgt * 4 * GetZoom() );
			else
				diamond.bottom = diamond.top - round( hgt * 4 * GetZoom() );

			rect.NormalizeRect();

		}

		DrawDiamond( dc, diamond );
	}

	if( GetStyle() & STYLE_CIRCLECROSS )
	{

		CSize marker( 16, 16 );
		CPoint pos = GetStyleMarkerRect( LINK_END, marker );
		CRect circ( round( static_cast< double >( pos.x ) * GetZoom() ), round( static_cast< double >( pos.y ) * GetZoom() ), round( static_cast< double >( pos.x + marker.cx ) * GetZoom() ), round( static_cast< double >( pos.y + marker.cy ) * GetZoom() ) );

		dc->SelectStockObject( BLACK_PEN );
		dc->SelectStockObject( WHITE_BRUSH );

		dc->Ellipse( &circ );

		dc->MoveTo( circ.left + circ.Width() / 2, circ.top );
		dc->LineTo( circ.left + circ.Width() / 2, circ.bottom );

		dc->MoveTo( circ.left, circ.top + circ.Height() / 2 );
		dc->LineTo( circ.right, circ.top + circ.Height() / 2 );

	}

	if( GetStyle() & STYLE_ARROWHEAD )
		DrawInheritanceArrow( dc );

	if( GetStyle() & STYLE_FILLED_ARROWHEAD )
		DrawDirectionArrow( dc );

	/////////////////////////////////////////////////////////////
	// Cleaning up

	dc->SetBkMode( mode );
	dc->SelectObject( oldfont );
	dc->SelectStockObject( WHITE_BRUSH );

}

CDiagramEntity* CUMLLineSegment::CreateFromString( const CString& str )
/* ============================================================
	Function :		CUMLLineSegment::CreateFromString
	Description :	Static factory function that creates and 
					returns an instance of this class if "str" 
					is a valid representation.
	Access :		Public
					
	Return :		CDiagramEntity*		-	The object, or "NULL" 
											if "str" is not a 
											representation of 
											this type.
	Parameters :	const CString& str	-	The string to create 
											from.
					
	Usage :			Can be used as a factory for text file loads. 
					Each object type should have its own 
					version - the default one is a model 
					implementation.

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

	CUMLLineSegment* obj = new CUMLLineSegment;
	if(!obj->FromString( str ) )
	{
		delete obj;
		obj = NULL;
	}

	return obj;

}

int CUMLLineSegment::GetHitCode( CPoint point ) const
/* ============================================================
	Function :		CUMLLineSegment::GetHitCode
	Description :	Returns the hit point constant for point.
	Access :		Public
					
	Return :		int				-	The hit point, 
										"DEHT_NONE" if none.
	Parameters :	CPoint point	-	The point to check
					
	Usage :			Call to see in what part of the object point 
					lies. The hit point can be one of the following:
						"DEHT_NONE" No hit-point
						"DEHT_BODY" Inside object body
						"DEHT_TOPLEFT" Top-left corner
						"DEHT_TOPMIDDLE" Middle top-side
						"DEHT_TOPRIGHT" Top-right corner
						"DEHT_BOTTOMLEFT" Bottom-left corner
						"DEHT_BOTTOMMIDDLE" Middle bottom-side
						"DEHT_BOTTOMRIGHT" Bottom-right corner
						"DEHT_LEFTMIDDLE" Middle left-side
						"DEHT_RIGHTMIDDLE" Middle right-side

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

	int result = DEHT_NONE;

	CRect rect = GetRect();

	hitParams hit;
	hit.hit = FALSE;
	hit.x = point.x;
	hit.y = point.y;

	LineDDA(	static_cast< int >( GetLeft() ), 
				static_cast< int >( GetTop() ), 
				static_cast< int >( GetRight() ), 
				static_cast< int >( GetBottom() ), 
				HitTest, 
				(LPARAM) &hit );

	if( hit.hit )
		result = DEHT_BODY;

	CRect rectTest;

	rectTest = GetSelectionMarkerRect( DEHT_TOPLEFT, rect );
	if( rectTest.PtInRect( point ) )
		result = DEHT_TOPLEFT;

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

	return result;

}

HCURSOR CUMLLineSegment::GetCursor( int hit ) const
/* ============================================================
	Function :		CUMLLineSegment::GetCursor
	Description :	Returns the cursor for the given hit point.
	Access :		Public
					
	Return :		HCURSOR	-	The cursor to show
	Parameters :	int hit	-	The hit point constant ("DEHT_") 
								to get the cursor for.

	Usage :			Call to get the cursor for a specific hit 
					point constant.
					"hit" can be one of the following:
						"DEHT_NONE" No hit-point
						"DEHT_BODY" Inside object body
						"DEHT_TOPLEFT" Top-left corner
						"DEHT_TOPMIDDLE" Middle top-side
						"DEHT_TOPRIGHT" Top-right corner
						"DEHT_BOTTOMLEFT" Bottom-left corner
						"DEHT_BOTTOMMIDDLE" Middle bottom-side
						"DEHT_BOTTOMRIGHT" Bottom-right corner
						"DEHT_LEFTMIDDLE" Middle left-side
						"DEHT_RIGHTMIDDLE" Middle right-side

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

	HCURSOR cursor = NULL;
	switch( hit )
	{
		case DEHT_BODY:
		case DEHT_TOPLEFT:
		case DEHT_BOTTOMRIGHT:
		cursor = LoadCursor( NULL, IDC_SIZEALL );
		break;
	}

	return cursor;

}

void CUMLLineSegment::DrawSelectionMarkers( CDC* dc, CRect rect ) const
/* ============================================================
	Function :		CUMLLineSegment::DrawSelectionMarkers
	Description :	Draws the selection markers for the 
					object.
	Access :		Public
					
	Return :		void
	Parameters :	CDC* dc		-	The "CDC" to draw to
					CRect rect	-	The real object rectangle.
					
	Usage :			"rect" is the true rectangle (zoomed) of the 
					object.

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

	CRect rectSelect;

	dc->SelectStockObject( BLACK_PEN );
	dc->SelectStockObject( BLACK_BRUSH );

	rectSelect = GetSelectionMarkerRect( DEHT_TOPLEFT, rect );
	dc->Rectangle( rectSelect );

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


}

void CUMLLineSegment::SetRect( CRect rect )
/* ============================================================
	Function :		CUMLLineSegment::SetRect
	Description :	Sets the rect of the object.
	Access :		Public
					
	Return :		void
	Parameters :	CRect rect	-	Rectangle to set
					
	Usage :			Overriden to avoid normalization and force 
					the line to be orthogonal.

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

	if( GetLeft() - GetRight() != 0 || GetTop() - GetBottom() != 0 )
	{
		if( IsHorizontal() )
		{
			if( rect.top == static_cast< int >( GetTop() ) )
				rect.bottom = rect.top;
			else
				rect.top = rect.bottom;
		}
		else
		{
			if( rect.left == static_cast< int >( GetLeft() ) )
				rect.right = rect.left;
			else
				rect.left = rect.right;
		}
	}

	SetLeft( rect.left );
	SetTop( rect.top );
	SetRight( rect.right );
	SetBottom( rect.bottom );

}

BOOL CUMLLineSegment::BodyInRect( CRect rect ) const
/* ============================================================
	Function :		CUMLLineSegment::BodyInRect
	Description :	Used to see if any part of the object lies 
					in rect.
	Access :		Public
					
	Return :		BOOL		-	"TRUE" if any part of the 
									object lies inside rect.
	Parameters :	CRect rect	-	The rect to check.
					
	Usage :			Call to see if the object overlaps - for 
					example - a selection rubberband.

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

	BOOL result = FALSE;
	hitParamsRect hit;
	hit.rect = rect;
	hit.hit = FALSE;

	LineDDA(	static_cast< int >( GetLeft() ), 
				static_cast< int >( GetTop() ), 
				static_cast< int >( GetRight() ), 
				static_cast< int >( GetBottom() ), 
				HitTestRect, 
				( LPARAM ) &hit );

	if( hit.hit )
		result = TRUE;

	return result;

}

CPoint CUMLLineSegment::GetLinkPosition( int type ) const
/* ============================================================
	Function :		CUMLLineSegment::GetLinkPosition
	Description :	Returns the position of a link.
	Access :		Public
					
	Return :		CPoint		-	The position of the link,
									-1, -1 if the link is not
									allowed.
	Parameters :	int type	-	The type of the link.
					
	Usage :			The possible link types are:
					"LINK_START" Links are allowed to the start of a line (the top-left corner).
					"LINK_END" Links are allowed to the end of a line (the bottom-right corner).

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

	CPoint point( -1, -1 );
	CRect rect = GetRect();

	switch( type )
	{
		case LINK_START:
			point.x = rect.left;
			point.y = rect.top;
			break;
		case LINK_END:
			point.x = rect.right;
			point.y = rect.bottom;
			break;
	}

	return point;

}

void CUMLLineSegment::SetStyle( int style )
/* ============================================================
	Function :		CUMLLineSegment::SetStyle
	Description :	Sets the style for this object.
	Access :		Public

	Return :		void
	Parameters :	int style	-	New style.
					
	Usage :			"style" can be one of the following:
						"STYLE_NONE" Black line
						"STYLE_ARROWHEAD" White arrowhead
						"STYLE_FILLED_ARROWHEAD" Black arrowhead
						"STYLE_CIRCLECROSS" A circle with a cross
						"STYLE_FILLED_DIAMOND" Black diamond
						"STYLE_DASHED" Dashed line
						"STYLE_INVISIBLE" Invisible line

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

	m_style = style;

}

void CUMLLineSegment::SetLineStyle( int style )
/* ============================================================
	Function :		CUMLLineSegment::SetLineStyle
	Description :	Clears the line style and sets "style" to 
					the complete line.
	Access :		Public
					
	Return :		void
	Parameters :	int style	-	Style to set
					
	Usage :			Call to set a style to the complete line.
					"style" can be one of the following:
						"STYLE_NONE" Black line
						"STYLE_ARROWHEAD" White arrowhead
						"STYLE_FILLED_ARROWHEAD" Black arrowhead
						"STYLE_CIRCLECROSS" A circle with a cross
						"STYLE_FILLED_DIAMOND" Black diamond
						"STYLE_DASHED" Dashed line
						"STYLE_INVISIBLE" Invisible line

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

	CUMLEntityContainer* objs = GetUMLContainer();
	if( objs )
	{

		CUMLLineSegment* start = objs->GetStartSegment( this );
		while( start )
		{
			start->m_style = STYLE_NONE;
			start = objs->GetNextSegment( start );
		}

		AddLineStyle( style );

	}
	else
		m_style = style;

}

void CUMLLineSegment::AddLineStyle( int style )
/* ============================================================
	Function :		CUMLLineSegment::AddLineStyle
	Description :	Adds a style to the line.
	Access :		Public
					
	Return :		void
	Parameters :	int style	-	Style to add.
					
	Usage :			Adds the style to the complete line or the 
					appropriate start or end segment.
					"style" can be one of the following:
						"STYLE_NONE" Black line
						"STYLE_ARROWHEAD" White arrowhead
						"STYLE_FILLED_ARROWHEAD" Black arrowhead
						"STYLE_CIRCLECROSS" A circle with a cross
						"STYLE_FILLED_DIAMOND" Black diamond
						"STYLE_DASHED" Dashed line
						"STYLE_INVISIBLE" Invisible line
   ============================================================*/
{

	CUMLEntityContainer* objs = GetUMLContainer();
	if( objs )
	{

		if( style & STYLE_FILLED_ARROWHEAD )
		{
			CUMLLineSegment* start = objs->GetStartSegment( this );
			start->m_style |= style;
		}

		if( style & STYLE_FILLED_DIAMOND || 
			style & STYLE_ARROWHEAD ||
			style & STYLE_CIRCLECROSS )
		{
			CUMLLineSegment* end = objs->GetEndSegment( this );
			end->m_style |= style;
		}

		if( style & STYLE_DASHED || style & STYLE_INVISIBLE )
		{
			CUMLLineSegment* start = objs->GetStartSegment( this );
			while( start )
			{
				start->m_style |= style;
				start = objs->GetNextSegment( start );
			}
		}
	}
	else
		m_style |= style;

}

void CUMLLineSegment::RemoveLineStyle( int style )
/* ============================================================
	Function :		CUMLLineSegment::RemoveLineStyle
	Description :	Removes a style from this and all other 
					segments of the line.
	Access :		Public
					
	Return :		void
	Parameters :	int style	-	Style to remove
					
	Usage :			Call to remove a style from the complete line.
					"style" can be one of the following:
						"STYLE_NONE" Black line
						"STYLE_ARROWHEAD" White arrowhead
						"STYLE_FILLED_ARROWHEAD" Black arrowhead
						"STYLE_CIRCLECROSS" A circle with a cross
						"STYLE_FILLED_DIAMOND" Black diamond
						"STYLE_DASHED" Dashed line
						"STYLE_INVISIBLE" Invisible line
   ============================================================*/
{

	CUMLEntityContainer* objs = GetUMLContainer();
	if( objs )
	{
		if( style & STYLE_FILLED_DIAMOND ||
			style & STYLE_FILLED_ARROWHEAD )
		{
			CUMLLineSegment* start = objs->GetStartSegment( this );
			start->m_style &= ~style;
		}
		if( style & STYLE_ARROWHEAD ||
			style & STYLE_CIRCLECROSS )
		{
			CUMLLineSegment* end = objs->GetEndSegment( this );
			end->m_style &= ~style;
		}

		if( style & STYLE_DASHED || style & STYLE_INVISIBLE )
		{
			CUMLLineSegment* start = objs->GetStartSegment( this );
			while( start )
			{
				start->m_style &= ~style;
				start = objs->GetNextSegment( start );
			}
		}
	}
	else
		m_style &= ~style;

}

int CUMLLineSegment::GetStyle() const
/* ============================================================
	Function :		CUMLLineSegment::GetStyle
	Description :	Gets the style of the line segment
	Access :		Public

	Return :		int	-	Style
	Parameters :	none

	Usage :			"style" can be one of the following:
						"STYLE_NONE" Black line
						"STYLE_ARROWHEAD" White arrowhead
						"STYLE_FILLED_ARROWHEAD" Black arrowhead
						"STYLE_CIRCLECROSS" A circle with a cross
						"STYLE_FILLED_DIAMOND" Black diamond
						"STYLE_DASHED" Dashed line
						"STYLE_INVISIBLE" Invisible line

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

	return m_style;

}

BOOL CUMLLineSegment::FromString( const CString& str )
/* ============================================================
	Function :		CUMLLineSegment::FromString
	Description :	Sets the data fro this object from "str".
	Access :		Public
					
	Return :		BOOL				-	"TRUE" if str represents 
											an object of this type.
	Parameters :	const CString& str	-	String representation
					
	Usage :			Called from the load-functionality.

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

	BOOL result = FALSE;
	CString data( str );

	if( LoadFromString( data ) )
	{

		CTokenizer tok( data );

		CString package;
		CString stereotype;

		CString startLink;
		CString endLink;

		int startLinkType;
		int endLinkType;

		CString	fontName;
		int		bkColor;

		int style;
		CString startLabel;
		CString endLabel;
		CString secondaryStartLabel;
		CString secondaryEndLabel;

		int startOffset;
		int endOffset;

		int count = 0;

		tok.GetAt( count++, package );
		tok.GetAt( count++, stereotype );
		tok.GetAt( count++, startLink );
		tok.GetAt( count++, endLink );
		tok.GetAt( count++, startLinkType );
		tok.GetAt( count++, endLinkType );
		tok.GetAt( count++, style );
		tok.GetAt( count++, fontName );
		tok.GetAt( count++, bkColor );
		tok.GetAt( count++, startLabel );
		tok.GetAt( count++, endLabel );
		tok.GetAt( count++, startOffset );
		tok.GetAt( count++, endOffset );
		tok.GetAt( count++, secondaryStartLabel );
		tok.GetAt( count++, secondaryEndLabel );

		UnmakeSaveString( startLabel );
		UnmakeSaveString( endLabel );
		UnmakeSaveString( secondaryStartLabel );
		UnmakeSaveString( secondaryEndLabel );
		UnmakeSaveString( package );
		UnmakeSaveString( stereotype );

		SetPackage( package );
		SetStereotype( stereotype );
		SetLink( LINK_START, startLink );
		SetLink( LINK_END, endLink );
		SetLinkType( LINK_START, startLinkType );
		SetLinkType( LINK_END, endLinkType );
		SetStyle( style );
		SetFont( fontName );
		SetBkColor( static_cast< COLORREF >( bkColor ) );
		SetStartLabel( startLabel );
		SetEndLabel( endLabel );
		SetOffset( LINK_START, startOffset );
		SetOffset( LINK_END, endOffset );
		SetSecondaryStartLabel( secondaryStartLabel );
		SetSecondaryEndLabel( secondaryEndLabel );

		result = TRUE;

	}

	return result;

}

CString CUMLLineSegment::GetString() const
/* ============================================================
	Function :		CUMLLineSegment::GetString
	Description :	Get the object as a string for saving.
	Access :		Public

	Return :		CString	-	Object as a string
	Parameters :	none

	Usage :			Called from the save functionality.

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

	CString str;

	CString package = GetPackage();
	CString stereotype = GetStereotype();
	CString startLabel1 = GetStartLabel();
	CString startLabel2 = GetSecondaryStartLabel();
	CString endLabel1 = GetEndLabel();
	CString endLabel2 = GetSecondaryEndLabel();

	MakeSaveString( package );
	MakeSaveString( stereotype );
	MakeSaveString( startLabel1 );
	MakeSaveString( startLabel2 );
	MakeSaveString( endLabel1 );
	MakeSaveString( endLabel2 );

	str.Format( _T( ",%s,%s,%s,%s,%i,%i,%i,%s,%i,%s,%s,%i,%i,%s,%s;" ), 
			package,
			stereotype,
			GetLink( LINK_START ), 
			GetLink( LINK_END ), 
			GetLinkType( LINK_START ), 
			GetLinkType( LINK_END ),
			GetStyle(),
			GetFont(),
			static_cast< int >( GetBkColor() ),
			startLabel1,
			endLabel1,
			GetOffset( LINK_START ),
			GetOffset( LINK_END ),
			startLabel2,
			endLabel2
		);

	str = GetDefaultGetString() + str;
	return str;

}

void CUMLLineSegment::DrawDiamond( CDC* dc, const CRect& rect )
/* ============================================================
	Function :		CUMLLineSegment::DrawDiamond
	Description :	Draws the composition diamond.
	Access :		Private
					
	Return :		void
	Parameters :	CDC* dc				-	The "CDC" to draw to
					const CRect& rect	-	The rect of the diamond
					
	Usage :			Called from "Draw".

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

	dc->SelectStockObject( BLACK_PEN );
	dc->SelectStockObject( BLACK_BRUSH );

	POINT pts[4];
	pts[0].x = rect.left + ( ( rect.right - rect.left ) / 2 );
	pts[0].y = rect.top;
	pts[1].x = rect.right;
	pts[1].y = rect.top + ( ( rect.bottom - rect.top ) / 2 );
	pts[2].x = pts[0].x;
	pts[2].y = rect.bottom;
	pts[3].x = rect.left;
	pts[3].y = pts[1].y;

	dc->Polygon( pts, 4 );

}

CString CUMLLineSegment::GetStartLabel() const
/* ============================================================
	Function :		CUMLLineSegment::SetSecondaryEndLabel
	Description :	Gets the start label.
	Access :		Public
					
	Return :		void
	Parameters :	const CString& label	-	The label
					
	Usage :			Gets the top or left label at the line
					start.

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

	return m_startLabel;

}

CString CUMLLineSegment::GetEndLabel() const
/* ============================================================
	Function :		CUMLLineSegment::SetSecondaryEndLabel
	Description :	Gets the end label.
	Access :		Public
					
	Return :		void
	Parameters :	const CString& label	-	The label
					
	Usage :			Gets the top or left label at the line
					end. 

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

	return m_endLabel;

}

CString CUMLLineSegment::GetSecondaryStartLabel() const
/* ============================================================
	Function :		CUMLLineSegment::SetSecondaryEndLabel
	Description :	Gets the secondary start label.
	Access :		Public
					
	Return :		void
	Parameters :	const CString& label	-	The label
					
	Usage :			Gets the bottom or right label at the line
					start. 

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

	return m_secondaryStartLabel;

}

CString CUMLLineSegment::GetSecondaryEndLabel() const
/* ============================================================
	Function :		CUMLLineSegment::SetSecondaryEndLabel
	Description :	Gets the secondary end label.
	Access :		Public
					
	Return :		void
	Parameters :	const CString& label	-	The label
					
	Usage :			Gets the bottom or right label at the line
					end. 

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

	return m_secondaryEndLabel;

}

void CUMLLineSegment::SetStartLabel( const CString& label )
/* ============================================================
	Function :		CUMLLineSegment::SetSecondaryEndLabel
	Description :	Sets the start label.
	Access :		Public
					
	Return :		void
	Parameters :	const CString& label	-	The label
					
	Usage :			Sets the top or left label at the line
					start. 

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

	m_startLabel = label;

}

void CUMLLineSegment::SetEndLabel( const CString& label )
/* ============================================================
	Function :		CUMLLineSegment::SetSecondaryEndLabel
	Description :	Sets the end label.
	Access :		Public
					
	Return :		void
	Parameters :	const CString& label	-	The label
					
	Usage :			Sets the top or left label at the line
					end. 

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

	m_endLabel = label;

}

void CUMLLineSegment::SetSecondaryStartLabel( const CString& label )
/* ============================================================
	Function :		CUMLLineSegment::SetSecondaryEndLabel
	Description :	Sets the secondary start label.
	Access :		Public
					
	Return :		void
	Parameters :	const CString& label	-	The label
					
	Usage :			Sets the bottom or right label at the line
					start. 

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

	m_secondaryStartLabel = label;

}

void CUMLLineSegment::SetSecondaryEndLabel( const CString& label )
/* ============================================================
	Function :		CUMLLineSegment::SetSecondaryEndLabel
	Description :	Sets the secondary end label.
	Access :		Public
					
	Return :		void
	Parameters :	const CString& label	-	The label
					
	Usage :			Sets the bottom or right label at the line
					end. 

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

	m_secondaryEndLabel = label;

}

BOOL CUMLLineSegment::IsHorizontal() const
/* ============================================================
	Function :		CUMLLineSegment::IsHorizontal
	Description :	Check if the line is horizontal
	Access :		Public
					
	Return :		BOOL	-	"TRUE" if horizontal
	Parameters :	none

	Usage :			Call to see if the segment is horizontal.

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

	return ( GetTop() == GetBottom() );

}

void CUMLLineSegment::SetRect( double left, double top, double right, double bottom )
/* ============================================================
	Function :		CUMLLineSegment::SetRect
	Description :	Sets the object rectangle.
	Access :		Public
					
	Return :		void
	Parameters :	double left		-	Left pos
					double top		-	Top pos
					double right	-	Right pos
					double bottom	-	Bottom pos.
					
	Usage :			Overridden to force the line to be 
					orthogonal.

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

	if( GetLeft() - GetRight() != 0 || GetTop() - GetBottom() != 0 )
	{
		if( IsHorizontal() )
		{
			if( top == GetTop() )
				bottom = top;
			else
				top = bottom;
		}
		else
		{
			if( left == GetLeft() )
				right = left;
			else
				left = right;
		}
	}

	SetTop( top );
	SetLeft( left );
	SetRight( right );
	SetBottom( bottom );

}

void CUMLLineSegment::Copy( CDiagramEntity* obj )
/* ============================================================
	Function :		CUMLLineSegment::Copy
	Description :	Copies from obj to this
	Access :		Public
					
	Return :		void
	Parameters :	CDiagramEntity* obj	-	Object to copy from
					
	Usage :			Overridden to copy the rest of the attributes.

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

	CUMLEntity::Copy( obj );

	CUMLLineSegment* uml = dynamic_cast< CUMLLineSegment* >( obj );
	if( uml )
	{

		SetStyle( uml->GetStyle() );
		SetStartLabel( uml->GetStartLabel() );
		SetEndLabel( uml->GetEndLabel() );
		SetOffset( LINK_START, uml->GetOffset( LINK_START ) );
		SetOffset( LINK_END, uml->GetOffset( LINK_END ) );
		SetLink( LINK_START, uml->GetLink( LINK_START ) );
		SetLink( LINK_END, uml->GetLink( LINK_END ) );
		SetLinkType( LINK_START, uml->GetLinkType( LINK_START ) );
		SetLinkType( LINK_END, uml->GetLinkType( LINK_END ) );

	}

}

int CUMLLineSegment::GetLineStyle() const
/* ============================================================
	Function :		CUMLLineSegment::GetLineStyle
	Description :	Returns the style for this and all the 
					other segments making up this line.
	Access :		Public
					
	Return :		int		-	The line style.
	Parameters :	none

	Usage :			Call to get the line style. The line style can be ORed from the following:
						"STYLE_NONE" No special style
						"STYLE_ARROWHEAD" Inheritance (empty arrowhead)
						"STYLE_FILLED_ARROWHEAD" Direction (filled arrowhead)
						"STYLE_CIRCLECROSS" Part-of circle (circle with cross)
						"STYLE_FILLED_DIAMOND" Composition (filled diamond)
						"STYLE_DASHED" Dependency (dashed line)
						"STYLE_INVISIBLE" Invisible (to connect objects)


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

	int result = m_style;
	CUMLEntityContainer* objs = GetUMLContainer();
	if( objs )
	{

		CUMLLineSegment* start = objs->GetStartSegment( ( CUMLLineSegment *const ) this );
		CUMLLineSegment* end = objs->GetEndSegment( ( CUMLLineSegment *const ) this );

		result |= start->GetStyle();
		result |= end->GetStyle();

	}

	return result;

}

void CUMLLineSegment::SetLink( int type, const CString& name )
/* ============================================================
	Function :		CUMLLineSegment::SetLink
	Description :	Set that name of the object that is 
					attached to the start or end of this line 
					segment.
	Access :		Public
					
	Return :		void
	Parameters :	int type			-	"LINK_START" or "LINK_END"
					const CString& name	-	Name of object
					
	Usage :			Call to attach an object to this line segment.

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

	switch( type )
	{
		case LINK_START:
			m_start = name;
			break;
		case LINK_END:
			m_end = name;
			break;
	}

}

CString CUMLLineSegment::GetLink( int type ) const
/* ============================================================
	Function :		CUMLLineSegment::GetLink
	Description :	Get the name of the object attached to the 
					start or end of this line segment.
	Access :		Public
					
	Return :		CString		-	Name of object, or empty if 
									none.
	Parameters :	int type	-	"LINK_START" or "LINK_END"
					
	Usage :			Call to get the name of the object attached 
					to either end of the line segment.

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

	CString result;

	switch( type )
	{
		case LINK_START:
			result = m_start;
			break;
		case LINK_END:
			result = m_end;
			break;
	}

	return result;

}

void CUMLLineSegment::SetLinkType( int type, int targetType )
/* ============================================================
	Function :		CUMLLineSegment::SetLinkType
	Description :	Set the link type for the start or end of 
					the line segment.
	Access :		Public
					
	Return :		void
	Parameters :	int type		-	"LINK_START" or "LINK_END"
					int targetType	-	The type of the link on 
										the object this line 
										segment will be attached 
										to.
					
	Usage :			Call to set a new link type for the line 
					segment start or end. The possible link 
					types for "targetType" are:
						"LINK_TOP" Link at the top
						"LINK_BOTTOM" Link at the bottom
						"LINK_LEFT" Link at the left
						"LINK_RIGHT" Link at the right

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

	switch( type )
	{
		case LINK_START:
			m_startType = targetType;
			break;
		case LINK_END:
			m_endType = targetType;
			break;
	}

}

int CUMLLineSegment::GetLinkType( int type ) const
/* ============================================================
	Function :		CUMLLineSegment::GetLinkType
	Description :	Returns the type of link the start or end 
					point is attached to.
	Access :		Public
					
	Return :		int			-	Any of the "LINK_"-constants
	Parameters :	int type	-	"LINK_START" or "LINK_END" for 
									the start or end point 
									respectively.
					
	Usage :			Call to get the type of link the start or 
					end of the line is attached to.The possible 
					link types are:
						"LINK_TOP" Link at the top
						"LINK_BOTTOM" Link at the bottom
						"LINK_LEFT" Link at the left
						"LINK_RIGHT" Link at the right
						"LINK_START" Link at the start point of a line
						"LINK_END" Link at the end point of a line.

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

	int result = LINK_NONE;

	switch( type )
	{
		case LINK_START:
			result = m_startType;
			break;
		case LINK_END:
			result = m_endType;
			break;
	}

	return result;

}

int CUMLLineSegment::GetOffset( int type) const
/* ============================================================
	Function :		CUMLLineSegment::GetOffset
	Description :	Returns the offset for a line end point.
	Access :		Public
					
	Return :		int			-	Offset to the top or left.
	Parameters :	int type	-	Type of point - "LINK_START" 
									or "LINK_END" for the start 
									and end respectively.
					
	Usage :			As lines can be attached anywhere on an 
					object side, the offset from the top or left 
					side is saved for the two end points.

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

	int result;

	if( type == LINK_START )
		result = m_startOffset;
	else
		result = m_endOffset;

	return result;

}

void CUMLLineSegment::SetOffset( int type, int linkOffset )
/* ============================================================
	Function :		CUMLLineSegment::SetOffset
	Description :	Sets the offset to the x- or y-position on 
					the object this line is linked to.
	Access :		Public
					
	Return :		void
	Parameters :	int type		-	"LINK_START" or "LINK_END" 
										for the appropriate end 
										point of the line.
					int linkOffset	-	Offset on the side (from 
										the top or left).
					
	Usage :			As lines can be attached anywhere on an 
					object side, the offset from the top or left 
					side is saved for the two end points.

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

	if( type == LINK_START )
		m_startOffset = linkOffset;
	else
		m_endOffset = linkOffset;

}

int CUMLLineSegment::GetLinkCode( CPoint point ) const
/* ============================================================
	Function :		CUMLLineSegment::GetLinkCode
	Description :	Get the link code for point.
	Access :		Public
					
	Return :		int				-	Link code
	Parameters :	CPoint point	-	The point to check
					
	Usage :			Call to hit-test the object. The function 
					returns "LINK_START" for the start of the 
					line, "LINK_END" for the end.

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

	int result = LINK_NONE;

	CRect rectTest;
	rectTest = GetLinkMarkerRect( LINK_START );
	if( rectTest.PtInRect( point ) )
		result = LINK_START;

	rectTest = GetLinkMarkerRect( LINK_END );
	if( rectTest.PtInRect( point ) )
		result = LINK_END;

	return result;

}

CString CUMLLineSegment::Export( UINT format ) const
/* ============================================================
	Function :		CUMLLineSegment::Export
	Description :	Exports this object to the desired format.
	Access :		Public
					
	Return :		CString		-	Result
	Parameters :	UINT format	-	Format to export to
					
	Usage :			Called from the editor/container to export 
					objects. "format" can be one of the following:
						"EXPORT_CPP" Export to cpp-files
						"EXPORT_H" Export to header files
						"EXPORT_HTML" Export to HTML-files

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

	CString result;

	switch( format )
	{
		case EXPORT_HTML:
			result = ExportHTML();
			break;
		case EXPORT_CPP:
			break;
		case EXPORT_H:
			break;
	}

	return result;

}

void CUMLLineSegment::Flip()
/* ============================================================
	Function :		CUMLLineSegment::Flip
	Description :	Flip the direction of the link
	Access :		Public
					
	Return :		void
	Parameters :	none

	Usage :			Called from flip-commands in the editor/
					container.

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

	CString tempString;
	int tempInt;

	tempString = GetLink( LINK_END );
	SetLink( LINK_END, GetLink( LINK_START ) );
	SetLink( LINK_START, tempString );

	tempInt = GetLinkType( LINK_END );
	SetLinkType( LINK_END, GetLinkType( LINK_START ) );
	SetLinkType( LINK_START, tempInt );

	tempInt = GetOffset( LINK_END );
	SetOffset( LINK_END, GetOffset( LINK_START ) );
	SetOffset( LINK_START, tempInt );

	CRect rect = GetRect();
	tempInt = rect.right;
	rect.right = rect.left;
	rect.left = tempInt;

	tempInt = rect.bottom;
	rect.bottom = rect.top;
	rect.top = tempInt;

	SetRect( rect );

}

BOOL CUMLLineSegment::IsSingleLineSegment() const
/* ============================================================
	Function :		CUMLLineSegment::IsSingleLineSegment
	Description :	Check if the line is a single-segment line
	Access :		Public
					
	Return :		BOOL	-	"TRUE" if single-segment
	Parameters :	none

	Usage :			Call to see if this line consists of more 
					segments.

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

	BOOL result = FALSE;

	if( ( GetLinkType( LINK_START ) & LINK_ALL ) && 
		( GetLinkType( LINK_END ) & LINK_ALL ) ) 
		result = TRUE;

	return result;

}

void CUMLLineSegment::DrawDirectionArrow( CDC* dc )
/* ============================================================
	Function :		CUMLLineSegment::DrawDirectionArrow
	Description :	Draws the single direction arrow to the 
					screen.
	Access :		Private
					
	Return :		void
	Parameters :	CDC* dc	-	"CDC" to draw to
					
	Usage :			Called from the "Draw" function

   ============================================================*/
{
	int hgt = GetMarkerSize().cy / 2;

	double x2 = GetLeft();
	double x1 = GetRight();
	double y2 = GetTop();
	double y1 = GetBottom();

	if( !( GetLinkType( LINK_START ) & LINK_ALL ) && 
		( GetLinkType( LINK_END ) & LINK_ALL ) )
	{
		x2 = GetRight();
		x1 = GetLeft();
		y2 = GetBottom();
		y1 = GetTop();
	}

	POINT pts[ 3 ];
	if( IsHorizontal() )
	{
		if( x1 < x2 )
		{
			pts[ 0 ].x = round( ( x2 - hgt * 4 ) * GetZoom() );
			pts[ 0 ].y = round( ( y1 - hgt ) * GetZoom() );
			pts[ 1 ].x = round( x2 * GetZoom() );
			pts[ 1 ].y = round( y1 * GetZoom() );
			pts[ 2 ].x = pts[ 0 ].x;
			pts[ 2 ].y = round( ( y1 + hgt ) * GetZoom() );
		}
		else
		{
			pts[ 0 ].x = round( ( x2 + hgt * 4 ) * GetZoom() );
			pts[ 0 ].y = round( ( y1 - hgt ) * GetZoom() );
			pts[ 1 ].x = round( x2 * GetZoom() );
			pts[ 1 ].y = round( y1 * GetZoom() );
			pts[ 2 ].x = pts[ 0 ].x;
			pts[ 2 ].y = round( ( y1 + hgt ) * GetZoom() );
		}
	}
	else
	{
		if( y1 < y2 )
		{
			pts[ 0 ].x = round( ( x2 - hgt ) * GetZoom() );
			pts[ 0 ].y = round( ( y2 - hgt * 4 ) * GetZoom() );

			pts[ 1 ].x = round( x2 * GetZoom() );
			pts[ 1 ].y = round( y2 * GetZoom() );

			pts[ 2 ].x = round( ( x2 + hgt ) * GetZoom() );
			pts[ 2 ].y = pts[ 0 ].y;
		}
		else
		{
			pts[ 0 ].x = round( ( x2 - hgt ) * GetZoom() );
			pts[ 0 ].y = round( ( y2 + hgt * 4 ) * GetZoom() );

			pts[ 1 ].x = round( x2 * GetZoom() );
			pts[ 1 ].y = round( y2 * GetZoom() );

			pts[ 2 ].x = round( ( x2 + hgt ) * GetZoom() );
			pts[ 2 ].y = pts[ 0 ].y;
		}
	}

	dc->SelectStockObject( BLACK_PEN );
	dc->SelectStockObject( BLACK_BRUSH );

	dc->Polygon( pts, 3 );

}

void CUMLLineSegment::DrawInheritanceArrow( CDC* dc )
/* ============================================================
	Function :		CUMLLineSegment::DrawInheritanceArrow
	Description :	Draws the inheritance arrow to the screen
	Access :		Private
					
	Return :		void
	Parameters :	CDC* dc	-	"CDC" to draw to
					
	Usage :			Called from the "Draw" function.

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

	int hgt = GetMarkerSize().cy / 2;

	double x1 = GetLeft();
	double x2 = GetRight();
	double y1 = GetTop();
	double y2 = GetBottom();

	if( ( GetLinkType( LINK_START ) & LINK_ALL ) && 
		!( GetLinkType( LINK_END ) & LINK_ALL ) )
	{
		x1 = GetRight();
		x2 = GetLeft();
		y1 = GetBottom();
		y2 = GetTop();
	}

	POINT pts[ 3 ];
	if( IsHorizontal() )
	{
		if( x1 < x2 )
		{
			pts[ 0 ].x = round( ( x2 - hgt * 4 ) * GetZoom() );
			pts[ 0 ].y = round( ( y1 - hgt ) * GetZoom() );
			pts[ 1 ].x = round( x2 * GetZoom() );
			pts[ 1 ].y = round( y1 * GetZoom() );
			pts[ 2 ].x = pts[ 0 ].x;
			pts[ 2 ].y = round( ( y1 + hgt ) * GetZoom() );
		}
		else
		{
			pts[ 0 ].x = round( ( x2 + hgt * 4 ) * GetZoom() );
			pts[ 0 ].y = round( ( y1 - hgt ) * GetZoom() );
			pts[ 1 ].x = round( x2 * GetZoom() );
			pts[ 1 ].y = round( y1 * GetZoom() );
			pts[ 2 ].x = pts[ 0 ].x;
			pts[ 2 ].y = round( ( y1 + hgt ) * GetZoom() );
		}
	}
	else
	{
		if( y1 < y2 )
		{
			pts[ 0 ].x = round( ( x2 - hgt ) * GetZoom() );
			pts[ 0 ].y = round( ( y2 - hgt * 4 ) * GetZoom() );

			pts[ 1 ].x = round( x2 * GetZoom() );
			pts[ 1 ].y = round( y2 * GetZoom() );

			pts[ 2 ].x = round( ( x2 + hgt ) * GetZoom() );
			pts[ 2 ].y = pts[ 0 ].y;
		}
		else
		{
			pts[ 0 ].x = round( ( x2 - hgt ) * GetZoom() );
			pts[ 0 ].y = round( ( y2 + hgt * 4 ) * GetZoom() );

			pts[ 1 ].x = round( x2 * GetZoom() );
			pts[ 1 ].y = round( y2 * GetZoom() );

			pts[ 2 ].x = round( ( x2 + hgt ) * GetZoom() );
			pts[ 2 ].y = pts[ 0 ].y;
		}
	}

	dc->SelectStockObject( BLACK_PEN );
	dc->SelectStockObject( WHITE_BRUSH );

	dc->Polygon( pts, 3 );

}

CString CUMLLineSegment::ExportHTML() const
/* ============================================================
	Function :		CUMLLineSegment::ExportHTML
	Description :	Exports this object to HTML
	Access :		Private
					
	Return :		CString	-	The HTML result
	Parameters :	none

	Usage :			Called from the export-mechanism

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

	CString result;
	if( !( GetStyle() & STYLE_INVISIBLE ) )
	{

		CRect	rect = GetRect();
		int style = GetStyle();

		rect.NormalizeRect();
		int width = max( 1, rect.Width() );
		int height = max( 1, rect.Height() );

		CString linetype ( _T( "solid" ) );
		if( style & STYLE_DASHED )
			linetype = _T( "dashed" );

		CString orientation( _T( "left" ) );
		if( IsHorizontal() )
			orientation = _T( "top" );

		CString line;
		line.Format( _T( "border-%s:1px %s black" ), orientation, linetype );

		result.Format( _T( "<div style='position:absolute;left:%i;top:%i;width:%i;height:%i;%s;'>&nbsp;</div>" ),
							rect.left,rect.top,width,height,line);

		if( GetStyle() & STYLE_ARROWHEAD )
			result += GetArrowHeadHTML();

		if( GetStyle() & STYLE_CIRCLECROSS )
			result += GetCircleCrossHTML();

		if( GetStyle() & STYLE_FILLED_ARROWHEAD )
			result += GetFilledArrowHeadHTML();

		if( GetStyle() & STYLE_FILLED_DIAMOND )
			result += GetFilledDiamondHTML();

		// Label
		CString label;
		CDC* dc = CWnd::GetDesktopWindow()->GetDC();
		CFont font;
		font.CreateFont( -12, 0,0,0,FW_NORMAL,0,0,0,0,0,0,0,0, GetFont() );
		CFont* oldfont = dc->SelectObject( &font );
		CString str = GetTitle();
		int cut = GetMarkerSize().cx / 2;
		if( str.GetLength() )
		{
			CRect rectTemp = GetRect();
			rectTemp.NormalizeRect();
			CRect r( rect.right - cut, rect.top, rect.right - ( rectTemp.Width() + cut ), rect.bottom );
			if( IsHorizontal() )
			{
				CRect r( rect.left, rect.top - ( 14 + cut ), rect.right, rect.bottom );
				r.NormalizeRect();
				label.Format( _T( "<div style='position:absolute;left:%i;top:%i;width:%i;height:%i;font-family:%s;font-size:12;background-color:transparent;text-align:center;'>%s</div>" ), 
					r.left, r.top, r.Width(), r.Height(), GetFont(), str );
				result += label;
			}
			else
			{
				CRect r( rect.right - ( dc->GetTextExtent( str ).cx + cut * 2 ), rect.top, rect.right - cut, rect.bottom );
				r.NormalizeRect();
				r.top += r.Height() / 2 - 7;
				r.bottom = r.top + 14;
				label.Format( _T( "<div style='position:absolute;left:%i;top:%i;width:%i;height:%i;font-family:%s;font-size:12;background-color:transparent;text-align:right;'>%s</div>" ), 
					r.left, r.top, r.Width(), r.Height(), GetFont(), str );
				result += label;
			}

		}

		str = GetStartLabel();
		if( str.GetLength() )
		{
			CRect rectTemp = GetRect();
			if( IsHorizontal() )
			{
				rectTemp.bottom = rectTemp.top - cut;
				rectTemp.top -= 14 + cut;

				if( rectTemp.left < rectTemp.right )
				{
					rectTemp.left += cut;
					rectTemp.right -= cut;
					label.Format( _T( "<div style='position:absolute;left:%i;top:%i;width:%i;height:%i;font-family:%s;font-size:12;background-color:transparent;text-align:right;'>%s</div>" ), 
						rectTemp.left, rectTemp.top, rectTemp.Width(), rectTemp.Height(), GetFont(), str );
					result += label;
				}
				else
				{
					int temp = rectTemp.left;
					rectTemp.left = rectTemp.right + cut;
					rectTemp.right = temp - cut;
					label.Format( _T( "<div style='position:absolute;left:%i;top:%i;width:%i;height:%i;font-family:%s;font-size:12;background-color:transparent;text-align:left;'>%s</div>" ), 
						rectTemp.left, rectTemp.top, rectTemp.Width(), rectTemp.Height(), GetFont(), str );
					result += label;
				}

			}
			else
			{
				rectTemp.left -= dc->GetTextExtent( str ).cx + 2 * cut;
				rectTemp.right -= cut;
				if( rectTemp.top < rectTemp.bottom )
					rectTemp.bottom = rectTemp.top + 14;
				else
				{
					rectTemp.top -= 14;
					rectTemp.bottom = rectTemp.top + 14;
				}

				label.Format( _T( "<div style='position:absolute;left:%i;top:%i;width:%i;height:%i;font-family:%s;font-size:12;background-color:transparent;text-align:right;'>%s</div>" ), 
					rectTemp.left, rectTemp.top, rectTemp.Width(), rectTemp.Height(), GetFont(), str );
				result += label;
			}

		}

		str = GetSecondaryStartLabel();
		if( str.GetLength() )
		{
			CRect rectTemp = GetRect();
			if( IsHorizontal() )
			{
				rectTemp.bottom += 14 + cut;
				rectTemp.top += cut;

				if( rectTemp.left < rectTemp.right )
				{
					rectTemp.left += cut;
					rectTemp.right -= cut;
					label.Format( _T( "<div style='position:absolute;left:%i;top:%i;width:%i;height:%i;font-family:%s;font-size:12;background-color:transparent;text-align:right;'>%s</div>" ), 
						rectTemp.left, rectTemp.top, rectTemp.Width(), rectTemp.Height(), GetFont(), str );
					result += label;
				}
				else
				{
					int temp = rectTemp.left;
					rectTemp.left = rectTemp.right + cut;
					rectTemp.right = temp - cut;
					label.Format( _T( "<div style='position:absolute;left:%i;top:%i;width:%i;height:%i;font-family:%s;font-size:12;background-color:transparent;text-align:left;'>%s</div>" ), 
						rectTemp.left, rectTemp.top, rectTemp.Width(), rectTemp.Height(), GetFont(), str );
					result += label;
				}

			}
			else
			{
				rectTemp.right += dc->GetTextExtent( str ).cx + 2 * cut;
				rectTemp.left += cut;

				if( rectTemp.top < rectTemp.bottom )
					rectTemp.bottom = rectTemp.top + 14;
				else
				{
					rectTemp.top -= 14;
					rectTemp.bottom = rectTemp.top + 14;
				}

				label.Format( _T( "<div style='position:absolute;left:%i;top:%i;width:%i;height:%i;font-family:%s;font-size:12;background-color:transparent;text-align:right;'>%s</div>" ), 
					rectTemp.left, rectTemp.top, rectTemp.Width(), rectTemp.Height(), GetFont(), str );
				result += label;
			}

		}

		str = GetEndLabel();
		if( str.GetLength() )
		{
			CRect rectTemp = GetRect();
			if( IsHorizontal() )
			{
				rectTemp.bottom = rectTemp.top - cut;
				rectTemp.top -= 14 + cut;

				if( rectTemp.left < rectTemp.right )
				{
					rectTemp.left += cut;
					rectTemp.right -= cut;
					label.Format( _T( "<div style='position:absolute;left:%i;top:%i;width:%i;height:%i;font-family:%s;font-size:12;background-color:transparent;text-align:left;'>%s</div>" ), 
						rectTemp.left, rectTemp.top, rectTemp.Width(), rectTemp.Height(), GetFont(), str );
					result += label;
				}
				else
				{
					int temp = rectTemp.left;
					rectTemp.left = rectTemp.right + cut;
					rectTemp.right = temp - cut;
					label.Format( _T( "<div style='position:absolute;left:%i;top:%i;width:%i;height:%i;font-family:%s;font-size:12;background-color:transparent;text-align:right;'>%s</div>" ), 
						rectTemp.left, rectTemp.top, rectTemp.Width(), rectTemp.Height(), GetFont(), str );
					result += label;
				}

			}
			else
			{
				rectTemp.left -= dc->GetTextExtent( str ).cx + 2 * cut;
				rectTemp.right -= cut;
				if( rectTemp.top < rectTemp.bottom )
					rectTemp.top = rectTemp.bottom - 14;
				else
				{
					rectTemp.top = rectTemp.bottom;
					rectTemp.bottom = rectTemp.top + 14;
				}
				label.Format( _T( "<div style='position:absolute;left:%i;top:%i;width:%i;height:%i;font-family:%s;font-size:12;background-color:transparent;text-align:right;'>%s</div>" ), 
					rectTemp.left, rectTemp.top, rectTemp.Width(), rectTemp.Height(), GetFont(), str );
				result += label;
			}
		}

		str = GetSecondaryEndLabel();
		if( str.GetLength() )
		{
			CRect rectTemp = GetRect();
			if( IsHorizontal() )
			{
				rectTemp.bottom += 14 + cut;
				rectTemp.top += cut;

				if( rectTemp.left < rectTemp.right )
				{
					rectTemp.left += cut;
					rectTemp.right -= cut;
					label.Format( _T( "<div style='position:absolute;left:%i;top:%i;width:%i;height:%i;font-family:%s;font-size:12;background-color:transparent;text-align:left;'>%s</div>" ), 
						rectTemp.left, rectTemp.top, rectTemp.Width(), rectTemp.Height(), GetFont(), str );
					result += label;
				}
				else
				{
					int temp = rectTemp.left;
					rectTemp.left = rectTemp.right + cut;
					rectTemp.right = temp - cut;
					label.Format( _T( "<div style='position:absolute;left:%i;top:%i;width:%i;height:%i;font-family:%s;font-size:12;background-color:transparent;text-align:right;'>%s</div>" ), 
						rectTemp.left, rectTemp.top, rectTemp.Width(), rectTemp.Height(), GetFont(), str );
					result += label;
				}

			}
			else
			{
				rectTemp.right += dc->GetTextExtent( str ).cx + 2 * cut;
				rectTemp.left += cut;
				if( rectTemp.top < rectTemp.bottom )
					rectTemp.top = rectTemp.bottom - 14;
				else
				{
					rectTemp.top = rectTemp.bottom;
					rectTemp.bottom = rectTemp.top + 14;
				}
				label.Format( _T( "<div style='position:absolute;left:%i;top:%i;width:%i;height:%i;font-family:%s;font-size:12;background-color:transparent;text-align:right;'>%s</div>" ), 
					rectTemp.left, rectTemp.top, rectTemp.Width(), rectTemp.Height(), GetFont(), str );
				result += label;
			}
		}

		dc->SelectObject( oldfont );
		CWnd::GetDesktopWindow()->ReleaseDC( dc );
	}

	return result;

}

CString CUMLLineSegment::GetArrowHeadHTML() const
/* ============================================================
	Function :		CUMLLineSegment::GetArrowHeadHTML
	Description :	Gets a string representing the inheritance
					arrow picture for HTML-exports.
	Access :		Private
					
	Return :		CString	-	The string in HTML-format.
	Parameters :	none

	Usage :			Called as a part of the export mechanism.

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

	BOOL invert = TRUE;
	if( !( GetLinkType( LINK_END ) & LINK_ALL ) && 
		( GetLinkType( LINK_START ) & LINK_ALL ) )
		invert = FALSE;

	CString img;
	CRect marker = GetRect();

	int width = 0;
	int height = 0;

	CSize size( 17, 9 );
	if( IsHorizontal() )
	{
		width = size.cx;
		height = size.cy;
		if( !invert )
		{
			if( marker.right > marker.left )
				img = _T( "images/larrow.gif" );
			else
				img = _T( "images/rarrow.gif" );
		}
		else
		{
			if( marker.right < marker.left )
				img = _T( "images/larrow.gif" );
			else
				img = _T( "images/rarrow.gif" );
		}
	}
	else
	{
		width = size.cy;
		height = size.cx;
		if( !invert )
		{
			if( marker.bottom > marker.top )
				img = _T( "images/uarrow.gif" );
			else
				img = _T( "images/darrow.gif" );
		}
		else
		{
			if( marker.bottom < marker.top )
				img = _T( "images/uarrow.gif" );
			else
				img = _T( "images/darrow.gif" );
		}
	}

	CPoint pos = GetStyleMarkerRect( LINK_END, size );

	CString arrow;
	arrow.Format( _T( "<div style='position:absolute;left:%i;top:%i;width:%i;height:%i;background-image:url(\"%s\");background-repeat:no-repeat;'>&nbsp;</div>" ),
		pos.x, pos.y, width, height, img );

	return arrow;

}

CString CUMLLineSegment::GetFilledArrowHeadHTML() const
/* ============================================================
	Function :		CUMLLineSegment::GetFilledArrowHeadHTML
	Description :	Gets a string representing the single-direction  
					arrow picture for HTML-exports.
	Access :		Private
					
	Return :		CString	-	The string in HTML-format.
	Parameters :	none

	Usage :			Called as a part of the export mechanism.

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

	BOOL invert = FALSE;
	if( !( GetLinkType( LINK_START ) & LINK_ALL ) && 
		( GetLinkType( LINK_END ) & LINK_ALL ) )
		invert = TRUE;

	CString img;
	CRect marker = GetRect();

	int width = 0;
	int height = 0;

	CSize size( 17, 9 );
	if( IsHorizontal() )
	{
		width = size.cx;
		height = size.cy;
		if( !invert )
		{
			if( marker.right < marker.left )
				img = _T( "images/rfarrow.gif" );
			else
				img = _T( "images/lfarrow.gif" );
		}
		else
		{
			if( marker.right > marker.left )
				img = _T( "images/rfarrow.gif" );
			else
				img = _T( "images/lfarrow.gif" );
		}
	}
	else
	{
		width = size.cy;
		height = size.cx;
		if( !invert )
		{
			if( marker.bottom < marker.top )
				img = _T( "images/dfarrow.gif" );
			else
				img = _T( "images/ufarrow.gif" );
		}
		else
		{
			if( marker.bottom > marker.top )
				img = _T( "images/dfarrow.gif" );
			else
				img = _T( "images/ufarrow.gif" );
		}
	}

	CPoint pos = GetStyleMarkerRect( LINK_START, size );

	CString arrow;
	arrow.Format( _T( "<div style='position:absolute;left:%i;top:%i;width:%i;height:%i;background-image:url(\"%s\");background-repeat:no-repeat;'>&nbsp;</div>" ),
		pos.x, pos.y, width, height, img );

	return arrow;

}

CString CUMLLineSegment::GetFilledDiamondHTML() const
/* ============================================================
	Function :		CUMLLineSegment::GetFilledDiamondHTML
	Description :	Gets a string representing the containment 
					picture for HTML-exports.
	Access :		Private
					
	Return :		CString	-	The string in HTML-format.
	Parameters :	none

	Usage :			Called as a part of the export mechanism.

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

	CString img;

	int width = 0;
	int height = 0;

	CSize size( 16, 9 );
	if( IsHorizontal() )
	{
		width = size.cx;
		height = size.cy;
		img = _T( "images/lrdiamond.gif" );
	}
	else
	{
		width = size.cy;
		height = size.cx;
		img = _T( "images/uddiamond.gif" );
	}

	CPoint pos = GetStyleMarkerRect( LINK_END, size );

	CString arrow;
	arrow.Format( _T( "<div style='position:absolute;left:%i;top:%i;width:%i;height:%i;background-image:url(\"%s\");background-repeat:no-repeat;'>&nbsp;</div>" ),
		pos.x, pos.y, width, height, img );

	return arrow;

}

CString CUMLLineSegment::GetCircleCrossHTML() const
/* ============================================================
	Function :		CUMLLineSegment::GetCircleCrossHTML
	Description :	Gets a string representing the part-of 
					picture for HTML-exports.
	Access :		Private
					
	Return :		CString	-	The string in HTML-format.
	Parameters :	none

	Usage :			Called as a part of the export mechanism.

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

	CString img( _T( "images/partof.gif" ) );

	CSize marker( 16, 16 );
	CPoint pos = GetStyleMarkerRect( LINK_END, marker );

	CString arrow;
	arrow.Format( _T( "<div style='position:absolute;left:%i;top:%i;width:%i;height:%i;background-image:url(\"%s\");background-repeat:no-repeat;'>&nbsp;</div>" ),
		pos.x, pos.y, marker.cx, marker.cy, img );

	return arrow;

}

CPoint CUMLLineSegment::GetStyleMarkerRect( int node, const CSize& size ) const
/* ============================================================
	Function :		CUMLLineSegment::GetStyleMarkerRect
	Description :	The function returns a the start point for 
					a rectangle with the size "size" for either 
					the start or end of a line.
	Access :		Private
					
	Return :		CPoint				-	The start point
	Parameters :	int node			-	Either "LINK_START" 
											or "LINK_END" for the 
											start or end of the 
											line.
					const CSize& size	-	The size of the 
											rectangle.
					
	Usage :			Call to get the position of a rectangle to 
					draw markers at either the start or the end 
					of a line segment.

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

	CPoint result( 0, 0 );

	int invers = LINK_START;
	if( node == LINK_START )
		invers = LINK_END;

	// We try to find what end of the segment is 
	// really the start/end, that is the end where 
	// a non-line object is attached.
	BOOL invert = FALSE;
	if( !( GetLinkType( node ) & LINK_ALL ) && 
		( GetLinkType( invers ) & LINK_ALL ) )
		invert = TRUE;

	CRect marker = GetRect();

	int width = 0;
	int height = 0;
	int left = 0;
	int top = 0;

	if( node == LINK_END )
	{
		if( IsHorizontal() )
		{
			width = size.cx;
			height = size.cy;
			top = marker.top - height / 2;
			if( !invert )
			{
				if( marker.right < marker.left )
					left = marker.right;
				else
					left = marker.right - width;
			}
			else
			{
				if( marker.right > marker.left )
					left = marker.left;
				else
					left = marker.left - width;
			}
		}
		else
		{
			width = size.cy;
			height = size.cx;
			left = marker.left - width / 2;
			if( !invert )
			{
				if( marker.bottom < marker.top )
					top = marker.bottom;
				else
					top = marker.bottom - height;
			}
			else
			{
				if( marker.bottom > marker.top )
					top = marker.top;
				else
					top = marker.top - height;
			}
		}

		result.x = left;
		result.y = top;

	}
	else
	{
		if( IsHorizontal() )
		{
			width = size.cx;
			height = size.cy;
			top = marker.top - height / 2;
			if( !invert )
			{
				if( marker.right < marker.left )
					left = marker.left - width;
				else
					left = marker.left;
			}
			else
			{
				if( marker.right > marker.left )
					left = marker.right - width;
				else
					left = marker.right;
			}
		}
		else
		{
			width = size.cy;
			height = size.cx;
			left = marker.left - width / 2;
			if( !invert )
			{
				if( marker.bottom < marker.top )
					top = marker.top - height;
				else
					top = marker.top;
			}
			else
			{
				if( marker.bottom > marker.top )
					top = marker.bottom - height;
				else
					top = marker.bottom;
			}
		}

		result.x = left;
		result.y = top;

	}

	return result;

}

BOOL CUMLLineSegment::DoMessage( UINT msg, CDiagramEntity* sender, CWnd* from )
/* ============================================================
	Function :		CUMLLineSegment::DoMessage
	Description :	Receives a message from the editor.
	Access :		Public
					
	Return :		BOOL					-	"TRUE" to stop 
												processing.
	Parameters :	UINT msg				-	App or editor 
												defined message.
					CDiagramEntity* sender	-	The objects sending 
												this message, if any.
					CWnd* from				-	The editor window.
					
	Usage :			Overridden to handle of the Flip-link 
					command.

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

	BOOL result = TRUE;

	if( msg == CMD_FLIP )
		GetUMLContainer()->FlipLink();
	else
		result = CUMLEntity::DoMessage( msg, sender, from );

	return result;

}

void CUMLLineSegment::ShowPopup( CPoint point, CWnd* parent )
/* ============================================================
	Function :		CUMLLineSegment::ShowPopup
	Description :	Displays an object-specific popup menu.
	Access :		Public
					
	Return :		void
	Parameters :	CPoint point	-	position of popup
					CWnd* parent	-	parent window of popup
					
	Usage :			Called from the editor when the line is 
					right-clicked.

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

	CMenu menu;
	menu.LoadMenu( IDR_UML_MENU_LINK );
	menu.GetSubMenu(0)->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, parent );

}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under A Public Domain dedication


Written By
Software Developer (Senior) Abstrakt Mekanik AB
Sweden Sweden
45 years old, married, three kids.

Started with computers more than 20 years ago on a CBM-64.

Read Theoretical Philosophy at the University of Lund.

Working as a C++ consultant developer.

Science-fiction freak. Enjoy vintage punkrock.

Comments and Discussions