/*
** 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 "CvsLog.h"
#include "AppConsole.h"
#include "LogParse.h"
#include "CvsEntries.h"
#include "BrowseFileView.h"
#include "WinCvsBrowser.h"
#include "CvsArgs.h"
#include "AppGlue.h"
#include "WincvsView.h"
#include "CvsPrefs.h"
#include "FileTraversal.h"
#include "ProgressDialog.h"

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

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

IMPLEMENT_DYNCREATE(CGraphView, CScrollView)

CGraphView::CGraphView()
{
	fFirstTime = true;
	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_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_COMMAND(ID_QUERY_ANNOTATE, OnQueryAnnotate)
	ON_UPDATE_COMMAND_UI(ID_QUERY_ANNOTATE, OnUpdateQueryAnnotate)
	//}}AFX_MSG_MAP
	ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)
	ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)
	ON_WM_INITMENUPOPUP()
END_MESSAGE_MAP()

class CWinLogData : public CLogNodeData
{
public:
	CWinLogData(CLogNode *node);
	virtual ~CWinLogData();

	inline kLogNode GetType(void) const { return fNode->GetType(); }
	const char *GetStr(void);

	void ComputeBounds(const CPoint & topLeft, CDC & hdc,
		EntnodeData *entryInfo = 0L);
	void Offset(CPoint o);
	void Update(CDC & hdc, EntnodeData *entryInfo = 0L);
	
	inline CRect & Bounds(void) { return totB; }
	inline CRect & SelfBounds(void) { return selfB; }

	inline bool Selected(void) const { return sel; }
	void SetSelected(CScrollView *view, bool state);
	void UnselectAll(CScrollView *view);
	CWinLogData *HitTest(CPoint point);

	static CWinLogData *CreateNewData(CLogNode *node);

	bool IsDiskNode(EntnodeData *entryInfo);

protected:
	virtual void UpdateSelf(CDC & hdc, EntnodeData *entryInfo) = 0;
	virtual CSize GetBoundsSelf(CDC & hdc, EntnodeData *entryInfo) = 0;

	inline CWinLogData *GetData(CLogNode *node)
		{ return (CWinLogData *)node->GetUserData(); }

	CRect selfB;
	CRect totB;
	bool sel;
};

class CWinLogHeaderData : public CWinLogData
{
public:
	CWinLogHeaderData(CLogNode *node) : CWinLogData(node) {}
	virtual ~CWinLogHeaderData() {}

protected:
	virtual void UpdateSelf(CDC & hdc, EntnodeData *entryInfo);
	virtual CSize GetBoundsSelf(CDC & hdc, EntnodeData *entryInfo);
};

class CWinLogRevData : public CWinLogData
{
public:
	CWinLogRevData(CLogNode *node) : CWinLogData(node) {}
	virtual ~CWinLogRevData() {}
	
protected:
	virtual void UpdateSelf(CDC & hdc, EntnodeData *entryInfo);
	virtual CSize GetBoundsSelf(CDC & hdc, EntnodeData *entryInfo);
};

class CWinLogTagData : public CWinLogData
{
public:
	CWinLogTagData(CLogNode *node) : CWinLogData(node) {}
	virtual ~CWinLogTagData() {}
	
protected:
	virtual void UpdateSelf(CDC & hdc, EntnodeData *entryInfo);
	virtual CSize GetBoundsSelf(CDC & hdc, EntnodeData *entryInfo);
};

class CWinLogBranchData : public CWinLogData
{
public:
	CWinLogBranchData(CLogNode *node) : CWinLogData(node) {}
	virtual ~CWinLogBranchData() {}
	
protected:
	virtual void UpdateSelf(CDC & hdc, EntnodeData *entryInfo);
	virtual CSize GetBoundsSelf(CDC & hdc, EntnodeData *entryInfo);
};

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

static void DrawNode(CDC & hdc, const CRect & selfB,
					 COLORREF contour, COLORREF shadowc,
					 bool isRounded, bool issel)
{
	CPen *pOldPen = 0L;
	CBrush *pOldBrush = 0L;
	CPen pen, pen2;
	CBrush brush;
	COLORREF selcolor = RGB(160, 160, 160);

	TRY
	{
		if(!isRounded)
		{
			if(!issel)
			{
				pen.CreatePen(PS_SOLID, 1, contour);
				pOldPen = hdc.SelectObject(&pen);
				hdc.Rectangle(selfB);
			}
			else
			{
				brush.CreateSolidBrush(selcolor);
				pOldBrush = hdc.SelectObject(&brush);
				pen.CreatePen(PS_SOLID, 1, contour);
				pOldPen = hdc.SelectObject(&pen);
				hdc.Rectangle(selfB);
			}
		}
		else
		{
			CRect shadow;
			if(!issel)
			{
				brush.CreateSolidBrush(shadowc);
				pOldBrush = hdc.SelectObject(&brush);
				pen.CreatePen(PS_SOLID, 1, shadowc);
				pOldPen = hdc.SelectObject(&pen);
				shadow = selfB;
				shadow.OffsetRect(CPoint(2, 2));
				hdc.RoundRect(shadow, CPoint(6, 6));
				hdc.SelectObject(pOldBrush);
				pOldBrush = 0L;

				pen2.CreatePen(PS_SOLID, 1, contour);
				hdc.SelectObject(&pen2);
				hdc.RoundRect(selfB, CPoint(6, 6));
			}
			else
			{
				brush.CreateSolidBrush(shadowc);
				pOldBrush = hdc.SelectObject(&brush);
				pen.CreatePen(PS_SOLID, 1, shadowc);
				pOldPen = hdc.SelectObject(&pen);
				shadow = selfB;
				shadow.OffsetRect(CPoint(2, 2));
				hdc.RoundRect(shadow, CPoint(6, 6));

				brush.DeleteObject();
				brush.CreateSolidBrush(selcolor);
				hdc.SelectObject(&brush);
				pen2.CreatePen(PS_SOLID, 1, contour);
				hdc.SelectObject(&pen2);
				hdc.RoundRect(selfB, CPoint(6, 6));
			}
		}

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

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

void CWinLogHeaderData::UpdateSelf(CDC & hdc, EntnodeData *entryInfo)
{
	// draw the node
	::DrawNode(hdc, selfB, RGB(255, 0, 0), RGB(120, 0, 0), true, sel);

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

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

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

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

	// draw the node
	::DrawNode(hdc, selfB, RGB(0, 0, 255), RGB(0, 0, 0), false, sel);

	// draw the state icon if it is the rev. of the disk
	if(IsDiskNode(entryInfo))
	{
		CImageList & list = CGraphView::GetImgList();
		CPoint w(selfB.TopLeft());
		w.x += 2;
		w.y += (selfB.Height() - 16) / 2;
		list.Draw(&hdc, CBrowseFileView::GetImageForEntry(entryInfo),
			w, ILD_TRANSPARENT);
	}

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

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

void CWinLogTagData::UpdateSelf(CDC & hdc, EntnodeData *entryInfo)
{
	// draw the node
	::DrawNode(hdc, selfB, RGB(0, 0, 0), RGB(120, 120, 120), true, sel);

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

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

void CWinLogBranchData::UpdateSelf(CDC & hdc, EntnodeData *entryInfo)
{
	// draw the node
	::DrawNode(hdc, selfB, RGB(0, 0, 255), RGB(0, 0, 120), true, sel);

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

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

CWinLogData::~CWinLogData()
{
}

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().str();
			break;
		}
		case kNodeTag :
		{
			CLogNodeTag & tag = *(CLogNodeTag *)fNode;
			return *tag;
			break;
		}
	}
	return 0L;
}

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;
}

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().str()) == 0;
}

const int kVChildSpace = 8;
const int kHChildSpace = 40;
const int kInflateNodeSpace = 8;

void CWinLogData::Offset(CPoint o)
{
	selfB.OffsetRect(o);
	totB.OffsetRect(o);
	vector<CLogNode *>::iterator i;
	for(i = fNode->Childs().begin(); i != fNode->Childs().end(); ++i)
	{
		CLogNode *subNode = *i;
		GetData(subNode)->Offset(o);
	}
	if(fNode->Next() != 0L)
		GetData(fNode->Next())->Offset(o);
}

void CWinLogData::SetSelected(CScrollView *view, bool state)
{
	sel = state;

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

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);
}

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;
}

void CWinLogData::Update(CDC & hdc, EntnodeData *entryInfo)
{
	CLogNode *subNode;
	CLogNode *nextNode = fNode->Next();
	CPen *pOldPen = 0L;
	CBrush *pOldBrush = 0L;
	CPen pen, pen2;
	CBrush brush;

	UpdateSelf(hdc, entryInfo);

	TRY
	{
		pen.CreatePen(PS_SOLID, 2, RGB(0, 0, 255));
		pen2.CreatePen(PS_SOLID, 1, RGB(0, 0, 0));
		pOldPen = hdc.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)
				hdc.SelectObject(&pen);
			else
				hdc.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;
			//int halfX = (cb.TopLeft().x - selfB.BottomRight().x) / 2;
			hdc.MoveTo(selfB.BottomRight().x, (int)y);
			hdc.LineTo(x, (int)y);
			hdc.LineTo(x, cb.CenterPoint().y);
			hdc.LineTo(cb.TopLeft().x, cb.CenterPoint().y);
			
			// draw the sub-node
			GetData(subNode)->Update(hdc, entryInfo);
		}

		// draw the next node
		if(nextNode != 0L)
		{
			// draw the link
			CRect & nb = GetData(nextNode)->SelfBounds();
			if(nextNode->GetType() == kNodeRev || nextNode->GetType() == kNodeBranch)
				hdc.SelectObject(&pen);
			else
				hdc.SelectObject(&pen2);
			int centerX = selfB.Width() < nb.Width() ?
				selfB.CenterPoint().x : nb.CenterPoint().x;
			hdc.MoveTo(centerX, selfB.BottomRight().y);
			hdc.LineTo(centerX, nb.TopLeft().y);
			
			// draw the next node
			GetData(nextNode)->Update(hdc, entryInfo);
		}

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

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

void CWinLogData::ComputeBounds(const CPoint & topLeft, CDC & hdc,
								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, hdc, entryInfo);
	}
	if(nextNode != 0L)
		CWinLogData::CreateNewData(nextNode);

	// compute self place
	selfB.SetRectEmpty();
	CSize size = GetBoundsSelf(hdc, 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);
	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, hdc, entryInfo);

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

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

CImageList CGraphView::m_ImageList;

void CGraphView::CalcImageSize(void)
{
	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);
	CPoint start(5, 5);
	data->ComputeBounds(start, dc, pDoc->GetEntryInfo());

	// reoffset from (0, 0)
	CRect bounds = data->Bounds();
	data->Offset(start - bounds.TopLeft());
	bounds = data->Bounds();

	// set the scroll size
	SetScrollSizes(MM_TEXT, bounds.Size() + CSize(5, 5));
}

void CGraphView::OnInitialUpdate()
{
	CScrollView::OnInitialUpdate();

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

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

class CGraphRectTracker : public CRectTracker
{
public:
	CGraphRectTracker(CGraphView *view) : mView(view) {}

	virtual void OnChangedRect(const CRect& rectOld);
protected:
	CGraphView *mView;
};

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

	if(!r.PtInRect(rb))
	{
		CPoint orig = mView->GetDeviceScrollPosition();
		CPoint offset;
		if(rb.x >= r.right)
			offset.x += 10;
		if(rb.y >= r.bottom)
			offset.y += 10;
		orig += offset;
		m_rect -= offset;
		mView->ScrollToPosition(orig);
	}
#endif
}

// traversal class
typedef enum
{
	kTContinue,
	kTGetDown,
	kTStop
} kTGraph;

class TGraph
{
public:

	kTGraph TraverseGraph(CLogNode * node);

	virtual kTGraph TraverseNode(CLogNode *node) = 0;
protected:
};

kTGraph TGraph::TraverseGraph(CLogNode * node)
{
	kTGraph res;

	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;
};

class TGraphRectSelect : public TGraph
{
public:
	TGraphRectSelect(bool shiftOn, CGraphView *view, CRect & r) :
		mShiftOn(shiftOn), mView(view), mR(r) {}

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

		// no need to go further
		CRect inter;
		if(!inter.IntersectRect(mR, data->Bounds()))
		{
			return kTContinue;
		}
		// self enclosed in the tracker ?
		CRect & b = data->SelfBounds();
		if(mR.PtInRect(b.TopLeft()) && mR.PtInRect(b.BottomRight()))
		{
			if(!mShiftOn)
			{
				if(!data->Selected())
					data->SetSelected(mView, true);
			}
			else
				data->SetSelected(mView, !data->Selected());
		}
		// traverse childrens
		return kTGetDown;
	}

protected:
	bool mShiftOn;
	CGraphView *mView;
	CRect & mR;
};

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

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);
}

class TGraphCmdStatus : public TGraph
{
public:
	TGraphCmdStatus() :
		fSel1(0L), fSel2(0L), fCountSel(0) {}

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

		if(data->Selected())
		{
			if(fSel1 == 0L)
				fSel1 = data;
			else if(fSel2 == 0L)
				fSel2 = data;
			fCountSel++;
		}

		return kTGetDown;
	}

	CWinLogData *fSel1;
	CWinLogData *fSel2;
	int fCountSel;
};

class CGraphConsole : public CCvsConsole
{
public:
	CGraphConsole()
	{
		CWincvsApp *app = (CWincvsApp *)AfxGetApp();
		view = app->GetConsoleView();
	}

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

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

	CWincvsView *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);
}

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

void CGraphView::OnViewDiff() 
{
	CGraphDoc *pDoc = (CGraphDoc *)GetDocument();
	CLogNode *root = pDoc->GetNode();
	if(root == 0L || root->GetUserData() == 0L)
		return;

	TGraphCmdStatus trav;
	trav.TraverseGraph(root);

	bool done = false;
	if(gCvsPrefs.ExtDiff() != 0L)
	{
		CStr f1, f2, prf1, prf2, ext, base;
		CvsArgs args1, args2;

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

		prf1 = base;
		prf1 << '_';
		prf1 << trav.fSel1->GetStr();
		prf1 << '_';
		args1.add("update");
		args1.add("-p");
		args1.add("-r");
		args1.add(trav.fSel1->GetStr());
		args1.add(pDoc->GetName());
		args1.print(pDoc->GetDir());
		f1 = launchCVS(pDoc->GetDir(), args1, (const char *)prf1, ext);

		if(trav.fCountSel == 2)
		{
			prf2 = base;
			prf2 << '_';
			prf2 << trav.fSel2->GetStr();
			prf2 << '_';
			args2.add("update");
			args2.add("-p");
			args2.add("-r");
			args2.add(trav.fSel2->GetStr());
			args2.add(pDoc->GetName());
			args2.print(pDoc->GetDir());
			f2 = launchCVS(pDoc->GetDir(), args2, (const char *)prf2, ext);
		}
		else
		{
			f2 = pDoc->GetDir();
			if(!f2.endsWith(kPathDelimiter))
				f2 << kPathDelimiter;
			f2 << pDoc->GetName();
		}

		if(f1.empty() || f2.empty())
			return;

		done = LaunchDiff(f1, f2);
	}

	if(!done)
	{
		CvsArgs args;
		CGraphConsole cons;
		args.add("diff");
		args.add("-r");
		args.add(trav.fSel1->GetStr());
		if(trav.fCountSel == 2)
		{
			args.add("-r");
			args.add(trav.fSel2->GetStr());
		}
		args.add(pDoc->GetName());
		args.print(pDoc->GetDir());
		launchCVS(pDoc->GetDir(), args.Argc(), args.Argv(), &cons);
	}
}

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

	TGraphCmdStatus trav;
	trav.TraverseGraph(root);
	BOOL outEnabled = (trav.fCountSel == 2 && trav.fSel1->GetType() != kNodeHeader &&
		trav.fSel2->GetType() != kNodeHeader) ||
		(trav.fCountSel == 1 && trav.fSel1->GetType() != kNodeHeader);
	pCmdUI->Enable(outEnabled);
}

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();
}

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

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.fSel1->Node();
	switch(node->GetType())
	{
		case kNodeHeader :
		{
			args.add("-A");
			break;
		}
		case kNodeRev :
		case kNodeBranch :
		case kNodeTag :
		{
			args.add("-r");
			args.add(trav.fSel1->GetStr());
			break;
		}
	}
	args.add(pDoc->GetName());
	args.print(pDoc->GetDir());
	CGraphConsole cons;
	launchCVS(pDoc->GetDir(), args.Argc(), args.Argv(), &cons);
	
	OnViewReload();
}

void CGraphView::OnUpdateViewTag(CCmdUI* pCmdUI) 
{
	CGraphDoc *pDoc = (CGraphDoc *)GetDocument();
	CLogNode *root = pDoc->GetNode();
	if(DisableCommon())
	{
		pCmdUI->Enable(FALSE);
		return;
	}

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

void CGraphView::OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo) 
{
	// TODO: Add your specialized code here and/or call the base class
	
	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);
}

void CGraphView::OnEndPrinting(CDC* pDC, CPrintInfo* pInfo) 
{
	// TODO: Add your specialized code here and/or call the base class
	
	CScrollView::OnEndPrinting(pDC, pInfo);

	CalcImageSize();
	Invalidate();
}

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

	return TRUE;
}

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);
}

bool CGraphView::DisableCommon()
{
	CWincvsApp *app = (CWincvsApp *)AfxGetApp();
	CGraphDoc *pDoc = (CGraphDoc *)GetDocument();
	CLogNode *root = pDoc->GetNode();
	return root == 0L || root->GetUserData() == 0L || app->gCvsRunning || this != CWnd::GetFocus() || gCvsPrefs.empty();
}


class TGraphDelCmd : public TGraph
{
public:
	TGraphDelCmd() :
		fCountSel(0), fOnlyRev(true) {}

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

		if(data->Selected())
		{
			if(data->GetType() != kNodeRev)
				fOnlyRev = false;
			else
				fAllRevs.push_back(CStr(data->GetStr()));
			fCountSel++;
		}

		return kTGetDown;
	}

	int fCountSel;
	bool fOnlyRev;
	std::vector<CStr> fAllRevs;
};

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.fAllRevs.begin(); i != trav.fAllRevs.end(); ++i, ++count)
	{
		progress.SetProgress(((float)count / (float)trav.fAllRevs.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();
}

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.fCountSel >= 1 && trav.fOnlyRev;
	pCmdUI->Enable(outEnabled);
}

class TGraphSelNonSig : public TGraph
{
public:
	TGraphSelNonSig(CGraphView *view) : fView(view)
	{
	}

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

		if(data->GetType() == kNodeRev)
		{
			if(node->Next() != 0L && node->Childs().size() == 0)
				data->SetSelected(fView, true);
		}

		return kTGetDown;
	}

	CGraphView *fView;
};

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);
}

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

// CVS console used to retrieve a specific revision
class CRevisionConsole : public CCvsConsole
{
public:
	CRevisionConsole() 
    : fOut(NULL) 
    {}

    ~CRevisionConsole() 
    {
        if (fOut)
        {
            fclose(fOut);
        }
    }

    BOOL init(LPCTSTR path, LPCTSTR fname)
    {
        TCHAR buf[_MAX_PATH];
        _tmakepath(buf, NULL, path, fname, NULL);
        fOut = fopen(buf, "w+");
        return fOut != NULL;
    }

	virtual long cvs_out(char *txt, long len)
	{
		fwrite(txt, sizeof(char), len, fOut);
		return len;
	}

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

protected:
	FILE *fOut;
};


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.fSel1->GetStr();
	args.add("update");
	args.add("-p");
	args.add("-r");
	args.add(trav.fSel1->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);
}

void CGraphView::OnUpdateViewRetrieve(CCmdUI* pCmdUI) 
{
    BOOL enabled = FALSE;
    if(!DisableCommon())
	{	
	    CGraphDoc *pDoc = (CGraphDoc *)GetDocument();
	    CLogNode *root = pDoc->GetNode();

	    TGraphCmdStatus trav;
	    trav.TraverseGraph(root);
	    enabled = (trav.fCountSel == 1 && trav.fSel1->GetType() != kNodeHeader);
    }
	pCmdUI->Enable(enabled);	
}

void CGraphView::DisableUpdate(CCmdUI* pCmdUI) 
{
	pCmdUI->Enable(false);	
}

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.fCountSel == 0 )
			{
				hit->SetSelected(this, true);
			}
			else if( trav.fCountSel >= 1 && trav.fSel1->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);
}

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;
	  }
	  
}

void CGraphView::OnUpdateQueryAnnotate(CCmdUI* pCmdUI) 
{
    BOOL enabled = FALSE;
    if(!DisableCommon())
	{	
		CGraphDoc *pDoc = (CGraphDoc *)GetDocument();
		CLogNode *root = pDoc->GetNode();
		
		TGraphCmdStatus trav;
		trav.TraverseGraph(root);
		enabled = (trav.fCountSel == 1 && trav.fSel1->GetType() != kNodeHeader);
    }
	pCmdUI->Enable(enabled);	
}

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.fSel1->GetStr();
		prf << '_';
		args.add("annotate");
		args.add("-r");
		args.add(trav.fSel1->GetStr());
		args.add(pDoc->GetName());
		args.print(pDoc->GetDir());
		file = launchCVS(pDoc->GetDir(), args, (const char *)prf, ext);
		
		if(file.empty())
			return;
		
		LaunchViewer(file);
	}
}
