// svrview.cpp : implementation of the CServerView class
//
// This is a part of the Microsoft Foundation Classes C++ library.
// Copyright (C) 1992-1995 Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// Microsoft Foundation Classes Reference and related
// electronic documentation provided with the library.
// See these sources for detailed information regarding the
// Microsoft Foundation Classes product.



#include "stdafx.h"
#include "afxdlgs.h"        // for FileOpen
#include "hiersvr.h"

#include "svrdoc.h"
#include "svrview.h"
#include "svritem.h"
#include "zoomdlg.h"

#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif

CServerNode* NEAR CServerView::m_pDragNode = NULL;

static int ReadString(CArchive& ar, char* pString, int nMaxLen);
static BOOL FindItemRect(CDC* pDC, CServerNode* pItemFind, LPRECT lpRect,
	CPoint& ptStart, CServerNode* pRoot);
static CServerNode* HitDetect(CDC* pDC, CPoint point,
	CPoint& ptStart, CServerNode* pRoot);

/////////////////////////////////////////////////////////////////////////////
// support for zooming

static int NEAR rgiZoomFactor[] =
{
	25, 50, 75, 100, 125, 150, 175, 200, 250, 300
};

/////////////////////////////////////////////////////////////////////////////
// CServerView

IMPLEMENT_DYNCREATE(CServerView, CScrollView)

BEGIN_MESSAGE_MAP(CServerView, CScrollView)
	//{{AFX_MSG_MAP(CServerView)
	ON_COMMAND(ID_CHANGE_NAME, OnChangeName)
	ON_COMMAND(ID_ADD_NODE, OnAddNode)
	ON_UPDATE_COMMAND_UI(ID_CHANGE_NAME, OnUpdateHasSel)
	ON_COMMAND(ID_EDIT_COPY, OnEditCopy)
	ON_WM_LBUTTONDOWN()
	ON_WM_RBUTTONDOWN()
	ON_COMMAND(ID_IMPORT_TEXT, OnImportText)
	ON_COMMAND(ID_VIEW_ZOOMCUSTOM, OnViewZoomCustom)
	ON_COMMAND(ID_EDIT_CUT, OnEditCut)
	ON_WM_LBUTTONDBLCLK()
	ON_COMMAND(ID_EDIT_PASTE, OnEditPaste)
	ON_UPDATE_COMMAND_UI(ID_EDIT_PASTE, OnUpdateEditPaste)
	ON_WM_CREATE()
	ON_COMMAND(ID_EDIT_CLEAR, OnEditClear)
	ON_COMMAND(ID_TREE_COLLAPSEBRANCH, OnTreeCollapsebranch)
	ON_COMMAND(ID_TREE_EXPANDALL, OnTreeExpandall)
	ON_COMMAND(ID_TREE_EXPANDBRANCH, OnTreeExpandbranch)
	ON_COMMAND(ID_TREE_EXPANDONELEVEL, OnTreeExpandonelevel)
	ON_UPDATE_COMMAND_UI(ID_TREE_COLLAPSEBRANCH, OnUpdateTreeCollapsebranch)
	ON_UPDATE_COMMAND_UI(ID_TREE_EXPANDONELEVEL, OnUpdateTreeExpandonelevel)
	ON_UPDATE_COMMAND_UI(ID_TREE_EXPANDALL, OnUpdateTreeExpandall)
	ON_UPDATE_COMMAND_UI(ID_TREE_EXPANDBRANCH, OnUpdateTreeExpandbranch)
	ON_WM_KEYDOWN()
	ON_UPDATE_COMMAND_UI(ID_ADD_NODE, OnUpdateHasSel)
	ON_UPDATE_COMMAND_UI(ID_EDIT_COPY, OnUpdateHasSel)
	ON_UPDATE_COMMAND_UI(ID_IMPORT_TEXT, OnUpdateHasSel)
	ON_UPDATE_COMMAND_UI(ID_EDIT_CUT, OnUpdateHasSel)
	ON_UPDATE_COMMAND_UI(ID_EDIT_CLEAR, OnUpdateHasSel)
	ON_WM_SIZE()
	//}}AFX_MSG_MAP
	ON_COMMAND_RANGE(ID_VIEW_ZOOM25, ID_VIEW_ZOOM300, OnZoom)
	ON_UPDATE_COMMAND_UI_RANGE(ID_VIEW_ZOOM25, ID_VIEW_ZOOM300, OnUpdateZoom)
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CServerView construction/destruction

CServerView::CServerView()
{
	m_pSelectedNode = NULL;
	m_ptStart = CPoint(CX_MARGIN, CY_MARGIN);   // upper left position

	// initialize zoom state (100% from size cache)
	m_zoomNum = CSize(100, 100);
	m_zoomDenom = CSize(100, 100);
	m_prevDropEffect = DROPEFFECT_NONE;
}

CServerView::~CServerView()
{
}

/////////////////////////////////////////////////////////////////////////////
// Attributes -- public

void CServerView::SetSelection(CServerNode* pNewSel, CDC* pDC)
{
	if (pNewSel == m_pSelectedNode)
		return;     // already set
	InvalidateItem(m_pSelectedNode, pDC);
	m_pSelectedNode = pNewSel;
	InvalidateItem(m_pSelectedNode, pDC);
}

void CServerView::SetSelection(CServerNode* pNewSel)
{
	CServerDC dc(this);
	SetSelection(pNewSel, &dc);
}

/////////////////////////////////////////////////////////////////////////////
// Operations -- public

void CServerView::SetScrollInfo()
{
	CServerNode* pRoot = GetDocument()->m_pRoot;
	ASSERT(pRoot != NULL);

	//BLOCK: determine new scrolling sizes
	CSize sizeLine;
	CSize sizePage;
	CSize sizeTotal;
	{
		CServerDC dc(this);

		pRoot->CalcNodeSize(&dc, sizeLine);
		sizeLine.cx = sizeDefault.cx;
		sizeLine.cy += CY_SEPARATOR;
		sizePage.cx = sizeDefault.cx;
		sizePage.cy = sizeLine.cy * 10;

		// set the scroll sizes dependent on the zoom factor
		dc.LPtoDP(&sizePage);
		dc.LPtoDP(&sizeLine);
		sizeTotal = CalcActualViewSize(&dc);
		// subtract out the right and bottom margins
		// these are not interesting and this way we avoid
		// some problems with nonlinear scaling of fonts
		CSize sizeMargin(CX_MARGIN, CY_MARGIN);
		CRect rect(CPoint(0, 0), sizeMargin);
		dc.LPtoDP(rect);
		sizeTotal -= rect.Size();
	}

	SetScrollSizes(MM_TEXT, sizeTotal, sizePage, sizeLine);
}

BOOL CServerView::SetZoomFactor(CSize zoomNum, CSize zoomDenom)
{
	if (zoomDenom != m_zoomDenom || zoomNum != m_zoomNum)
	{
		// sync to new zoom factor
		m_zoomNum = zoomNum;
		m_zoomDenom = zoomDenom;

		// resync to new sizes
		Invalidate();
		return TRUE;
	}
	return FALSE;
}

void CServerView::UpdateServerView()
{
	CServerDoc* pDoc = GetDocument();
	pDoc->SetModifiedFlag();
	pDoc->UpdateAllItems(NULL);
	pDoc->UpdateAllViews(NULL);
}

CSize CServerView::CalcActualViewSize(CDC *pDC)
{
	return CalcActualItemSize(GetDocument()->m_pRoot, pDC);
}

CSize CServerView::CalcActualItemSize(CServerNode* pItem, CDC *pDC)
{
	CSize sizeTotal(0, 0);
	CPoint ptStart = m_ptStart;
	pItem->CalcBounding(pDC, ptStart, sizeTotal);
	sizeTotal.cx += CX_MARGIN;
	// sizeTotal is the extent in logical coords
	// convert from logical to device
	CRect rc(CPoint(0,0),sizeTotal);
	pDC->LPtoDP(rc);
	return rc.Size();
}

CSize CServerView::CalcScaledViewSize()
{
	return CalcScaledItemSize(GetDocument()->m_pRoot);
}

CSize CServerView::CalcScaledItemSize(CServerNode* pItem)
{
	CServerDC dc(this);
	// set extent to 100%
	dc.SetViewportExt(CSize(1,1));
	dc.SetWindowExt(CSize(1,1));

	CSize sizeTotal(0, 0);
	CPoint ptStart = m_ptStart;
	pItem->CalcBounding(&dc, ptStart, sizeTotal);
	sizeTotal.cx += CX_MARGIN;
	// sizeTotal is the native extent in device coords
	sizeTotal.cx = MulDiv(sizeTotal.cx,m_zoomNum.cx,m_zoomDenom.cx);
	sizeTotal.cy = MulDiv(sizeTotal.cy,m_zoomNum.cy,m_zoomDenom.cy);
	return sizeTotal;
}

BOOL CServerView::DoPasteItem(COleDataObject* pDataObject)
{
	ASSERT(pDataObject != NULL);
	ASSERT_VALID(this);
	ASSERT(m_pSelectedNode != NULL);

	BeginWaitCursor();

	TRY
	{
		if (pDataObject->IsDataAvailable(CServerDoc::m_cfPrivate))
			DoPasteNative(pDataObject);
		else if (pDataObject->IsDataAvailable(CF_TEXT))
			DoPasteText(pDataObject);
		else
			AfxThrowNotSupportedException();
	}
	CATCH_ALL(e)
	{
		// general cleanup
		TRACE0("failed to embed/link an OLE object\n");
		EndWaitCursor();
		return FALSE;
	}
	END_CATCH_ALL

	EndWaitCursor();
	return TRUE;
}

void CServerView::DoPasteNative(COleDataObject *pDataObject)
{
	ASSERT(m_pSelectedNode != NULL);
	CServerNode* pItem = new CServerNode(GetDocument());
	ASSERT_VALID(pItem);

	// get file refering to clipboard data
	CFile *pFile = pDataObject->GetFileData(CServerDoc::m_cfPrivate);
	if (pFile == NULL)
	{
		delete pItem;
		return;
	}

	// connect the file to the archive and read the contents
	CArchive ar(pFile, CArchive::load);
	ar.m_pDocument = GetDocument(); // for COleClientItem serialize
	pItem->Serialize(ar);
	ar.Close();
	delete pFile;

	// add the item to selected node
	m_pSelectedNode->m_listChild.AddHead(pItem);
	SetSelection(pItem);
}

void CServerView::DoPasteText(COleDataObject *pDataObject)
{
	ASSERT(m_pSelectedNode != NULL);

	// get file refering to clipboard data
	CFile *pFile = pDataObject->GetFileData(CF_TEXT);
	if (pFile == NULL)
		return;

	// connect the file to the archive and read the contents
	CArchive ar(pFile, CArchive::load);
	DoImportText(ar);
	ar.Close();
	delete pFile;
}

void CServerView::DoImportText(CArchive &ar)
{
	// read in lines appending items from this node
	char szNode[256];
#ifdef _UNICODE
	TCHAR szT[256];
#endif
	int nCurLevel = 0;
	CServerNode* parents[MAX_LEVEL];
	parents[0] = m_pSelectedNode;   // must have a parent at the current level
	CServerNode* pNewSel = NULL;

	while (ReadString(ar, szNode, sizeof(szNode)) != NULL)
	{
		int cch = strlen(szNode);
		if (cch == 0)
			continue;
		if (szNode[cch-1] != '\n')
		{
			AfxMessageBox(
				_T("Text file contains too long a line - aborting read\n"));
			break;      // leaves items loaded so far
		}
		szNode[cch-1] = '\0';
		// check the indentation level
		char* pch = szNode;
		while (*pch == '\t')
			pch++;
		int nLevel = (pch - szNode)+1;
		ASSERT(nLevel >= 1);        // levels are 1 based, 0 is the root

		if (*pch == '\0')
			continue;       // skip blank lines
		if (nLevel > nCurLevel + 1 || nLevel >= MAX_LEVEL)
		{
			AfxMessageBox(
				_T("Text file contains too many tabs = aborting read\n"));
			break;      // leaves items loaded so far
		}

#ifdef _UNICODE
		_mbstowcsz(szT, pch, sizeof(szT)/sizeof(szT[0]));
		parents[nLevel] = parents[nLevel-1]->CreateChildNode(szT);
#else
		parents[nLevel] = parents[nLevel-1]->CreateChildNode(pch);
#endif
		nCurLevel = nLevel;
		if (pNewSel == NULL)
			pNewSel = parents[nLevel];
	}
	if (nCurLevel == 0)
		return;     // nothing added

	SetSelection(pNewSel);
	UpdateServerView();
}

void CServerView::ScrollToItem(CServerNode *pItem, BOOL bScrollUp)
{
	CServerDC dc(this);

	CRect rect;
	CPoint ptStart = m_ptStart;
	if (FindItemRect(&dc, pItem, &rect, ptStart, GetDocument()->m_pRoot))
	{
		CRect rc;
		dc.LPtoDP(rect);
		if (GetDocument()->IsInPlaceActive())
		{
			CRect rcPos,rcClip;
			GetDocument()->GetItemPosition(&rcPos);
			GetDocument()->GetItemClipRect(&rcClip);
			rc.IntersectRect(&rcPos,&rcClip);
			rc.OffsetRect(-rcPos.left,-rcPos.top);
			if (!rc.PtInRect(rect.TopLeft()) || !rc.PtInRect(rect.BottomRight()))
			{
				CSize size(0, 0);
				// scroll inplace site
				if (bScrollUp)
					size.cy = rect.top - rc.top;
				else
					size.cy = rect.bottom + rcPos.top - rcClip.bottom;
				GetDocument()->ScrollContainerBy(size);
			}
		}
		else
		{
			GetClientRect(rc);
			if (!rc.PtInRect(rect.TopLeft()) || !rc.PtInRect(rect.BottomRight()))
			{
				CPoint ptDevScroll = GetDeviceScrollPosition();
				if (bScrollUp)
					ScrollToPosition(CPoint(0,rect.top + ptDevScroll.y));
				else
					ScrollToPosition(
						CPoint(0,ptDevScroll.y + rect.bottom - rc.Height()));
			}
		}
	}
}

void CServerView::Locate(int nCount, BOOL bScrollUp)
{
	CServerNode* pRoot = GetDocument()->m_pRoot;
	CServerNode* pItem = pRoot;

	if (m_pSelectedNode == NULL)
		SetSelection(pRoot);

	if (nCount == 0) // goto top or bottom depending on direction
	{
		if (bScrollUp)
			pItem = pRoot;
		else
		{
			while (pItem->HasChildren())
				pItem = (CServerNode *)(pItem->m_listChild.GetTail());
		}
	}
	else if (bScrollUp)
	{
		pItem = m_pSelectedNode;
		while (nCount--)
			pItem = pRoot->GetPrev(pItem);
	}
	else // going down
	{
		pItem = m_pSelectedNode;
		while (nCount--)
			pItem = pRoot->GetNext(pItem);
	}
	ScrollToItem(pItem, bScrollUp);
	SetSelection(pItem);
	UpdateWindow();
}

void CServerView::InvalidateItem(CServerNode *pItem, CDC* pDC)
{
	if (pItem != NULL)
	{
		CRect rect;
		CPoint ptStart = m_ptStart;
		if (FindItemRect(pDC, pItem, &rect, ptStart, GetDocument()->m_pRoot))
		{
			pDC->LPtoDP(rect);
			InvalidateRect(rect, FALSE);
		}
	}
}

void CServerView::InvalidateItem(CServerNode* pItem)
{
	CServerDC dc(this);
	InvalidateItem(pItem, &dc);
}

void CServerView::ToggleSelectedItem()
{
	if (m_pSelectedNode != NULL && m_pSelectedNode->HasChildren())
	{
		if (m_pSelectedNode->m_bHideChildren)
			OnTreeExpandonelevel();
		else
			OnTreeCollapsebranch();
	}
}

/////////////////////////////////////////////////////////////////////////////
// Implementation

void CServerView::OnDraw(CDC* pDC)
{
	if (GetDocument()->DrawTree(pDC, m_ptStart, m_pSelectedNode) == -1)
		TRACE0("Error: CServerDoc::DrawTree failed!!\n");
}

void CServerView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)
{
	CScrollView::OnUpdate(pSender, lHint, pHint);
	CServerDoc* pDoc = GetDocument();
	CServerNode* pRoot = pDoc->m_pRoot;
	ASSERT(pRoot != NULL);

	if (m_pSelectedNode == NULL)
		m_pSelectedNode = pRoot;

	if (pDoc->IsInPlaceActive())
	{
		ASSERT_VALID(pDoc->m_pRoot);
		CRect rect;
		pDoc->GetItemPosition(rect);

		// adjust rect for new size of view
		rect.BottomRight() = rect.TopLeft() + CalcScaledViewSize();
		pDoc->RequestPositionChange(rect);
			// may resize the window before returning
	}
	SetScrollInfo();
}

void CServerView::OnInitialUpdate()
{
	// initialize zoom state (100% from size cache)
	m_zoomNum = CSize(100, 100);
	m_zoomDenom = CSize(100, 100);

	// call base class last (will call OnUpdate)
	CScrollView::OnInitialUpdate();
}

void CServerView::OnPrepareDC(CDC* pDC, CPrintInfo* pInfo)
{
	CScrollView::OnPrepareDC(pDC, pInfo);

	pDC->SetMapMode(MM_ANISOTROPIC);
	pDC->SetViewportExt(m_zoomNum);
	pDC->SetWindowExt(m_zoomDenom);
}

void CServerView::SelectAtPoint(CPoint point, CPoint& pointHit)
{
	CServerDC dc(this);

	// change the selection as appropriate
	pointHit = m_ptStart;
	CServerNode* pSelection = HitDetect(&dc, point, pointHit,
		GetDocument()->m_pRoot);

	// now set the selection to new hit-tested node
	SetSelection(pSelection, &dc);
}

void CServerView::DoBranch(CServerNode *pItem, BOOL bHide)
{
	if (pItem->HasChildren())
	{
		pItem->m_bHideChildren = bHide;
		POSITION pos = pItem->m_listChild.GetHeadPosition();
		while (pos != NULL)
			DoBranch((CServerNode *)pItem->m_listChild.GetNext(pos),bHide);
	}
}

BOOL CServerView::OnDrop(COleDataObject* pDataObject, DROPEFFECT, CPoint)
{
	ASSERT_VALID(this);
	OnDragLeave();
	if (DoPasteItem(pDataObject))
	{
		UpdateServerView();
		return TRUE;
	}
	else
		return FALSE;
}

DROPEFFECT CServerView::OnDragEnter(COleDataObject* pDataObject,
	DWORD grfKeyState, CPoint point)
{
	ASSERT(m_prevDropEffect == DROPEFFECT_NONE);
	return OnDragOver(pDataObject, grfKeyState, point);
}

DROPEFFECT CServerView::OnDragOver(COleDataObject* pDataObject,
	DWORD grfKeyState, CPoint point)
{
	DROPEFFECT dropEffect = DROPEFFECT_NONE;
	if (pDataObject->IsDataAvailable(CServerDoc::m_cfPrivate) ||
		pDataObject->IsDataAvailable(CF_TEXT))
	{
		CPoint pointHit;
		SelectAtPoint(point, pointHit);
		UpdateWindow();
		if (m_pSelectedNode != NULL)
		{
			// don't allow a node to drop on itself
			if (m_pDragNode == m_pSelectedNode)
				dropEffect = DROPEFFECT_NONE;
			// if drop target is a child of drag source
			// then don't drop
			else if (m_pDragNode != NULL && m_pDragNode->IsChild(m_pSelectedNode))
				dropEffect = DROPEFFECT_NONE;
			// check for force link
#ifndef _MAC			
			else if ((grfKeyState & (MK_CONTROL|MK_SHIFT)) == (MK_CONTROL|MK_SHIFT))
#else
			else if ((grfKeyState & (MK_OPTION|MK_SHIFT)) == (MK_OPTION|MK_SHIFT))
#endif			
				dropEffect = DROPEFFECT_COPY;
			// check for force copy
#ifndef _MAC			
			else if ((grfKeyState & MK_CONTROL) == MK_CONTROL)
#else				
			else if ((grfKeyState & MK_OPTION) == MK_OPTION)
#endif			
				dropEffect = DROPEFFECT_COPY;
			// check for force move
			else if ((grfKeyState & MK_ALT) == MK_ALT)
				dropEffect = DROPEFFECT_MOVE;
			// if m_pDragNode == NULL then not dropping on self
			// so make default effect be copy
			// if dropping on self make default be move
			else
				dropEffect = (m_pDragNode == NULL) ? DROPEFFECT_COPY :
					DROPEFFECT_MOVE;
		}
	}
	m_prevDropEffect = dropEffect;
	return dropEffect;
}

void CServerView::OnDragLeave()
{
	m_prevDropEffect = DROPEFFECT_NONE;
}

/////////////////////////////////////////////////////////////////////////////
// CServerView commands

void CServerView::OnUpdateHasSel(CCmdUI* pCmdUI)
{
	pCmdUI->Enable(m_pSelectedNode != NULL);
}

void CServerView::OnChangeName()
{
	if (m_pSelectedNode != NULL && m_pSelectedNode->PromptChangeNode())
	{
		GetDocument()->SetModifiedFlag();
		GetDocument()->UpdateAllItems(NULL);
		GetDocument()->UpdateAllViews(NULL);
	}
}

void CServerView::OnAddNode()
{
	if (m_pSelectedNode == NULL)
		AfxThrowNotSupportedException();
	CServerNode* pNew;
	if ((pNew = m_pSelectedNode->PromptNewChildNode()) == NULL)
		return;

	SetSelection(pNew);
	UpdateServerView();
}

void CServerView::OnEditCopy()
{
	ASSERT_VALID(this);
	if (m_pSelectedNode == NULL)
		return;

	// get a server item suitable to generate the clipboard data
	CServerItem* pItem = m_pSelectedNode->m_pServerItem;
	if (pItem == NULL)
		pItem = new CServerItem(GetDocument(), m_pSelectedNode);
	ASSERT_VALID(pItem);

	pItem->CopyToClipboard(TRUE);
}

void CServerView::OnLButtonDown(UINT, CPoint point)
{
	// m_pDragNode != NULL only when drag in progress
	ASSERT(m_pDragNode == NULL);
	CPoint pointHit;
	SelectAtPoint(point, pointHit);
	UpdateWindow();

	// maybe start drag drop operation on selection
	if (m_pSelectedNode != NULL)
	{
		// record current selection because a drop back in same view
		// will change the current selection
		m_pDragNode = m_pSelectedNode;

		// determine rectangle of object we'll be dragging
		CServerDC dc(this);
		CSize size = CalcActualItemSize(m_pSelectedNode, &dc);
		CRect rect(pointHit, size);

		// get a server item suitable to generate the clipboard data
		CServerItem* pItem = m_pSelectedNode->m_pServerItem;
		if (pItem == NULL)
			pItem = new CServerItem(GetDocument(), m_pSelectedNode);
		ASSERT_VALID(pItem);

		// rect must be in screen co-ordinates
		dc.LPtoDP(&rect);
		CPoint ptOffset = point - rect.TopLeft();
		ClientToScreen(&rect);

		// drag the object
		if (pItem->DoDragDrop(rect, ptOffset, TRUE,
			DROPEFFECT_COPY | DROPEFFECT_MOVE) == DROPEFFECT_MOVE)
		{
			// the object was moved, need to delete/move depending on dest
			CServerDoc* pDoc = GetDocument();
			if (m_pSelectedNode == m_pDragNode)
				SetSelection(pDoc->m_pRoot->GetPrev(m_pSelectedNode));
			if (m_pDragNode == pDoc->m_pRoot)
				pDoc->m_pRoot->InitRootNode();
			else
				pDoc->m_pRoot->FindAndDelete(m_pDragNode);

			// if drop was not back in same view clear selected node
			UpdateServerView();
		}
		m_pDragNode = NULL;
	}
}

void CServerView::OnRButtonDown(UINT /*nFlags*/, CPoint point)
{
	// make sure window is active
	GetParentFrame()->ActivateFrame();

	CPoint pointHit;
	SelectAtPoint(point, pointHit); // select node first
	UpdateWindow();

	int iSub;
	if (m_pSelectedNode == NULL ||
	  (iSub = m_pSelectedNode->GetPopupMenuIndex()) == -1)
	   return;  // nothing if no selection or no popup

	CMenu popups;
	if (!popups.LoadMenu(IDR_POPUPS))
		AfxThrowResourceException();
	CMenu* pMenu = popups.GetSubMenu(iSub);
	ASSERT(pMenu != NULL);

	ClientToScreen(&point);
	pMenu->TrackPopupMenu(TPM_CENTERALIGN | TPM_RIGHTBUTTON,
		point.x, point.y, AfxGetMainWnd());
}

void CServerView::OnImportText()
{
	if (m_pSelectedNode == NULL)
		AfxThrowNotSupportedException();

	CFileDialog dlg(TRUE, _T("txt"), NULL,
		OFN_FILEMUSTEXIST | OFN_HIDEREADONLY,
#ifdef _MAC
		_T("Text Files|TEXT|All Files|****||"));
#else
		_T("Text Files (*.txt)|*.txt|All Files (*.*)|*.*||"));
#endif
	if (dlg.DoModal() != IDOK)
		return;                 // stay with old data file

	CFile file;
	if (!file.Open(dlg.GetPathName(), CFile::modeRead))
	{
		AfxMessageBox(_T("Failed to open text file"));
		return;
	}

	CArchive ar(&file,CArchive::load);
	DoImportText(ar);
	ar.Close();
	file.Close();
}

static int ReadString(CArchive& ar, char* pString, int nMaxLen)
{
	int nCount = 0;
	char ch;
	pString[nCount] = (char)0;
	TRY
	{
		do
		{
			ar >> (BYTE&)ch;

			// watch out for ^Z (EOF under DOS)
			if (ch == 0x1A)
				break;

			// combine "\r\n" pair into single "\n"
			if (ch == '\n' && nCount != 0 && pString[nCount-1] == '\r')
				nCount--;

			pString[nCount++] = ch;

		} while (nCount < nMaxLen-1 && ch != '\n');
	}
	END_TRY

#ifdef _OLD
	// insert newline if missing
	if (nCount != 0 && pString[nCount-1] != '\n' && nCount < nMaxLen-1)
		pString[nCount++] = '\n';
	pString[nCount] = (char)0;
#else
	// insert newline if missing
	if (nCount != 0 && pString[nCount-1] != '\n' && nCount < nMaxLen-1)
	{
		if (pString[nCount-1] == '\0')
			pString[nCount-1] = '\n';
		else
			pString[nCount++] = '\n';
	}
	pString[nCount] = (char)0;
#endif
	return nCount;
}

/////////////////////////////////////////////////////////////////////////////
// Zooming user interface

void CServerView::OnZoom(UINT nID)
{
	ASSERT(nID >= ID_VIEW_ZOOM25 && nID <= ID_VIEW_ZOOM300);

	CSize zoomDenom;
	CSize zoomNum;

	// all of our zoom factors use denominator of 100
	zoomDenom.cx = 100;
	zoomDenom.cy = 100;

	// get zoom factor numerator and set it
	ASSERT(nID-ID_VIEW_ZOOM25 < sizeof(rgiZoomFactor)/sizeof(rgiZoomFactor[0]));
	int iZoomFactor = rgiZoomFactor[nID-ID_VIEW_ZOOM25];
	zoomNum.cx = iZoomFactor;
	zoomNum.cy = iZoomFactor;

	// change the zoom factor to new zoom factor
	SetZoomFactor(zoomNum, zoomDenom);
	SetScrollInfo();
}

void CServerView::OnUpdateZoom(CCmdUI* pCmdUI)
{
	UINT nID = pCmdUI->m_nID;

	if (m_zoomDenom.cx == 100 && m_zoomDenom.cy == 100 &&
		nID != ID_VIEW_ZOOMCUSTOM)
	{
		ASSERT(nID-ID_VIEW_ZOOM25 <
			sizeof(rgiZoomFactor)/sizeof(rgiZoomFactor[0]));
		int iZoomFactor = rgiZoomFactor[nID-ID_VIEW_ZOOM25];

		if (iZoomFactor == m_zoomNum.cx && iZoomFactor == m_zoomNum.cy)
		{
			pCmdUI->SetCheck(TRUE);
			return;
		}
	}

	// default to not checked
	pCmdUI->SetCheck(FALSE);
}

void CServerView::OnViewZoomCustom()
{
	// prepare dialog data
	CZoomDlg dlg;
	dlg.m_zoomX = m_zoomNum.cx;
	dlg.m_zoomY = m_zoomNum.cy;

	// launch dialog
	if (dlg.DoModal() == IDOK)
	{
		// set new zoom factors
		CSize zoomNum(dlg.m_zoomX, dlg.m_zoomY);
		SetZoomFactor(zoomNum, m_zoomDenom);
		SetScrollInfo();
	}
}

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

void CServerView::OnEditCut()
{
	ASSERT(m_pSelectedNode != NULL);
	CServerDoc* pDoc = GetDocument();
	OnEditCopy();
	if (m_pSelectedNode == pDoc->m_pRoot)
		pDoc->m_pRoot->InitRootNode();
	else if (pDoc->m_pRoot->FindAndDelete(m_pSelectedNode))
		m_pSelectedNode = NULL;
	UpdateServerView();
}

void CServerView::OnLButtonDblClk(UINT /*nFlags*/, CPoint point)
{
	CPoint pointHit;
	SelectAtPoint(point, pointHit);
	ToggleSelectedItem();
}

void CServerView::OnEditPaste()
{
	COleDataObject clipboardData;
	clipboardData.AttachClipboard();
	DoPasteItem(&clipboardData);
	UpdateServerView();
}

void CServerView::OnUpdateEditPaste(CCmdUI* pCmdUI)
{
	// determine if private or standard OLE formats are on the clipboard
	COleDataObject dataObj;
	BOOL bEnable = m_pSelectedNode != NULL &&
		dataObj.AttachClipboard() &&
		(dataObj.IsDataAvailable(CServerDoc::m_cfPrivate) ||
		 dataObj.IsDataAvailable(CF_TEXT));
	// enable command based on availability
	pCmdUI->Enable(bEnable);
}

int CServerView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	if (CScrollView::OnCreate(lpCreateStruct) == -1)
		return -1;

	// register drop target
	m_dropTarget.Register(this);

	// setup scroll state so it is possible to calculate the extent
	SetScrollSizes(MM_TEXT, CSize(0, 0));

	return 0;
}

void CServerView::OnEditClear()
{
	CServerNode* pRoot = GetDocument()->m_pRoot;
	if (m_pSelectedNode == NULL)
		return;
	if (m_pSelectedNode == pRoot)
		pRoot->InitRootNode();
	else
	{
		CServerNode *pItem = pRoot->GetPrev(m_pSelectedNode);
		if (pRoot->FindAndDelete(m_pSelectedNode))
			m_pSelectedNode = pRoot->GetNext(pItem);
	}
	UpdateServerView();
}

void CServerView::OnTreeCollapsebranch()
{
	DoBranch(m_pSelectedNode,TRUE);
	UpdateServerView();
}

void CServerView::OnTreeExpandall()
{
	DoBranch(GetDocument()->m_pRoot,FALSE);
	UpdateServerView();
}

void CServerView::OnTreeExpandbranch()
{
	DoBranch(m_pSelectedNode,FALSE);
	UpdateServerView();
}

void CServerView::OnTreeExpandonelevel()
{
	if (m_pSelectedNode->HasChildren())
	{
		m_pSelectedNode->m_bHideChildren = FALSE;
		UpdateServerView();
	}
}

void CServerView::OnUpdateTreeCollapsebranch(CCmdUI* pCmdUI)
{
	pCmdUI->Enable(m_pSelectedNode != NULL && m_pSelectedNode->HasChildren() &&
		!m_pSelectedNode->m_bHideChildren);
}

void CServerView::OnUpdateTreeExpandonelevel(CCmdUI* pCmdUI)
{
	pCmdUI->Enable(m_pSelectedNode != NULL && m_pSelectedNode->HasChildren() &&
		m_pSelectedNode->m_bHideChildren);
}

void CServerView::OnUpdateTreeExpandall(CCmdUI* pCmdUI)
{
	pCmdUI->Enable(GetDocument()->m_pRoot != NULL &&
		GetDocument()->m_pRoot->HasChildren());
}

void CServerView::OnUpdateTreeExpandbranch(CCmdUI* pCmdUI)
{
	pCmdUI->Enable(m_pSelectedNode != NULL && m_pSelectedNode->HasChildren());
}

void CServerView::OnKeyDown(UINT nChar, UINT /*nRepCnt*/, UINT /*nFlags*/)
{
	switch (nChar)
	{
		case VK_RETURN:
			ToggleSelectedItem();
			break;
		case VK_UP:
			Locate(1, TRUE);
			break;
		case VK_DOWN:
			Locate(1, FALSE);
			break;
		case VK_HOME:
			Locate(0, TRUE);
			break;
		case VK_END:
			Locate(0, FALSE);
			break;
		case VK_PRIOR:
			Locate(10, TRUE);
			break;
		case VK_NEXT:
			Locate(10, FALSE);
			break;
	}
}

void CServerView::OnSize(UINT nType, int cx, int cy)
{
	// update scroll bar size before handling the size message
	//  (this avoids multiple updates during in-place activation)
	SetScrollInfo();

	CScrollView::OnSize(nType, cx, cy);
}

/////////////////////////////////////////////////////////////////////////////
// CServerDC class -- helper for setting up and cleaning up DC

CServerDC::CServerDC(CServerView *pView) : CClientDC(pView)
{
	pView->OnPrepareDC(this);
	pOldFont = pView->GetDocument()->SelectDocFont(this, font);
}

CServerDC::~CServerDC()
{
	if (pOldFont != NULL)
		SelectObject(pOldFont);
}

/////////////////////////////////////////////////////////////////////////////
// non-member static functions

// calculate the bounding rect for a given item in the context of this view
static BOOL FindItemRect(CDC* pDC, CServerNode* pItemFind, LPRECT lpRect,
	CPoint& ptStart, CServerNode* pRoot)
{
	ASSERT(pItemFind != NULL);
	ASSERT(lpRect != NULL);
	ASSERT(pRoot != NULL);

	CSize sizeNode;
	pRoot->CalcNodeSize(pDC, sizeNode);
	if (pItemFind == pRoot)
	{
		// item rect does not include separator
		lpRect->right = (lpRect->left = ptStart.x) + sizeNode.cx;
		lpRect->bottom = (lpRect->top = ptStart.y) + sizeNode.cy;
		return TRUE;
	}

	CPoint pt(CX_INDENT, CY_SEPARATOR);
	ptStart.x += CX_INDENT; // not essential for calculation
	ptStart.y += sizeNode.cy + CY_SEPARATOR;

	// check the kids
	if (!pRoot->m_bHideChildren)
	{
		POSITION pos = pRoot->m_listChild.GetHeadPosition();
		while (pos != NULL)
		{
			CServerNode* pChild = (CServerNode*) (pRoot->m_listChild.GetNext(pos));
			if (FindItemRect(pDC, pItemFind, lpRect, ptStart, pChild))
				return TRUE;    // found
		}
	}
	ptStart.x -= CX_INDENT;
	return FALSE;
}

// calculate the bounding rect for a given item in the context of this view
static CServerNode* HitDetect(CDC* pDC, CPoint point,
	CPoint& ptStart, CServerNode* pRoot)
{
	ASSERT(pRoot != NULL);

	CSize sizeNode;
	pRoot->CalcNodeSize(pDC, sizeNode);
	CRect rect(ptStart, sizeNode);
	pDC->LPtoDP(rect);
	if (rect.PtInRect(point))
		return pRoot;

	ptStart.x += CX_INDENT;
	ptStart.y += sizeNode.cy + CY_SEPARATOR;
	CPoint pt = ptStart;
	pDC->LPtoDP(&pt);
	if (pt.y >= point.y)
		return FALSE;       // no point in looking any lower

	// check the kids
	if (!pRoot->m_bHideChildren)
	{
		POSITION pos = pRoot->m_listChild.GetHeadPosition();
		while (pos != NULL)
		{
			CServerNode* pChild = (CServerNode*) (pRoot->m_listChild.GetNext(pos));
			CServerNode* pItem = HitDetect(pDC, point, ptStart, pChild);
			if (pItem != NULL)
				return pItem;   // found
		}
	}
	ptStart.x -= CX_INDENT;
	return NULL;
}

/////////////////////////////////////////////////////////////////////////////
// CServerView diagnostics

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

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