/*
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 1, or (at your option)
** any later version.

** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.

** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

// GraphView.cpp : implementation file
//

#include "stdafx.h"
#include "wincvs.h"
#include "GraphView.h"
#include "GraphDoc.h"
#include "AppConsole.h"
#include "LogParse.h"
#include "CvsEntries.h"
#include "BrowseFileView.h"
#include "WinCvsBrowser.h"
#include "CvsArgs.h"
#include "WincvsView.h"
#include "CvsPrefs.h"
#include "FileTraversal.h"
#include "ProgressDlg.h"
#include "GraphOptDlg.h"
#include "AdminOptionDlg.h"
#include "CvsAlert.h"
#include "CvsCommands.h"
#include "MultiFiles.h"
#include "BrowseViewHandlers.h"

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

// Persistent settings
CPersistentInt gGraphSelColor("P_GraphSelColor", RGB_DEF_SEL);
CPersistentInt gGraphShadowColor("P_GraphShadowColor", RGB_DEF_SHADOW);
CPersistentInt gGraphHeaderColor("P_GraphHeaderColor", RGB_DEF_HEADER);
CPersistentInt gGraphTagColor("P_GraphTagColor", RGB_DEF_TAG);
CPersistentInt gGraphBranchColor("P_GraphBranchColor", RGB_DEF_BRANCH);
CPersistentInt gGraphNodeColor("P_GraphNodeColor", RGB_DEF_NODE);
CPersistentInt gGraphDeadNodeColor("P_GraphDeadNodeColor", RGB_DEF_DEADNODE);
CPersistentInt gGraphUserStateNodeColor("P_GraphUserStateNodeColor", RGB_DEF_USERSTATENODE);

/// Vertical child spacing
const int kVChildSpace = 8;
/// Horizontal child spacing
const int kHChildSpace = 40;
/// Inflate node spacing
const int kInflateNodeSpace = 8;

/*!
	Helper function to draw the figure used for branch tag representation
	\param dc Device context
	\param selfB Bounds
*/
static void DrawOctangle(CDC& dc, const CRect& selfB)
{
	int cutLen = selfB.Height() / 4;
	CPoint point1(selfB.left, selfB.top + cutLen);
	CPoint point2(selfB.left + cutLen, selfB.top);
	CPoint point3(selfB.right - cutLen, selfB.top);
	CPoint point4(selfB.right, selfB.top + cutLen);
	CPoint point5(selfB.right, selfB.bottom - cutLen);
	CPoint point6(selfB.right - cutLen, selfB.bottom);
	CPoint point7(selfB.left + cutLen, selfB.bottom);
	CPoint point8(selfB.left, selfB.bottom - cutLen);
	CPoint arrPoints[OCTANGLE_COUNT] = {
		point1,
		point2,
		point3,
		point4,
		point5,
		point6,
		point7,
		point8};

	dc.Polygon(arrPoints, OCTANGLE_COUNT);
}

/*!
	Helper function used to draw the node
	\param dc Device context
	\param selfB Bounds
	\param contour Contour color
	\param shape Shape
	\param isSel true if it's selected, false if not selected
	\param penStyle Pen style
*/
static void DrawNode(CDC& dc, const CRect& selfB,
					 COLORREF contour, 
					 kNodeShape shape, bool isSel, int penStyle = PS_SOLID)
{
	CPen* pOldPen = 0L;
	CBrush* pOldBrush = 0L;
	CPen pen, pen2;
	CBrush brush;
	COLORREF selcolor = gGraphSelColor;
	COLORREF shadowc = gGraphShadowColor;
	
	TRY
	{
		// Prepare the shadow
		CRect shadow = selfB;
		shadow.OffsetRect(SHADOW_OFFSET_PT);
		
		brush.CreateSolidBrush(shadowc);
		pOldBrush = dc.SelectObject(&brush);
		pen.CreatePen(penStyle, 1, shadowc);
		pOldPen = dc.SelectObject(&pen);

		// Draw the shadow
		switch( shape )
		{
		case kRectangle:
			dc.Rectangle(shadow);
			break;
		case kRoundRect:
			dc.RoundRect(shadow, ROUND_RECT_PT);
			break;
		case kOctangle:
			DrawOctangle(dc, shadow);
			break;
		default:
			ASSERT(FALSE);	//unknown type
			return;
		}

		// Prepare selection
		if( isSel )
		{
			brush.DeleteObject();
			brush.CreateSolidBrush(selcolor);
			dc.SelectObject(&brush);
		}
		else
		{
			dc.SelectObject(pOldBrush);
			pOldBrush = 0L;
		}

		pen2.CreatePen(penStyle, 1, contour);
		dc.SelectObject(&pen2);
		
		// Draw the main shape
		switch( shape )
		{
		case kRectangle:
			dc.Rectangle(selfB);
			break;
		case kRoundRect:
			dc.RoundRect(selfB, ROUND_RECT_PT);
			break;
		case kOctangle:
			DrawOctangle(dc, selfB);
			break;
		default:
			ASSERT(FALSE);	//unknown type
			return;
		}

		// Cleanup
		if( pOldPen != 0L )
		{
			dc.SelectObject(pOldPen);
			pOldPen = 0L;
		}
		
		if( pOldBrush != 0L )
		{
			dc.SelectObject(pOldBrush);
			pOldBrush = 0L;
		}

		pen.DeleteObject();
		pen2.DeleteObject();
		brush.DeleteObject();
	}
	CATCH_ALL(e)
	{
		if( pOldPen != 0L )
			dc.SelectObject(pOldPen);

		if( pOldBrush != 0L )
			dc.SelectObject(pOldBrush);
	}
	END_CATCH_ALL
}

//////////////////////////////////////////////////////////////////////////
// CWinLogData

CWinLogData::CWinLogData(CLogNode* node) : CLogNodeData(node)
{
	selfB.SetRectEmpty();
	totB.SetRectEmpty();
	sel = false;
}

CWinLogData::~CWinLogData()
{
}

/*!
	Get log node data
	\param node Log node to get the date from
	\return The log node data
*/
CWinLogData* CWinLogData::GetData(CLogNode* node)
{
	return (CWinLogData*)node->GetUserData();
}

/*!
	Get log node type
	\return The log node type
*/
kLogNode CWinLogData::GetType(void) const
{ 
	return fNode->GetType(); 
}

/*!
	Get the node string
	\return The node string
*/
const char* CWinLogData::GetStr(void)
{
	switch(GetType())
	{
	case kNodeHeader:
		{
			CLogNodeHeader& header = *(CLogNodeHeader*)fNode;
			return (*header).WorkingFile();
			break;
		}
	case kNodeBranch:
		{
			CLogNodeBranch& branch = *(CLogNodeBranch*)fNode;
			return *branch;
			break;
		}
	case kNodeRev:
		{
			CLogNodeRev& rev = *(CLogNodeRev*)fNode;
			return (*rev).RevNum().c_str();
			break;
		}
	case kNodeTag:
		{
			CLogNodeTag& tag = *(CLogNodeTag*)fNode;
			return *tag;
			break;
		}
	}

	return 0L;
}

/*!
	Get the bounds rectangle
	\return The bounds rectangle
*/
CRect& CWinLogData::Bounds(void)
{
	return totB;
}

/*!
	Get the self bounds
	\return The self bounds
*/
CRect& CWinLogData::SelfBounds(void)
{
	return selfB;
}

/*!
	Create new log data
	\param node Log node
	\return New log data on success, NULL otherwise
*/
CWinLogData* CWinLogData::CreateNewData(CLogNode* node)
{
	if( node->GetUserData() != 0L )
		return (CWinLogData*)node->GetUserData();

	CWinLogData* res = 0L;
	switch(node->GetType())
	{
	case kNodeHeader :
		res = new CWinLogHeaderData(node);
		break;
	case kNodeBranch :
		res = new CWinLogBranchData(node);
		break;
	case kNodeRev :
		res = new CWinLogRevData(node);
		break;
	case kNodeTag :
		res = new CWinLogTagData(node);
		break;
	}

	node->SetUserData(res);
	return res;
}

/*!
	Check whether the selected node is the same as sandbox revision
	\return true if the selected node is a revision and the revision is the same as sandbox revision, false otherwise
*/
bool CWinLogData::IsDiskNode(EntnodeData* entryInfo)
{
	if( entryInfo == 0L || GetType() != kNodeRev )
		return false;

	const char* vn = (*entryInfo)[EntnodeFile::kVN];
	if( vn == 0L )
		return false;

	return strcmp(vn, (**(CLogNodeRev*)fNode).RevNum().c_str()) == 0;
}

/*!
	Check whether the selected node has the <b>dead</b> state
	\return true if the selected node is a revision and the state is <b>dead</b>, false otherwise
*/
bool CWinLogData::IsDeadNode()
{
	if( GetType() != kNodeRev )
		return false;
	
	return strcmp("dead", (**(CLogNodeRev*)fNode).State()) == 0;
}

/*!
	Check whether the selected state is a user-defined state
	\return true if the selected node is a revision and the state is user-defined, false otherwise
*/
bool CWinLogData::IsUserState()
{
	if( GetType() != kNodeRev )
		return false;
	
	return !(strcmp("dead", (**(CLogNodeRev*)fNode).State()) == 0 || 
		strcmp("Exp", (**(CLogNodeRev*)fNode).State()) == 0);
}

/*!
	Offset the log node
	\param offset Offset
*/
void CWinLogData::Offset(CPoint offset)
{
	selfB.OffsetRect(offset);
	totB.OffsetRect(offset);
	
	vector<CLogNode*>::iterator i;
	for(i = fNode->Childs().begin(); i != fNode->Childs().end(); ++i)
	{
		CLogNode* subNode = *i;
		GetData(subNode)->Offset(offset);
	}

	if( fNode->Next() != 0L )
	{
		GetData(fNode->Next())->Offset(offset);
	}
}

/*!
	Get the selection state
	\return true if selected, false otherwise
*/
bool CWinLogData::Selected(void) const
{
	return sel;
}

/*!
	Set the selection state
	\param view View to set the selection state
	\param state New selection state
*/
void CWinLogData::SetSelected(CScrollView* view, bool state)
{
	sel = state;

	CRect invalR = selfB;
	invalR.OffsetRect(-view->GetDeviceScrollPosition());
	view->InvalidateRect(invalR);
}

/*!
	Remove selection from all nodes
	\param view View
*/
void CWinLogData::UnselectAll(CScrollView* view)
{
	if( sel )
	{
		SetSelected(view, false);
	}

	vector<CLogNode*>::iterator i;
	for(i = fNode->Childs().begin(); i != fNode->Childs().end(); ++i)
	{
		CLogNode* subNode = *i;
		GetData(subNode)->UnselectAll(view);
	}

	if( fNode->Next() != 0L )
		GetData(fNode->Next())->UnselectAll(view);
}

/*!
	Text whether the point is within the node and return the found node
	\param point Point to test
	\return Log data if node found, NULL otherwise
*/
CWinLogData* CWinLogData::HitTest(CPoint point)
{
	if( !totB.PtInRect(point) )
		return 0L;

	if( selfB.PtInRect(point) )
		return this;

	CWinLogData* result;
	vector<CLogNode*>::iterator i;
	for(i = fNode->Childs().begin(); i != fNode->Childs().end(); ++i)
	{
		CLogNode* subNode = *i;
		result = GetData(subNode)->HitTest(point);
		if( result != 0L )
			return result;
	}

	if( fNode->Next() != 0L )
	{
		result = GetData(fNode->Next())->HitTest(point);
		if( result != 0L )
			return result;
	}

	return 0L;
}

/*!
	Draw the node
	\param dc Device context
	\param entryInfo Entry info
*/
void CWinLogData::Update(CDC& dc, EntnodeData* entryInfo)
{
	CLogNode* subNode;
	CLogNode* nextNode = fNode->Next();
	CPen* pOldPen = 0L;
	CBrush* pOldBrush = 0L;
	CPen pen, pen2;
	CBrush brush;

	UpdateSelf(dc, entryInfo);

	TRY
	{
		pen.CreatePen(PS_SOLID, 2, RGB(0, 0, 255));
		pen2.CreatePen(PS_SOLID, 1, RGB(0, 0, 0));
		pOldPen = dc.SelectObject(&pen);

		int topY = selfB.TopLeft().y + 2;
		int botY = selfB.BottomRight().y - 2;
		int lefX = selfB.BottomRight().x + 4;

		int count = 1;
		int tot = fNode->Childs().size() + 1;

		// Draw the children
		vector<CLogNode*>::iterator i;
		for(i = fNode->Childs().begin(); i != fNode->Childs().end(); ++i, ++count)
		{
			subNode = *i;
			
			// Draw the link
			CRect& cb = GetData(subNode)->SelfBounds();
			
			if( subNode->GetType() == kNodeRev || subNode->GetType() == kNodeBranch )
				dc.SelectObject(&pen);
			else
				dc.SelectObject(&pen2);

			float y = (float)topY + (float)(botY - topY) * (float)count / (float)tot;
			int rigX = cb.TopLeft().x - 4;
			float x = (float)rigX - (float)(rigX - lefX) * (float)count / (float)tot;

			// Special drawing for the header node
			if( count == 1 && fNode->GetType() == kNodeHeader )
			{
				dc.MoveTo(selfB.TopLeft().x + kHChildSpace/2, selfB.BottomRight().y);
				dc.LineTo(selfB.TopLeft().x + kHChildSpace/2, cb.CenterPoint().y);
				dc.LineTo(cb.TopLeft().x, cb.CenterPoint().y);
			}
			else
			{
				//int halfX = (cb.TopLeft().x - selfB.BottomRight().x) / 2;
				dc.MoveTo(selfB.BottomRight().x, (int)y);
				dc.LineTo((int)x, (int)y);
				dc.LineTo((int)x, cb.CenterPoint().y);
				dc.LineTo(cb.TopLeft().x, cb.CenterPoint().y);
			}
			
			// Draw the sub-node
			GetData(subNode)->Update(dc, entryInfo);
		}

		// Draw the next node
		if( nextNode != 0L )
		{
			// Draw the link
			CRect& nb = GetData(nextNode)->SelfBounds();
			if( nextNode->GetType() == kNodeRev || nextNode->GetType() == kNodeBranch )
				dc.SelectObject(&pen);
			else
				dc.SelectObject(&pen2);
			int centerX = selfB.Width() < nb.Width() ?
				selfB.CenterPoint().x : nb.CenterPoint().x;

			dc.MoveTo(centerX, selfB.BottomRight().y);
			dc.LineTo(centerX, nb.TopLeft().y);
			
			// Draw the next node
			GetData(nextNode)->Update(dc, entryInfo);
		}

		if( pOldPen != 0L )
		{
			dc.SelectObject(pOldPen);
			pOldPen = 0L;
		}

		if( pOldBrush != 0L )
		{
			dc.SelectObject(pOldBrush);
			pOldBrush = 0L;
		}

		pen.DeleteObject();
		pen2.DeleteObject();
	}
	CATCH_ALL(e)
	{
		if( pOldPen != 0L )
			dc.SelectObject(pOldPen);

		if( pOldBrush != 0L )
			dc.SelectObject(pOldBrush);
	}
	END_CATCH_ALL
}

/*!
	Calculate the bounds
	\param topLeft Top-left point for the bounds
	\param dc Device context
	\param entryInfo Entry info
*/
void CWinLogData::ComputeBounds(const CPoint& topLeft, CDC& dc, EntnodeData* entryInfo)
{
	CLogNode* subNode;
	CLogNode* nextNode = fNode->Next();

	// First compute childrens bounds
	vector<CLogNode*>::iterator i;
	for(i = fNode->Childs().begin(); i != fNode->Childs().end(); ++i)
	{
		subNode = *i;
		CWinLogData::CreateNewData(subNode);
		GetData(subNode)->ComputeBounds(topLeft, dc, entryInfo);
	}

	if( nextNode != 0L )
		CWinLogData::CreateNewData(nextNode);

	// Compute self place
	selfB.SetRectEmpty();
	CSize size = GetBoundsSelf(dc, entryInfo);
	size.cx += kInflateNodeSpace;
	size.cy += kInflateNodeSpace;
	selfB.SetRect(0, 0, size.cx, size.cy);

	// Offset to the place assigned to this node
	selfB.OffsetRect(topLeft - selfB.TopLeft());

	// Calculate the total height of the childrens
	int vSize = 0;
	for(i = fNode->Childs().begin(); i != fNode->Childs().end(); ++i)
	{
		subNode = *i;
		CRect& b = GetData(subNode)->Bounds();
		vSize += b.Height();
	}

	if( !fNode->Childs().empty() )
		vSize += (fNode->Childs().size() - 1) * kVChildSpace;

	// Offset the children relative to self
	CPoint startChilds(topLeft.x + selfB.Width() + kHChildSpace,
		/*selfB.CenterPoint().y - vSize / 2*/topLeft.y);

	// Place the first child at the bottom of the header node so it won't take so much space for long filenames
	if( fNode->GetType() == kNodeHeader )
	{
		startChilds = CPoint(topLeft.x + kHChildSpace, topLeft.y + selfB.Height() + kVChildSpace);
	}

	for(i = fNode->Childs().begin(); i != fNode->Childs().end(); ++i)
	{
		subNode = *i;
		CRect curPos = GetData(subNode)->Bounds();
		GetData(subNode)->Offset(startChilds - curPos.TopLeft());
		startChilds.y += curPos.Height() + kVChildSpace;
	}

	// Calculate the total bounds of the childrens
	CRect bc;
	bc.SetRectEmpty();
	for(i = fNode->Childs().begin(); i != fNode->Childs().end(); ++i)
	{
		subNode = *i;
		CRect& b = GetData(subNode)->Bounds();
		bc.UnionRect(bc, b);
	}

	// Now we got the complete size
	totB.UnionRect(selfB, bc);

	if( nextNode != 0L )
	{
		CPoint nextTopLeft;
		nextTopLeft.x = totB.TopLeft().x;
		nextTopLeft.y = totB.BottomRight().y + kVChildSpace;
		GetData(nextNode)->ComputeBounds(nextTopLeft, dc, entryInfo);

		totB.UnionRect(totB, GetData(nextNode)->Bounds());
	}
}

//////////////////////////////////////////////////////////////////////////
// CWinLogHeaderData

CSize CWinLogHeaderData::GetBoundsSelf(CDC& dc, EntnodeData* entryInfo)
{
	CLogNodeHeader& header = *(CLogNodeHeader*)fNode;
	return dc.GetTextExtent((*header).WorkingFile(),
		(*header).WorkingFile().length());
}

void CWinLogHeaderData::UpdateSelf(CDC& dc, EntnodeData* entryInfo)
{
	// Draw the node
	::DrawNode(dc, SelfBounds(), gGraphHeaderColor, kRoundRect, Selected());

	// Draw the content of the node
	TEXTMETRIC tm;
	dc.GetTextMetrics(&tm);
	
	CPoint center = SelfBounds().CenterPoint();
	int nCenterX = center.x;
	int nCenterY = center.y - tm.tmHeight/2;
	
	CLogNodeHeader& header = *(CLogNodeHeader*)fNode;
	dc.ExtTextOut(nCenterX, nCenterY, ETO_CLIPPED, SelfBounds(),
		(*header).WorkingFile(), (*header).WorkingFile().length(), NULL);
}

//////////////////////////////////////////////////////////////////////////
// CWinLogRevData

CSize CWinLogRevData::GetBoundsSelf(CDC& dc, EntnodeData* entryInfo)
{
	CLogNodeRev& rev = *(CLogNodeRev*)fNode;
	CSize size = dc.GetTextExtent((*rev).RevNum().c_str(), strlen((*rev).RevNum().c_str()));
	
	if( IsDiskNode(entryInfo) )
	{
		size.cx += 2 + 16 + 2;
		size.cy = max(16, size.cy);
	}

	return size;
}

void CWinLogRevData::UpdateSelf(CDC& dc, EntnodeData* entryInfo)
{
	CRect txtRect = SelfBounds();

	if( IsDiskNode(entryInfo) )
	{
		txtRect.left += 2 + 16 + 2;
	}

	// Draw the node
	COLORREF contour = gGraphNodeColor;
	int penStyle = PS_SOLID;

	if( IsDeadNode() )
	{
		contour = (COLORREF)gGraphDeadNodeColor;
		penStyle = STYLE_DEADNODE;
	}
	else if( IsUserState() )
	{
		contour = (COLORREF)gGraphUserStateNodeColor;
		penStyle = STYLE_USERSTATENODE;
	}

	::DrawNode(dc, SelfBounds(), contour, kRectangle, Selected(), penStyle);

	// Draw the state icon if it is the rev. of the disk
	if( IsDiskNode(entryInfo) )
	{
		CImageList& list = CGraphView::GetImgList();
		CPoint w(SelfBounds().TopLeft());
		w.x += 2;
		w.y += (SelfBounds().Height() - 16) / 2;

		list.Draw(&dc, CBrowseFileView::GetImageForEntry(entryInfo), w, ILD_TRANSPARENT);
	}

	// Draw the content of the node
	TEXTMETRIC tm;
	dc.GetTextMetrics(&tm);
	CPoint center = txtRect.CenterPoint();
	int nCenterX = center.x;
	int nCenterY = center.y - tm.tmHeight/2;
	CLogNodeRev& rev = *(CLogNodeRev*)fNode;
	dc.ExtTextOut(nCenterX, nCenterY, ETO_CLIPPED, txtRect,
		(*rev).RevNum().c_str(), strlen((*rev).RevNum().c_str()), NULL);
}

//////////////////////////////////////////////////////////////////////////
// CWinLogTagData

CSize CWinLogTagData::GetBoundsSelf(CDC& dc, EntnodeData* entryInfo)
{
	CLogNodeTag& tag = *(CLogNodeTag*)fNode;
	return dc.GetTextExtent(*tag, (*tag).length());
}

void CWinLogTagData::UpdateSelf(CDC& dc, EntnodeData* entryInfo)
{
	// Draw the node
	::DrawNode(dc, SelfBounds(), gGraphTagColor, kRoundRect, Selected());

	// Draw the content of the node
	TEXTMETRIC tm;
	dc.GetTextMetrics(&tm);
	CPoint center = SelfBounds().CenterPoint();
	int nCenterX = center.x;
	int nCenterY = center.y - tm.tmHeight/2;
	
	CLogNodeTag& tag = *(CLogNodeTag*)fNode;
	dc.ExtTextOut(nCenterX, nCenterY, ETO_CLIPPED, SelfBounds(), *tag, (*tag).length(), NULL);
}

//////////////////////////////////////////////////////////////////////////
// CWinLogBranchData

CSize CWinLogBranchData::GetBoundsSelf(CDC& dc, EntnodeData* entryInfo)
{
	CLogNodeBranch& branch = *(CLogNodeBranch*)fNode;
	return dc.GetTextExtent(*branch, (*branch).length());
}

void CWinLogBranchData::UpdateSelf(CDC& dc, EntnodeData* entryInfo)
{
	// Draw the node
	::DrawNode(dc, SelfBounds(), gGraphBranchColor, kOctangle, Selected());

	// Draw the content of the node
	TEXTMETRIC tm;
	dc.GetTextMetrics(&tm);
	CPoint center = SelfBounds().CenterPoint();
	int nCenterX = center.x;
	int nCenterY = center.y - tm.tmHeight/2;
	CLogNodeBranch& branch = *(CLogNodeBranch*)fNode;
	dc.ExtTextOut(nCenterX, nCenterY, ETO_CLIPPED, SelfBounds(), *branch, (*branch).length(), NULL);
}

//////////////////////////////////////////////////////////////////////////
/// CGraphRectTracker

/// OnChangedRect virtual override
void CGraphRectTracker::OnChangedRect(const CRect& rectOld)
{
#if 0 // :-< doesn't work...
	CRect r;
	m_view->GetClientRect(&r);
	CPoint rb = m_rect.BottomRight();

	if( !r.PtInRect(rb) )
	{
		CPoint orig = m_view->GetDeviceScrollPosition();
		CPoint offset;
		if( rb.x >= r.right )
			offset.x += 10;

		if( rb.y >= r.bottom )
			offset.y += 10;

		orig += offset;
		m_rect -= offset;
		m_view->ScrollToPosition(orig);
	}
#endif
}

//////////////////////////////////////////////////////////////////////////
// TGraph

/*!
	Traverse the graph
	\param node Node
	\return kTGraph
*/
kTGraph TGraph::TraverseGraph(CLogNode* node)
{
	kTGraph res = TraverseNode(node);
	if( res == kTStop )
		return kTStop;

	if( res == kTGetDown )
	{
		vector<CLogNode*>::iterator i;
		for(i = node->Childs().begin(); i != node->Childs().end(); ++i)
		{
			res = TraverseGraph(*i);
			if( res == kTStop )
				return kTStop;
		}
	}

	if( node->Next() != 0L )
	{
		res = TraverseGraph(node->Next());
		if( res == kTStop )
			return kTStop;
	}

	return kTContinue;
};

//////////////////////////////////////////////////////////////////////////
// TGraphRectSelect

TGraphRectSelect::TGraphRectSelect(bool shiftOn, CGraphView* view, CRect& rect) 
	: m_shiftOn(shiftOn), m_view(view), m_rect(rect)
{
}

kTGraph TGraphRectSelect::TraverseNode(CLogNode* node)
{
	CWinLogData* data = (CWinLogData*)node->GetUserData();
	
	// No need to go further
	CRect inter;
	if( !inter.IntersectRect(m_rect, data->Bounds()) )
	{
		return kTContinue;
	}
	
	// Self enclosed in the tracker ?
	CRect& boundsRect = data->SelfBounds();
	if( m_rect.PtInRect(boundsRect.TopLeft()) && m_rect.PtInRect(boundsRect.BottomRight()) )
	{
		if( !m_shiftOn )
		{
			if( !data->Selected() )
				data->SetSelected(m_view, true);
		}
		else
			data->SetSelected(m_view, !data->Selected());
	}
	
	// Traverse childrens
	return kTGetDown;
}

//////////////////////////////////////////////////////////////////////////
// TGraphCmdStatus

TGraphCmdStatus::TGraphCmdStatus() 
	: m_fSel1(0L), m_fSel2(0L), m_selCount(0)
{
}

kTGraph TGraphCmdStatus::TraverseNode(CLogNode* node)
{
	CWinLogData* data = (CWinLogData*)node->GetUserData();
	
	if( data->Selected() )
	{
		if( m_fSel1 == 0L )
			m_fSel1 = data;
		else if( m_fSel2 == 0L )
			m_fSel2 = data;
		
		m_selCount++;
	}
	
	return kTGetDown;
}

/*!
	Get the selection count
	\return The selection count
*/
int TGraphCmdStatus::GetSelCount() const
{
	return m_selCount;
}

/*!
	Get the first selection
	\return The first selection
*/
CWinLogData* TGraphCmdStatus::GetSel1() const
{
	return m_fSel1;
}

/*!
	Get the second selection
	\return The second selection
*/
CWinLogData* TGraphCmdStatus::GetSel2() const
{
	return m_fSel2;
}

//////////////////////////////////////////////////////////////////////////
// CGraphConsole

CGraphConsole::CGraphConsole()
{
	CWincvsApp* app = (CWincvsApp*)AfxGetApp();
	m_view = app->GetConsoleView();
}

long CGraphConsole::cvs_out(char* txt, long len)
{
	if( m_view != 0L )
		m_view->OutConsole(txt, len);
	
	return len;
}

long CGraphConsole::cvs_err(char* txt, long len)
{
	if( m_view != 0L )
		m_view->OutConsole(txt, len, true);
	
	return len;
}

//////////////////////////////////////////////////////////////////////////
// TGraphDelCmd

TGraphDelCmd::TGraphDelCmd() 
	: m_countSel(0), m_onlyRev(true)
{
}

kTGraph TGraphDelCmd::TraverseNode(CLogNode* node)
{
	CWinLogData* data = (CWinLogData*)node->GetUserData();

	if( data->Selected() )
	{
		if( data->GetType() != kNodeRev )
			m_onlyRev = false;
		else
			m_allRevs.push_back(CStr(data->GetStr()));
	
		m_countSel++;
	}

	return kTGetDown;
}

/*!
	Get the selection count
	\return The selection count
*/
int TGraphDelCmd::GetCountSel() const
{
	return m_countSel;
}

/*!
	Get only revisions flag
	\return The only revisions flag
*/
bool TGraphDelCmd::GetOnlyRev() const
{
	return m_onlyRev;
}

/*!
	Get all revisions
	\return All revisions
*/
std::vector<CStr>& TGraphDelCmd::GetAllRevs()
{
	return m_allRevs;
}

//////////////////////////////////////////////////////////////////////////
// TGraphSelNonSig

TGraphSelNonSig::TGraphSelNonSig(CGraphView* view) 
	: m_view(view)
{
}

kTGraph TGraphSelNonSig::TraverseNode(CLogNode* node)
{
	CWinLogData* data = (CWinLogData*)node->GetUserData();
	
	if( data->GetType() == kNodeRev )
	{
		if( node->Next() != 0L && node->Childs().size() == 0 )
			data->SetSelected(m_view, true);
	}
	
	return kTGetDown;
}

//////////////////////////////////////////////////////////////////////////
// CRevisionConsole

CRevisionConsole::CRevisionConsole() 
    : m_fOut(NULL) 
{
}

CRevisionConsole::~CRevisionConsole() 
{
    if(m_fOut)
    {
        fclose(m_fOut);
    }
}

/*!
	Initialize console
	\param path File path
	\param fname File name
	\return true on success, false otherwise
*/
bool CRevisionConsole::init(LPCTSTR path, LPCTSTR fname)
{
    TCHAR buf[_MAX_PATH];
    _tmakepath(buf, NULL, path, fname, NULL);
    m_fOut = fopen(buf, "w+");
    
	return m_fOut != NULL;
}

long CRevisionConsole::cvs_out(char *txt, long len)
{
	fwrite(txt, sizeof(char), len, m_fOut);
	return len;
}

long CRevisionConsole::cvs_err(char *txt, long len)
{
	cvs_errstr(txt, len);
	return len;
}

/////////////////////////////////////////////////////////////////////////////
// CGraphView

CImageList CGraphView::m_imageList;

IMPLEMENT_DYNCREATE(CGraphView, CScrollView)

CGraphView::CGraphView()
{
	if( m_imageList.m_hImageList == 0L )
		m_imageList.Create(IDB_SMALLICONS, 16, 1, RGB(255, 255, 255));
}

CGraphView::~CGraphView()
{
}


BEGIN_MESSAGE_MAP(CGraphView, CScrollView)
	//{{AFX_MSG_MAP(CGraphView)
	ON_WM_LBUTTONDOWN()
	ON_COMMAND(ID_VIEW_RELOAD, OnViewReload)
	ON_UPDATE_COMMAND_UI(ID_VIEW_RELOAD, OnUpdateViewReload)
	ON_COMMAND(ID_VIEW_DIFF, OnViewDiff)
	ON_UPDATE_COMMAND_UI(ID_VIEW_DIFF, OnUpdateViewDiff)
	ON_COMMAND(ID_VIEW_UPDATE, OnViewUpdate)
	ON_UPDATE_COMMAND_UI(ID_VIEW_UPDATE, OnUpdateViewUpdate)
	ON_COMMAND(ID_VIEW_TAG, OnViewTag)
	ON_UPDATE_COMMAND_UI(ID_VIEW_TAG, OnUpdateViewTag)
	ON_COMMAND(ID_VIEW_DELREV, OnViewDelrev)
	ON_UPDATE_COMMAND_UI(ID_VIEW_DELREV, OnUpdateViewDelrev)
	ON_COMMAND(ID_VIEW_SELNONSIG, OnViewSelnonsig)
	ON_UPDATE_COMMAND_UI(ID_VIEW_SELNONSIG, OnUpdateViewSelnonsig)
	ON_COMMAND(ID_VIEW_RETRIEVE, OnViewRetrieve)
	ON_UPDATE_COMMAND_UI(ID_VIEW_RETRIEVE, OnUpdateViewRetrieve)
	ON_UPDATE_COMMAND_UI(ID_VIEW_RECURSIVE, DisableUpdate)
	ON_WM_CONTEXTMENU()
	ON_COMMAND(ID_QUERY_ANNOTATE, OnQueryAnnotate)
	ON_UPDATE_COMMAND_UI(ID_QUERY_ANNOTATE, OnUpdateQueryAnnotate)
	ON_COMMAND(ID_GRAPH_GRAPHOPTIONS, OnGraphGraphoptions)
	ON_UPDATE_COMMAND_UI(ID_GRAPH_CHANGELOGMESSAGE, OnUpdateGraphChangelogmessage)
	ON_COMMAND(ID_GRAPH_CHANGELOGMESSAGE, OnGraphChangelogmessage)
	ON_UPDATE_COMMAND_UI(ID_GRAPH_SETDESCRIPTION, OnUpdateGraphSetdescription)
	ON_COMMAND(ID_GRAPH_SETDESCRIPTION, OnGraphSetdescription)
	ON_COMMAND(ID_GRAPH_SETSTATE, OnGraphSetstate)
	ON_UPDATE_COMMAND_UI(ID_GRAPH_SETSTATE, OnUpdateGraphSetstate)
	ON_COMMAND(ID_VIEW_UNLOCKF, OnViewUnlockf)
	ON_UPDATE_COMMAND_UI(ID_VIEW_MISSING, DisableUpdate)
	ON_UPDATE_COMMAND_UI(ID_VIEW_MODIFIED, DisableUpdate)
	ON_UPDATE_COMMAND_UI(ID_VIEW_CONFLICT, DisableUpdate)
	ON_UPDATE_COMMAND_UI(ID_VIEW_UNKNOWN, DisableUpdate)
	ON_UPDATE_COMMAND_UI(ID_VIEW_HIDEUNKNOWN, DisableUpdate)
	ON_UPDATE_COMMAND_UI(ID_VIEW_IGNORE, DisableUpdate)
	ON_UPDATE_COMMAND_UI(ID_VIEW_ADDED, DisableUpdate)
	ON_UPDATE_COMMAND_UI(ID_VIEW_REMOVED, DisableUpdate)
	ON_UPDATE_COMMAND_UI(ID_VIEW_CHANGED, DisableUpdate)
	ON_UPDATE_COMMAND_UI(ID_FILTERMASK, DisableUpdate)
	ON_UPDATE_COMMAND_UI(ID_VIEW_UNLOCKF, OnUpdateViewUnlockf)
	//}}AFX_MSG_MAP
	ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)
	ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)
	ON_WM_INITMENUPOPUP()
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CGraphView drawing

/// Calculate the size of the graph and set the scroll bars
void CGraphView::CalcImageSize(void)
{
	const CPoint startPoint(STARTPOINT_PT);
	CWindowDC dc(this);
	CGraphDoc* pDoc = (CGraphDoc*)GetDocument();
	
	CLogNode* root = pDoc->GetNode();
	if( root == 0L )
		return;

	// Set all the bounds locations
	CWinLogData* data = CWinLogData::CreateNewData(root);
	root->SetUserData(data);
	data->ComputeBounds(startPoint, dc, pDoc->GetEntryInfo());

	// Re-offset from (0, 0)
	CRect bounds = data->Bounds();
	data->Offset(startPoint - bounds.TopLeft());
	bounds = data->Bounds();

	// Set the scroll size, we add the margin of the start point offset on each side
	SetScrollSizes(MM_TEXT, bounds.Size() + CSize(startPoint)+CSize(startPoint)+CSize(SHADOW_OFFSET_PT));
}

/// OnInitialUpdate virtual override, set the initial scroll sizes
void CGraphView::OnInitialUpdate()
{
	CScrollView::OnInitialUpdate();

	SetScrollSizes(MM_TEXT, CSize(200, 100));
	//CalcImageSize();
}

/// OnDraw virtual override, paint the graph
void CGraphView::OnDraw(CDC* pDC)
{
	CGraphDoc* pDoc = (CGraphDoc*)GetDocument();
	CLogNode* root = pDoc->GetNode();
	if( root == 0L || root->GetUserData() == 0L )
		return;

	CWinLogData* data = (CWinLogData*)root->GetUserData();

	pDC->SetTextAlign(TA_CENTER);
	pDC->SetBkMode(TRANSPARENT);
	data->Update(*pDC, pDoc->GetEntryInfo());
}

/////////////////////////////////////////////////////////////////////////////
// CGraphView diagnostics

#ifdef _DEBUG
void CGraphView::AssertValid() const
{
	CScrollView::AssertValid();
}

void CGraphView::Dump(CDumpContext& dc) const
{
	CScrollView::Dump(dc);
}
#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////
// CGraphView message handlers

/// WM_LBUTTONDOWN message handler, update selection
void CGraphView::OnLButtonDown(UINT nFlags, CPoint point) 
{
	CGraphDoc* pDoc = (CGraphDoc*)GetDocument();
	CLogNode* root = pDoc->GetNode();
	if( root == 0L || root->GetUserData() == 0L )
		return;

	CWinLogData* data = (CWinLogData*)root->GetUserData();

	if( nFlags & MK_LBUTTON )
	{
		if((nFlags & MK_SHIFT) == 0 && (nFlags & MK_CONTROL) == 0)
			data->UnselectAll(this);
		
		point += GetDeviceScrollPosition();
		
		CWinLogData* hit = data->HitTest(point);
		if( hit != 0L )
		{
			if( (nFlags & MK_SHIFT) == 0 && (nFlags & MK_CONTROL) == 0 )
			{
				if( !hit->Selected() )
				{
					hit->SetSelected(this, true);
					CWincvsApp* app = (CWincvsApp*)AfxGetApp();
					CWincvsView* view = app->GetConsoleView();
					
					if( view != 0L )
					{
						CColorConsole out(view);
						CvsLogOutput(out, hit->Node());
					}
				}
			}
			else
			{
				hit->SetSelected(this, !hit->Selected());
			}
		}
		else
		{
			CGraphRectTracker track(this);
			point -= GetDeviceScrollPosition();
			if( track.TrackRubberBand(this, point, true) )
			{
				// Traverse and mark
				track.m_rect.OffsetRect(GetDeviceScrollPosition());
				track.m_rect.NormalizeRect();
				
				TGraphRectSelect traversal((nFlags & MK_CONTROL) != 0, this, track.m_rect);
				traversal.TraverseGraph(root);
			}
		}
	}
	else if( nFlags & MK_RBUTTON )
	{
	}

	CScrollView::OnLButtonDown(nFlags, point);
}

/// WM_COMMAND message handler, reload view
void CGraphView::OnViewReload() 
{
	CGraphDoc* pDoc = (CGraphDoc*)GetDocument();
	CLogNode* root = pDoc->GetNode();
	if( root == 0L || root->GetUserData() == 0L )
		return;

	CvsArgs args;
	args.add("log");
	args.add(pDoc->GetName());
	args.print(pDoc->GetDir());
	CStr dir(pDoc->GetDir());
	CvsLogParse(dir, args, true, pDoc);
}

/// Update command UI state
void CGraphView::OnUpdateViewReload(CCmdUI* pCmdUI)
{
	if( DisableCommon() )
		pCmdUI->Enable(FALSE);
	else
		pCmdUI->Enable(TRUE);
}

/// WM_COMMAND message handler, diff the selection
void CGraphView::OnViewDiff() 
{
	CGraphDoc* pDoc = (CGraphDoc*)GetDocument();
	CLogNode* root = pDoc->GetNode();
	if( root == NULL || root->GetUserData() == NULL || pDoc == NULL )
		return;

	TGraphCmdStatus trav;
	trav.TraverseGraph(root);

	MultiFiles mf;
	
	mf.newdir(pDoc->GetDir());
	mf.newfile(pDoc->GetName(), 0L, trav.GetSel1()->GetStr());

	if( trav.GetSelCount() == 2 )
	{
		mf.newfile(pDoc->GetName(), 0L, trav.GetSel2()->GetStr());
	}

	KoGraphDiffHandler handler;
	handler.OnFiles(&mf);
}

/// Update command UI state
void CGraphView::OnUpdateViewDiff(CCmdUI* pCmdUI) 
{
	CGraphDoc* pDoc = (CGraphDoc*)GetDocument();
	CLogNode* root = pDoc->GetNode();
	if( DisableCommon() )
	{
		pCmdUI->Enable(FALSE);
		return;
	}

	TGraphCmdStatus trav;
	trav.TraverseGraph(root);
	
	const BOOL outEnabled = (trav.GetSelCount() == 2 && trav.GetSel1()->GetType() != kNodeHeader &&
		trav.GetSel2()->GetType() != kNodeHeader) ||
		(trav.GetSelCount() == 1 && trav.GetSel1()->GetType() != kNodeHeader);

	pCmdUI->Enable(outEnabled);
}

/// WM_COMMAND message handler, update
void CGraphView::OnViewUpdate() 
{
	CGraphDoc* pDoc = (CGraphDoc*)GetDocument();
	CLogNode* root = pDoc->GetNode();
	if(root == 0L || root->GetUserData() == 0L)
		return;

	CvsArgs args;
	args.add("update");
	args.add("-A");
	args.add(pDoc->GetName());
	args.print(pDoc->GetDir());
	CGraphConsole cons;
	launchCVS(pDoc->GetDir(), args.Argc(), args.Argv(), &cons);
	
	OnViewReload();
}

/// Update command UI state
void CGraphView::OnUpdateViewUpdate(CCmdUI* pCmdUI) 
{
	if( DisableCommon() )
		pCmdUI->Enable(FALSE);
	else
		pCmdUI->Enable(TRUE);
}

/// WM_COMMAND message handler, tag update
void CGraphView::OnViewTag() 
{
	CGraphDoc* pDoc = (CGraphDoc*)GetDocument();
	CLogNode* root = pDoc->GetNode();
	
	if( root == 0L || root->GetUserData() == 0L )
		return;

	TGraphCmdStatus trav;
	trav.TraverseGraph(root);
	
	CvsArgs args;
	args.add("update");
	
	CLogNode* node = trav.GetSel1()->Node();
	switch(node->GetType())
	{
	case kNodeHeader:
		{
			args.add("-A");
			break;
		}
	case kNodeRev:
	case kNodeBranch:
	case kNodeTag:
		{
			args.add("-r");
			args.add(trav.GetSel1()->GetStr());
			break;
		}
	}

	args.add(pDoc->GetName());
	args.print(pDoc->GetDir());
	
	CGraphConsole cons;
	launchCVS(pDoc->GetDir(), args.Argc(), args.Argv(), &cons);
	
	OnViewReload();
}

/// Update command UI state
void CGraphView::OnUpdateViewTag(CCmdUI* pCmdUI) 
{
	CGraphDoc* pDoc = (CGraphDoc*)GetDocument();
	CLogNode* root = pDoc->GetNode();

	if( DisableCommon() )
	{
		pCmdUI->Enable(FALSE);
		return;
	}

	TGraphCmdStatus trav;
	trav.TraverseGraph(root);
	
	const BOOL outEnabled = trav.GetSelCount() == 1;
	pCmdUI->Enable(outEnabled);
}

/// OnBeginPrinting virtual override
void CGraphView::OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo) 
{
	CScrollView::OnBeginPrinting(pDC, pInfo);

	CGraphDoc* pDoc = (CGraphDoc*)GetDocument();
	CLogNode* root = pDoc->GetNode();
	if( root == 0L )
		return;

	// set all the bounds locations
	CWinLogData* data = CWinLogData::CreateNewData(root);
	root->SetUserData(data);
	CPoint start(0, 0);
	data->ComputeBounds(start, *pDC, pDoc->GetEntryInfo());

	// compute the number of pages
	CRect bounds = data->Bounds();
	m_pageHeight = pDC->GetDeviceCaps(VERTRES);
	m_numberOfPages = bounds.Height() / m_pageHeight + 1;

	pInfo->SetMaxPage(m_numberOfPages);
}

/// OnEndPrinting virtual override
void CGraphView::OnEndPrinting(CDC* pDC, CPrintInfo* pInfo) 
{
	CScrollView::OnEndPrinting(pDC, pInfo);

	CalcImageSize();
	Invalidate();
}

/// OnPreparePrinting virtual override
BOOL CGraphView::OnPreparePrinting(CPrintInfo* pInfo) 
{
	BOOL res = DoPreparePrinting(pInfo);
	if( !res )
		return res;

	return TRUE;
}

/// OnPrint virtual override
void CGraphView::OnPrint(CDC* pDC, CPrintInfo* pInfo) 
{
	if( !pDC->IsPrinting() ) 
		return;

	CGraphDoc* pDoc = (CGraphDoc*)GetDocument();
	CLogNode* root = pDoc->GetNode();
	if( root == 0L || root->GetUserData() == 0L )
		return;

	CWinLogData* data = (CWinLogData*)root->GetUserData();
	
	// Orient the viewport so that the first row to be printed
	// has a viewport coordinate of (0,0).
	int yTopOfPage = (pInfo->m_nCurPage -1) * m_pageHeight;
	pDC->SetViewportOrg(0, -yTopOfPage);
	pDC->SetTextAlign(TA_CENTER);
	pDC->SetBkMode(TRANSPARENT);
	data->Update(*pDC, pDoc->GetEntryInfo());
	
	//	CScrollView::OnPrint(pDC, pInfo);
}

/*!
	Check whether the commands should be disabled
	\return true if commands should be disabled, false otherwise
*/
bool CGraphView::DisableCommon()
{
	CWincvsApp* app = (CWincvsApp*)AfxGetApp();
	CGraphDoc* pDoc = (CGraphDoc*)GetDocument();
	CLogNode* root = pDoc->GetNode();

	return root == 0L || root->GetUserData() == 0L || app->IsCvsRunning() || this != CWnd::GetFocus() || gCvsPrefs.empty();
}

/*!
	Check whether a single node is selected
	\param kLogNode Node type
	\param equal true if selection should be equal, false otherwise
	\return true if the selection is a single node, false otherwise
*/
bool CGraphView::IsSelSingleNode(kLogNode type, bool equal /*= true*/)
{
	CGraphDoc* pDoc = (CGraphDoc*)GetDocument();
	CLogNode* root = pDoc->GetNode();
	
	TGraphCmdStatus trav;
	trav.TraverseGraph(root);
	
	return (trav.GetSelCount() == 1 && 
		(equal ? trav.GetSel1()->GetType() == type : trav.GetSel1()->GetType() != type));
}

/// WM_COMMAND message handler, delete selected revisions
void CGraphView::OnViewDelrev() 
{
	CGraphDoc* pDoc = (CGraphDoc*)GetDocument();
	CLogNode* root = pDoc->GetNode();
	if( root == 0L || root->GetUserData() == 0L )
		return;

	if( AfxMessageBox("Are you really sure you want to do that ? This will permanently remove the revisions on the server.",
		MB_OKCANCEL|MB_DEFBUTTON2|MB_ICONSTOP) != IDOK )
	{
		return;
	}

	TGraphDelCmd trav;
	trav.TraverseGraph(root);

	CProgress progress(true, "Deleting revisions...");

	std::vector<CStr>::const_iterator i;
	int count = 0;
	for(i = trav.GetAllRevs().begin(); i != trav.GetAllRevs().end(); ++i, ++count)
	{
		progress.SetProgress((int)(((float)count / (float)trav.GetAllRevs().size()) * 100.0 + .5));
		if( progress.HasAborted() )
			break;

		CvsArgs args;
		args.add("admin");
		args.add("-o");
		args.add(*i);
		args.add(pDoc->GetName());
		args.print(pDoc->GetDir());
		
		CGraphConsole cons;
		launchCVS(pDoc->GetDir(), args.Argc(), args.Argv(), &cons);
	}
	
	if( !progress.HasAborted() )
		progress.SetProgress(100);

	OnViewReload();
}

/// Update command UI state
void CGraphView::OnUpdateViewDelrev(CCmdUI* pCmdUI) 
{
	CGraphDoc* pDoc = (CGraphDoc*)GetDocument();
	CLogNode* root = pDoc->GetNode();
	if( DisableCommon() )
	{
		pCmdUI->Enable(FALSE);
		return;
	}

	TGraphDelCmd trav;
	trav.TraverseGraph(root);
	BOOL outEnabled = trav.GetCountSel() >= 1 && trav.GetOnlyRev();
	pCmdUI->Enable(outEnabled);
}

/// WM_COMMAND message handler, select non-significant revisions
void CGraphView::OnViewSelnonsig() 
{
	CGraphDoc* pDoc = (CGraphDoc*)GetDocument();
	CLogNode* root = pDoc->GetNode();
	if( root == 0L || root->GetUserData() == 0L )
		return;

	CWinLogData* data = (CWinLogData*)root->GetUserData();
	data->UnselectAll(this);

	TGraphSelNonSig trav(this);
	trav.TraverseGraph(root);
}

/// Update command UI state
void CGraphView::OnUpdateViewSelnonsig(CCmdUI* pCmdUI) 
{
	if( DisableCommon() )
		pCmdUI->Enable(FALSE);
	else
		pCmdUI->Enable(TRUE);
}

/// WM_COMMAND message handler, retrieve revisions
void CGraphView::OnViewRetrieve() 
{
	CGraphDoc* pDoc = (CGraphDoc*)GetDocument();
	CLogNode* root = pDoc->GetNode();
	if( root == 0L || root->GetUserData() == 0L )
		return;

	TGraphCmdStatus trav;
	trav.TraverseGraph(root);

	CStr f1, prf1;
	CvsArgs args;

	prf1 = pDoc->GetName();
	prf1 << ".#.";
	prf1 << trav.GetSel1()->GetStr();
	args.add("update");
	args.add("-p");
	args.add("-r");
	args.add(trav.GetSel1()->GetStr());
	args.add(pDoc->GetName());
	args.print(pDoc->GetDir());

    CRevisionConsole console;
    console.init(pDoc->GetDir(), prf1); 

    int code = launchCVS(pDoc->GetDir(), args.Argc(), args.Argv(), &console);
}

/// Update command UI state
void CGraphView::OnUpdateViewRetrieve(CCmdUI* pCmdUI) 
{
	BOOL enabled = FALSE;
	if( !DisableCommon() )
	{
		enabled = IsSelSingleNode(kNodeHeader, false);
	}

	pCmdUI->Enable(enabled);
}

/// Update command UI state, disable command
void CGraphView::DisableUpdate(CCmdUI* pCmdUI) 
{
	pCmdUI->Enable(false);	
}

/*!
	Display context menu
	\param pWnd Window
	\param point Mouse position
*/
void CGraphView::OnContextMenu(CWnd* pWnd, CPoint point) 
{
	// Comply with windows UI standard (Windows Explorer file view used as a reference),
	// change selections according to the state of Control and Shift keys
	BYTE abKeyboardState[256];
	BOOL bControlState = FALSE;
	BOOL bShiftState = FALSE;
	memset( abKeyboardState, 0, 256 );
	
	if( GetKeyboardState(abKeyboardState) )
	{
		bControlState = abKeyboardState[VK_CONTROL] & 0x80;
		bShiftState = abKeyboardState[VK_SHIFT] & 0x80;
	}
	
    if( (!bControlState || (bControlState && bShiftState)) && !DisableCommon() )
	{	
		CGraphDoc* pDoc = (CGraphDoc*)GetDocument();
		CLogNode* root = pDoc->GetNode();
		
		TGraphCmdStatus trav;
		trav.TraverseGraph(root);

		CWinLogData* data = (CWinLogData*)root->GetUserData();
		CPoint hitPoint(point);
		ScreenToClient(&hitPoint);
		hitPoint += GetDeviceScrollPosition();
		CWinLogData* hit = data->HitTest(hitPoint);

		if( hit != 0L )
		{
			if( trav.GetSelCount() == 0 )
			{
				hit->SetSelected(this, true);
			}
			else if( trav.GetSelCount() >= 1 && trav.GetSel1()->GetType() != kNodeHeader )
			{
				if( !(hit->Selected() && bShiftState) && !(bControlState && bShiftState && hit->Selected()) )
				{
					data->UnselectAll(this);
				}

				hit->SetSelected(this, true);
			}
		}
    }
	
	// Get the menu to display from the main app window, which for graph view
	// will be IDR_GRAPH,  The submenu to display is the menu under "Graph".
	// Note that if the position of this menu changes, the index under 
	// GetSubMenu must also change.
	CMenu* pMenu    = ::AfxGetMainWnd()->GetMenu();
	CMenu* pSubMenu = pMenu->GetSubMenu(5);

	pSubMenu->TrackPopupMenu(TPM_LEFTALIGN|TPM_LEFTBUTTON|TPM_RIGHTBUTTON, point.x, point.y, this);
}

/// WM_INITMENUPOPUP message handler
void CGraphView::OnInitMenuPopup(CMenu* pPopupMenu, UINT nIndex, BOOL bSysMenu)
{
    ASSERT(pPopupMenu != NULL);
    // Check the enabled state of various menu items.
	
    CCmdUI state;
    state.m_pMenu = pPopupMenu;
    ASSERT(state.m_pOther == NULL);
    ASSERT(state.m_pParentMenu == NULL);
	
    // Determine if menu is popup in top-level menu and set m_pOther to
    // it if so (m_pParentMenu == NULL indicates that it is secondary popup).
    HMENU hParentMenu;
    if( AfxGetThreadState()->m_hTrackingMenu == pPopupMenu->m_hMenu )
        state.m_pParentMenu = pPopupMenu;    // Parent == child for tracking popup.
    else if( (hParentMenu = ::GetMenu(m_hWnd)) != NULL )
    {
        CWnd* pParent = this;
		// Child windows don't have menus--need to go to the top!
        if( pParent != NULL &&
			(hParentMenu = ::GetMenu(pParent->m_hWnd)) != NULL )
        {
			int nIndexMax = ::GetMenuItemCount(hParentMenu);
			for(int nIndex = 0; nIndex < nIndexMax; nIndex++)
			{
				if( ::GetSubMenu(hParentMenu, nIndex) == pPopupMenu->m_hMenu )
				{
					// When popup is found, m_pParentMenu is containing menu.
					state.m_pParentMenu = CMenu::FromHandle(hParentMenu);
					break;
				}
			}
        }
    }
	
    state.m_nIndexMax = pPopupMenu->GetMenuItemCount();
    for(state.m_nIndex = 0; state.m_nIndex < state.m_nIndexMax; state.m_nIndex++)
    {
        state.m_nID = pPopupMenu->GetMenuItemID(state.m_nIndex);
        if( state.m_nID == 0 )
			continue; // Menu separator or invalid cmd - ignore it.
		
        ASSERT(state.m_pOther == NULL);
        ASSERT(state.m_pMenu != NULL);
        if( state.m_nID == (UINT)-1 )
        {
			// Possibly a popup menu, route to first item of that popup.
			state.m_pSubMenu = pPopupMenu->GetSubMenu(state.m_nIndex);
			if( state.m_pSubMenu == NULL ||
				(state.m_nID = state.m_pSubMenu->GetMenuItemID(0)) == 0 ||
				state.m_nID == (UINT)-1 )
			{
				continue;       // First item of popup can't be routed to.
			}

			state.DoUpdate(this, TRUE);   // Popups are never auto disabled.
        }
        else
        {
			// Normal menu item.
			// Auto enable/disable if frame window has m_bAutoMenuEnable
			// set and command is _not_ a system command.
			state.m_pSubMenu = NULL;
			state.DoUpdate(this, FALSE);
        }
		
        // Adjust for menu deletions and additions.
        UINT nCount = pPopupMenu->GetMenuItemCount();
        if( nCount < state.m_nIndexMax )
        {
			state.m_nIndex -= (state.m_nIndexMax - nCount);
			while( state.m_nIndex < nCount &&
				pPopupMenu->GetMenuItemID(state.m_nIndex) == state.m_nID )
			{
				state.m_nIndex++;
			}
        }

        state.m_nIndexMax = nCount;
	}
}

/// Update command UI state
void CGraphView::OnUpdateQueryAnnotate(CCmdUI* pCmdUI) 
{
	BOOL enabled = FALSE;
	if( !DisableCommon() )
	{
		enabled = IsSelSingleNode(kNodeHeader, false);
	}

	pCmdUI->Enable(enabled);
}

/// WM_COMMAND message handler, annotate selection
void CGraphView::OnQueryAnnotate() 
{
	CGraphDoc* pDoc = (CGraphDoc*)GetDocument();
	CLogNode* root = pDoc->GetNode();
	if( root == 0L || root->GetUserData() == 0L )
		return;

	TGraphCmdStatus trav;
	trav.TraverseGraph(root);

	if( gCvsPrefs.ExtDiff() != 0L )
	{
		CStr file, prf, ext, base;
		CvsArgs args;

		GetExtension(pDoc->GetName(), base, ext);

		prf = base;
		prf << "_ann_";
		prf << trav.GetSel1()->GetStr();
		prf << '_';
		args.add("annotate");
		args.add("-r");
		args.add(trav.GetSel1()->GetStr());
		args.add(pDoc->GetName());
		args.print(pDoc->GetDir());
		file = launchCVS(pDoc->GetDir(), args, prf.c_str(), ext);
		
		if( file.empty() )
			return;
		
		LaunchViewer(file);
	}
}

/// WM_COMMAND message handler, display graph options menu
void CGraphView::OnGraphGraphoptions() 
{
	if( CompatGetGraphOptions(gGraphSelColor, gGraphShadowColor, 
		gGraphHeaderColor, gGraphTagColor, gGraphBranchColor, 
		gGraphNodeColor, gGraphDeadNodeColor, gGraphUserStateNodeColor) )
	{
		// add any additional processing here
	}

	CWincvsApp* pApp = (CWincvsApp*)AfxGetApp();
	if( pApp )
	{
		pApp->UpdateAllDocsView();
	}
}

/// Update command UI state
void CGraphView::OnUpdateGraphChangelogmessage(CCmdUI* pCmdUI) 
{
	BOOL enabled = FALSE;
	if( !DisableCommon() )
	{
		enabled = IsSelSingleNode(kNodeRev);
	}

	pCmdUI->Enable(enabled);
}

/// WM_COMMAND message handler, change the log message
void CGraphView::OnGraphChangelogmessage() 
{
	CGraphDoc* pDoc = (CGraphDoc*)GetDocument();
	CLogNode* root = pDoc->GetNode();
	if( root == 0L || root->GetUserData() == 0L )
		return;
	
	TGraphCmdStatus trav;
	trav.TraverseGraph(root);
	
	CLogNode* node = trav.GetSel1()->Node();
	ASSERT(node->GetType() == kNodeRev);
	CRevFile& rev = **(CLogNodeRev*)node;
	
	CPStr msg = (const char*)rev.DescLog();
	
	if( CompatGetAdminOptions(kChangeLog, msg) )
	{
		CvsArgs args;
		args.add("admin");
		args.add(CStr("-m") + trav.GetSel1()->GetStr() + CStr(":") + CleanupLogMsg(msg));
		
		args.add(pDoc->GetName());
		args.print(pDoc->GetDir());
		
		CGraphConsole cons;
		launchCVS(pDoc->GetDir(), args.Argc(), args.Argv(), &cons);
		OnViewReload();
	}
}

/// Update command UI state
void CGraphView::OnUpdateGraphSetdescription(CCmdUI* pCmdUI) 
{
	BOOL enabled = FALSE;
	if( !DisableCommon() )
	{
		enabled = IsSelSingleNode(kNodeHeader);
	}

	pCmdUI->Enable(enabled);
}

/// WM_COMMAND message handler, set the description
void CGraphView::OnGraphSetdescription() 
{
	CGraphDoc* pDoc = (CGraphDoc*)GetDocument();
	CLogNode* root = pDoc->GetNode();
	if( root == 0L || root->GetUserData() == 0L )
		return;
	
	TGraphCmdStatus trav;
	trav.TraverseGraph(root);
	
	CLogNode* node = trav.GetSel1()->Node();
	ASSERT(node->GetType() == kNodeHeader);
	CRcsFile& rev = **(CLogNodeHeader*)node;
	
	CPStr msg = (const char*)rev.DescLog();
	
	if( CompatGetAdminOptions(kSetDescription, msg) )
	{
		CvsArgs args;
		args.add("admin");
		args.add(CStr("-t-") + CleanupLogMsg(msg));
		
		args.add(pDoc->GetName());
		args.print(pDoc->GetDir());
		
		CGraphConsole cons;
		launchCVS(pDoc->GetDir(), args.Argc(), args.Argv(), &cons);
		OnViewReload();
	}
}

/// Update command UI state
void CGraphView::OnUpdateGraphSetstate(CCmdUI* pCmdUI) 
{
	BOOL enabled = FALSE;
	if( !DisableCommon() )
	{
		enabled = IsSelSingleNode(kNodeRev);
	}

	pCmdUI->Enable(enabled);
}

/// WM_COMMAND message handler, set the state
void CGraphView::OnGraphSetstate() 
{
	CGraphDoc* pDoc = (CGraphDoc*)GetDocument();
	CLogNode* root = pDoc->GetNode();
	if( root == 0L || root->GetUserData() == 0L )
		return;
	
	TGraphCmdStatus trav;
	trav.TraverseGraph(root);

	CLogNode* node = trav.GetSel1()->Node();
	ASSERT(node->GetType() == kNodeRev);
	CRevFile& rev = **(CLogNodeRev*)node;

	CPStr state = (const char*)rev.State();

	if( strcmp("dead", (const char*)state ) == 0 &&
		CvsAlert("The revision state is: \"dead\". You should not alter it manually.\nAre you sure you want to continue?", "Cancel", "Continue") )
	{
		return;
	}
	
	if( CompatGetAdminOptions(kSetState, state) )
	{
		CvsArgs args;
		args.add("admin");
		args.add("-s");
		args.add(CleanupLogMsg(state) + CStr(":") + trav.GetSel1()->GetStr());
		
		args.add(pDoc->GetName());
		args.print(pDoc->GetDir());
		
		CGraphConsole cons;
		launchCVS(pDoc->GetDir(), args.Argc(), args.Argv(), &cons);
		OnViewReload();
	}
}

/// Update command UI state
void CGraphView::OnUpdateViewUnlockf(CCmdUI* pCmdUI) 
{
	BOOL enabled = FALSE;
	if( !DisableCommon() )
	{
		enabled = IsSelSingleNode(kNodeRev);
	}

	pCmdUI->Enable(enabled);
}

/// WM_COMMAND message handler, unlock the selection
void CGraphView::OnViewUnlockf() 
{
	CGraphDoc* pDoc = (CGraphDoc*)GetDocument();
	CLogNode* root = pDoc->GetNode();
	if( root == 0L || root->GetUserData() == 0L )
		return;
	
	TGraphCmdStatus trav;
	trav.TraverseGraph(root);
	
	CvsArgs args;
	args.add("admin");
	args.add(CStr("-u") + trav.GetSel1()->GetStr());
	
	args.add(pDoc->GetName());
	args.print(pDoc->GetDir());
	
	CGraphConsole cons;
	launchCVS(pDoc->GetDir(), args.Argc(), args.Argv(), &cons);
	OnViewReload();
}
