// WinCvsBrowser.cpp : implementation file
//

#include "stdafx.h"
#include "wincvs.h"
#include "MainFrm.h"
#include "WinCvsBrowser.h"
#include "FileTraversal.h"
#include "AppConsole.h"
#include "CvsPrefs.h"
#include "CvsCommands.h"
#include "BrowseFileView.h"
#include "TclGlue.h"
#include "MacrosSetup.h"
#include "wincvs_winutil.h"

#ifdef WIN32
#	include "resource.h"
#endif /* WIN32 */

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

static const int WM_RESETVIEW = WM_APP + 12;

static PCPStr gOldLoc("P_BrowserLoc", "C:\\");
static CMString gHistoryLocs(100, "P_BrowserLocs");

enum
{
	kIconFolderClosed,
	kIconFolderOpened,
	kIconFolderCVSClosed,
	kIconFolderCVSOpened,
	kIconFolderIgnoreClosed,
	kIconFolderIgnoreOpened,
	kIconFolderIgnoreLost
};

/////////////////////////////////////////////////////////////////////////////
// CWinCvsBrowser

IMPLEMENT_DYNCREATE(CWinCvsBrowser, CTreeView)

BEGIN_MESSAGE_MAP(CWinCvsBrowser, CTreeView)
	//{{AFX_MSG_MAP(CWinCvsBrowser)
	ON_NOTIFY_REFLECT(TVN_ITEMEXPANDING, OnItemexpanding)
	ON_WM_RBUTTONDOWN()
	ON_NOTIFY_REFLECT(TVN_SELCHANGING, OnSelchanging)
	ON_UPDATE_COMMAND_UI(ID_VIEW_ADD, OnUpdateViewAdd)
	ON_UPDATE_COMMAND_UI(ID_VIEW_COMMIT, OnUpdateViewUpdate)
	ON_COMMAND(ID_VIEW_ADD, OnViewAdd)
	ON_COMMAND(ID_VIEW_COMMIT, OnViewCommit)
	ON_COMMAND(ID_VIEW_UPDATE, OnViewUpdate)
	ON_COMMAND(ID_VIEW_QUERYUPDATE, OnViewQueryUpdate)
	ON_COMMAND(ID_VIEW_DIFF, OnViewDiff)
	ON_COMMAND(ID_VIEW_LOG, OnViewLog)
	ON_COMMAND(ID_VIEW_STATUS, OnViewStatus)
	ON_COMMAND(ID_VIEW_LOCKF, OnViewLock)
	ON_COMMAND(ID_VIEW_UNLOCKF, OnViewUnlock)
	ON_COMMAND(ID_VIEW_WATCHON, OnViewWatchOn)
	ON_COMMAND(ID_VIEW_WATCHOFF, OnViewWatchOff)
	ON_COMMAND(ID_VIEW_EDIT, OnViewEdit)
	ON_COMMAND(ID_VIEW_UNEDIT, OnViewUnedit)
	ON_COMMAND(ID_VIEW_WATCHERS, OnViewWatchers)
	ON_COMMAND(ID_VIEW_EDITORS, OnViewEditors)
	ON_COMMAND(ID_VIEW_RELEASE, OnViewRelease)
	ON_COMMAND(ID_VIEW_TAGNEW, OnViewTagNew)
	ON_COMMAND(ID_VIEW_TAGDELETE, OnViewTagDelete)
	ON_COMMAND(ID_VIEW_TAGBRANCH, OnViewTagBranch)
	ON_COMMAND(ID_VIEW_EXPLORE, OnViewExplore)
	ON_UPDATE_COMMAND_UI(ID_VIEW_EXPLORE, OnUpdateViewExplore)
	ON_UPDATE_COMMAND_UI(ID_VIEW_RELOAD, OnUpdateViewReload)
	ON_COMMAND(ID_VIEW_RELOAD, OnViewReload)
	ON_UPDATE_COMMAND_UI(ID_MACRO_SEL, OnUpdateMacroSel)
	ON_COMMAND(ID_VIEW_CHECKOUT, OnViewCheckout)
	ON_UPDATE_COMMAND_UI(ID_VIEW_CHECKOUT, OnUpdateViewCheckout)
	ON_COMMAND(ID_VIEW_IMPORT, OnViewImport)
	ON_UPDATE_COMMAND_UI(ID_VIEW_IMPORT, OnUpdateViewImport)
	ON_WM_SETCURSOR()
	ON_NOTIFY_REFLECT(TVN_SELCHANGED, OnSelchanged)
	ON_WM_DESTROY()
	ON_UPDATE_COMMAND_UI(ID_VIEW_DIFF, OnUpdateViewUpdate)
	ON_UPDATE_COMMAND_UI(ID_VIEW_EDIT, OnUpdateViewUpdate)
	ON_UPDATE_COMMAND_UI(ID_VIEW_EDITORS, OnUpdateViewUpdate)
	ON_UPDATE_COMMAND_UI(ID_VIEW_LOCKF, OnUpdateViewUpdate)
	ON_UPDATE_COMMAND_UI(ID_VIEW_LOG, OnUpdateViewUpdate)
	ON_UPDATE_COMMAND_UI(ID_VIEW_RELEASE, OnUpdateViewUpdate)
	ON_UPDATE_COMMAND_UI(ID_VIEW_STATUS, OnUpdateViewUpdate)
	ON_UPDATE_COMMAND_UI(ID_VIEW_UNEDIT, OnUpdateViewUpdate)
	ON_UPDATE_COMMAND_UI(ID_VIEW_UPDATE, OnUpdateViewUpdate)
	ON_UPDATE_COMMAND_UI(ID_VIEW_QUERYUPDATE, OnUpdateViewUpdate)
	ON_UPDATE_COMMAND_UI(ID_VIEW_UNLOCKF, OnUpdateViewUpdate)
	ON_UPDATE_COMMAND_UI(ID_VIEW_WATCHERS, OnUpdateViewUpdate)
	ON_UPDATE_COMMAND_UI(ID_VIEW_WATCHON, OnUpdateViewUpdate)
	ON_UPDATE_COMMAND_UI(ID_VIEW_WATCHOFF, OnUpdateViewUpdate)
	ON_UPDATE_COMMAND_UI(ID_VIEW_TAGNEW, OnUpdateViewUpdate)
	ON_UPDATE_COMMAND_UI(ID_VIEW_TAGDELETE, OnUpdateViewUpdate)
	ON_UPDATE_COMMAND_UI(ID_VIEW_TAGBRANCH, OnUpdateViewUpdate)
	//}}AFX_MSG_MAP
	ON_COMMAND_EX_RANGE(ID_MACRO_SEL, ID_MACRO_SEL + 99, OnMacroSel)
	ON_UPDATE_COMMAND_UI_RANGE(ID_CUST_FILES_MENU, ID_CUST_FOLDER_MENU, OnUpdateCustomize)
	ON_COMMAND_EX_RANGE(ID_CUST_FILES_MENU, ID_CUST_FOLDER_MENU, OnCustomize)
	ON_MESSAGE(WM_RESETVIEW, OnResetView)
END_MESSAGE_MAP()

CImageList CWinCvsBrowser::m_Imagelist;

static const char *gDummyFile = "@@@dummy file@@@";

CWinCvsBrowser::CWinCvsBrowser()
: m_isSelectionNotificationLocked(false)
, m_isSelectionChanged(false)	 
, m_isResetViewPending(false)
{
}

CWinCvsBrowser::~CWinCvsBrowser()
{ }

/////////////////////////////////////////////////////////////////////////////
// CWinCvsBrowser drawing

void CWinCvsBrowser::OnDraw(CDC* pDC)
{
	// TODO: add draw code here
}

/////////////////////////////////////////////////////////////////////////////
// CWinCvsBrowser diagnostics

#ifdef _DEBUG
void CWinCvsBrowser::AssertValid() const
{
	CTreeView::AssertValid();
}

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

/////////////////////////////////////////////////////////////////////////////
// CWinCvsBrowser message handlers

BOOL CWinCvsBrowser::Create(LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID, CCreateContext* pContext) 
{
	if (!CTreeView::Create(lpszClassName, lpszWindowName, dwStyle, rect, pParentWnd, nID, pContext))
	{
		return FALSE;
	}
	
	if(m_Imagelist.m_hImageList == 0L)
		m_Imagelist.Create(16, 16, ILC_MASK, 1, 1);
	m_Imagelist.SetBkColor(GetSysColor(COLOR_WINDOW));

	HICON hIcon = AfxGetApp()->LoadIcon(IDI_FOLDER);
	m_Imagelist.Add(hIcon);
	hIcon = AfxGetApp()->LoadIcon(IDI_FOLDERO);
	m_Imagelist.Add(hIcon);
	hIcon = AfxGetApp()->LoadIcon(IDI_FOLDERCVS);
	m_Imagelist.Add(hIcon);
	hIcon = AfxGetApp()->LoadIcon(IDI_FOLDEROCVS);
	m_Imagelist.Add(hIcon);
	hIcon = AfxGetApp()->LoadIcon(IDI_FOLDERIGN);
	m_Imagelist.Add(hIcon);
	hIcon = AfxGetApp()->LoadIcon(IDI_FOLDEROIGN);
	m_Imagelist.Add(hIcon);
	hIcon = AfxGetApp()->LoadIcon(IDI_FOLDERLOST);
	m_Imagelist.Add(hIcon);

	CTreeCtrl& treeCtrl = GetTreeCtrl();
	treeCtrl.SetImageList(&m_Imagelist, TVSIL_NORMAL);

	// set the initial root
	CStr newpath((const char *)gOldLoc);
	ResetBrowser(newpath, true);

	SetNewStyle(TVS_HASLINES | TVS_HASBUTTONS | TVS_LINESATROOT, TRUE);

	return TRUE;
}

void CWinCvsBrowser::DeleteAllItems(HTREEITEM root)
{
	// we don't want the file view to be notified when things get deleted
	// process selection after we are done with all that jazz below
	CSelectionChangeNotifier notifier(this, true); 

	CTreeCtrl& treeCtrl = GetTreeCtrl();
	bool eraseRoot = false;
	if(root == 0L)
	{
		root = treeCtrl.GetRootItem( );
		eraseRoot = true;
	}
	if(root == 0L)
		return;

	HTREEITEM childItem = treeCtrl.GetNextItem(root, TVGN_CHILD);

	// erase all the childs
	while(childItem != 0L)
	{
		EntnodeData *data = (EntnodeData *)treeCtrl.GetItemData(childItem);
		if(data != 0L) {
			treeCtrl.SetItemData(childItem,0L);	//So that subsequent references after UnRef() won't SEGV
			data->UnRef();	//Or move this one after DeleteAllItems? so we don't loose one system call. For now, safe way
		}

		DeleteAllItems(childItem);

		HTREEITEM nextItem = treeCtrl.GetNextItem(childItem, TVGN_NEXT);
		VERIFY(treeCtrl.DeleteItem(childItem));
		childItem = nextItem;
	}

	// erase the root only if initially root was 0L
	if(eraseRoot)
	{
		EntnodeData *data = (EntnodeData *)treeCtrl.GetItemData(root);
		ASSERT(data == 0L);

		VERIFY(treeCtrl.DeleteItem(root));
	}
}

static void SetIcon(CTreeCtrl& treeCtrl, HTREEITEM item,
					bool hasCvsInfos, bool expand, EntnodeData *data = 0L)
{
	TV_ITEM pItem;
	pItem.hItem = item;
	pItem.mask = TVIF_HANDLE | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
	VERIFY(treeCtrl.GetItem(&pItem));

	int newImage;
	if(data == 0L)
	{
		if(expand)
			newImage = hasCvsInfos ? kIconFolderCVSOpened : kIconFolderOpened;
		else
			newImage = hasCvsInfos ? kIconFolderCVSClosed : kIconFolderClosed;
	}
	else
	{
		if(expand)
		{
			if(data->IsIgnored())
				newImage = kIconFolderIgnoreOpened;
			else if(data->IsUnknown())
				newImage = kIconFolderOpened;
			else
				newImage = kIconFolderCVSOpened;
		}
		else
		{
			if(data->IsIgnored())
				newImage = kIconFolderIgnoreClosed;
			else if(data->IsUnknown())
				newImage = kIconFolderClosed;
			else
				newImage = kIconFolderCVSClosed;
		}
	}

	if(pItem.iImage != newImage)
	{
		pItem.iImage = newImage;
		pItem.iSelectedImage = newImage;
		pItem.mask = TVIF_HANDLE | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
		treeCtrl.SetItem(&pItem);
	}
}

// regarding a path and a root item, create an
// item with a dummy item inside if and only if
// the folder is not empty.
class TBrowserFillDummy : public TraversalReport
{
public:
	HTREEITEM	m_root;
	HTREEITEM	m_item;
	CTreeCtrl & m_treeCtrl;
	bool		m_empty;
	bool		m_hascvs;
	EntnodeData *m_data;

	TBrowserFillDummy(CTreeCtrl & treeCtrl, HTREEITEM root, HTREEITEM item, EntnodeData *data) :
		m_root(root), m_item(item), m_treeCtrl(treeCtrl), m_empty(true),
		m_data(data), m_hascvs(false) {}

	virtual kTraversal EnterDirectory(const char *fullpath, const char *dirname, const FSSpec * macspec)
	{
		if(m_item == 0L)
		{
			TV_INSERTSTRUCT tvstruct;
			tvstruct.hParent = m_root;
			tvstruct.hInsertAfter = TVI_SORT;
			tvstruct.item.iImage = kIconFolderClosed;
			tvstruct.item.iSelectedImage = kIconFolderClosed;
			tvstruct.item.pszText = (char *)dirname;
			tvstruct.item.mask = TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_TEXT;
			m_item = m_treeCtrl.InsertItem(&tvstruct);
		}

		// assign the entries to this item
		if(m_data != 0L)
		{
			EntnodeData *data = (EntnodeData *)m_treeCtrl.GetItemData(m_item);

			if(m_data != 0L)
				m_data->Ref();
			m_treeCtrl.SetItemData(m_item, (long)m_data);

			if(data != 0L)
				data->UnRef();
		}
		else
			m_data = (EntnodeData *)m_treeCtrl.GetItemData(m_item);

		return kContinueTraversal;
	}

	virtual kTraversal ExitDirectory(const char *fullpath)
	{
		SetIcon(m_treeCtrl, m_item, m_hascvs, false, m_data);

		if(m_empty)
			return kContinueTraversal;

		// add a dummy item to simulate the folder
		// is not empty. This dummy item will be
		// replaced by a complete listing when the
		// node is expanding...
		TV_INSERTSTRUCT tvstruct;
		tvstruct.hParent = m_item;
		tvstruct.hInsertAfter = TVI_SORT;
		tvstruct.item.pszText = (char *)gDummyFile;
		tvstruct.item.mask = TVIF_TEXT;
		m_treeCtrl.InsertItem(&tvstruct);

		return kContinueTraversal;
	}

	virtual kTraversal OnError(const char *err, int errcode)
	{
		return kTraversalError;
	}

	virtual kTraversal OnIdle(const char *fullpath)
	{
		return kContinueTraversal;
	}

	virtual kTraversal OnDirectory(const char *fullpath,
		const char *fullname,
		const char *name,
		const struct stat & dir, const FSSpec * macspec)
	{
		if(stricmp(name, "cvs") == 0)
			m_hascvs = true;
		else
			m_empty = false;
		return kSkipFile;
	}
};

// regarding a path and a root item, create an
// item with all the subdirectories inside.
class TBrowserFill : public TraversalReport
{
public:
	HTREEITEM m_root;
	HTREEITEM m_item;
	CTreeCtrl & m_treeCtrl;
	CSortList<ENTNODE> & m_entries;
	vector<CStr> m_ignlist;
	bool m_isShowIgnored;

	TBrowserFill(CTreeCtrl & treeCtrl, HTREEITEM root, HTREEITEM item, CSortList<ENTNODE> & entries) :
			m_root(root), m_item(item), m_treeCtrl(treeCtrl),
			m_entries(entries), m_isShowIgnored(false)
	{
		if (CWincvsApp* app = (CWincvsApp *)AfxGetApp())
        { 
			m_isShowIgnored = app->GetIgnoreModel()->IsShowIgnored();
        }
	}

	virtual kTraversal EnterDirectory(const char *fullpath, const char *dirname, const FSSpec * macspec)
	{
		ASSERT(m_item != 0L);

		Entries_Open (m_entries, fullpath);
		BuildIgnoredList(m_ignlist, fullpath);

		return kContinueTraversal;
	}

	virtual kTraversal ExitDirectory(const char *fullpath)
	{
		m_ignlist.erase(m_ignlist.begin(), m_ignlist.end());
		SetIcon(m_treeCtrl, m_item, true /*ignored*/, true,
			(EntnodeData *)m_treeCtrl.GetItemData(m_item));
		return kContinueTraversal;
	}

	virtual kTraversal OnError(const char *err, int errcode)
	{
		return kTraversalError;
	}

	virtual kTraversal OnIdle(const char *fullpath)
	{
		return kContinueTraversal;
	}

	virtual kTraversal OnDirectory(const char *fullpath,
		const char *fullname,
		const char *name,
		const struct stat & dir, const FSSpec * macspec)
	{
		if(stricmp(name, "cvs") == 0)
			return kSkipFile;

		// is the sub-directory ignored ?
		EntnodeData *data = Entries_SetVisited(fullpath, m_entries, name, dir, true, &m_ignlist);
		if(!m_isShowIgnored && data->IsIgnored())
			return kSkipFile;

		// create the item for the sub-directory
		TBrowserFillDummy traverse(m_treeCtrl, m_item, 0L, data);
		kTraversal res = FileTraverse(fullname, traverse);

		// assign the icon regarding our Entries info
		if(traverse.m_item != 0L)
		{
			SetIcon(m_treeCtrl, traverse.m_item, true /*ignored*/, false, data);
		}

		return kSkipFile;
	}
	
};

void CWinCvsBrowser::ResetBrowser(const char *path, bool notifyView)
{
	CWaitCursor doWait;
	
	// Must make a copy of 'path' before calling DeleteAllItems(), since
	// this function may have been called with 'm_root' passed as the first 
	// parameter (in fact it is in several places in the code) whose value
	// gets deleted by the call to DeleteAllItems() below, making 'path'
	// point to an invalid memory location
	CStr pathCopy(path);
	DeleteAllItems();
	bool isReload = !pathCopy.empty() && !m_root.empty() && stricmp(m_root, pathCopy) == 0;

	m_root = pathCopy;

	if(pathCopy.empty())
		return;

	if(!isReload)
	{
		if(HasPersistentSettings(m_root))
			LoadPersistentSettings(m_root);
		else
		{
			// check if the path has a CVS folder. If not,
			// we don't want the user to be prompted (like when 
			// WinCvs is starting for the first time).
			UStr cvsFolder(m_root);
			if(!cvsFolder.endsWith(kPathDelimiter))
				cvsFolder << kPathDelimiter;
			cvsFolder << "CVS";
			struct stat sb;
			if (stat(cvsFolder, &sb) != -1 && S_ISDIR(sb.st_mode))
				AskCreatePersistentSettings(m_root);
		}
	}

	CWincvsApp* app = (CWincvsApp *)AfxGetApp();
	if(stricmp(gOldLoc, m_root) != 0)
		gOldLoc = m_root;

	// update the history combo
	gHistoryLocs.Insert(m_root);
	CMainFrame *f = app->GetMainFrame();
	CComboBox & c = f->GetBrowseHistory();
	c.ResetContent();
	const std::vector<CStr> & list = gHistoryLocs.GetList();
	std::vector<CStr>::const_iterator i;
	for(i = list.begin(); i != list.end(); ++i)
	{
		c.InsertString(-1, *i);
	}
	c.SetCurSel(0);
	TRACE("Browse History has %d items\n", c.GetCount());

	CTreeCtrl& treeCtrl = GetTreeCtrl();
	TBrowserFillDummy traverse(treeCtrl, 0L, 0L, 0L);
	kTraversal res = FileTraverse(m_root, traverse);

	CBrowseFileView *fileView = app->GetFileView();
	if(fileView != 0L)
	{
		HTREEITEM root = treeCtrl.GetRootItem( );
		if(root != 0L)
		{
			treeCtrl.Expand(root, TVE_EXPAND);
			if(notifyView)
			{
				VERIFY(treeCtrl.Select(root, TVGN_CARET));
			}
		}
	}
}

void CWinCvsBrowser::SetNewStyle(long lStyleMask, BOOL bSetBits)
{
	long		lStyleOld;

	lStyleOld = GetWindowLong(m_hWnd, GWL_STYLE);
	lStyleOld &= ~lStyleMask;
	if (bSetBits)
		lStyleOld |= lStyleMask;

	SetWindowLong(m_hWnd, GWL_STYLE, lStyleOld);
	SetWindowPos(NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER);
}

void CWinCvsBrowser::RetrievePath(HTREEITEM item, CStr & path)
{
	CTreeCtrl& treeCtrl = GetTreeCtrl();
	CStr tmp, newPath;
	path = "";
	TV_ITEM pItem;
	char name[512];
	do
	{
		pItem.hItem = item;
		pItem.pszText = name;
		pItem.cchTextMax = 512;
		pItem.mask = TVIF_HANDLE | TVIF_TEXT;
		
		VERIFY(treeCtrl.GetItem(&pItem));
		item = treeCtrl.GetParentItem(item);

		tmp = path;
		newPath = item == 0L ? (const char *)m_root : pItem.pszText;
		if(!newPath.endsWith(kPathDelimiter))
			newPath << kPathDelimiter;
		newPath << path;
		path = newPath;
	} while(item != 0L);
}

void CWinCvsBrowser::OnItemexpanding(NMHDR* pNMHDR, LRESULT* pResult) 
{
	// prevent from changing directory if cvs is running
	CWincvsApp* app = (CWincvsApp *)AfxGetApp();
	if(app->gCvsRunning)
	{
		*pResult = TRUE;
		return;
	}

	CTreeCtrl& treeCtrl = GetTreeCtrl();
	NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;

	HTREEITEM item = pNMTreeView->itemNew.hItem;

	EntnodeData *data = (EntnodeData *)treeCtrl.GetItemData(item);
	if(data != 0L && data->IsMissing())
	{
		*pResult = TRUE;
		return;
	}

	// erase all the childs
	DeleteAllItems(item);

	CStr path;
	RetrievePath(item, path);

	if(pNMTreeView->action & TVE_COLLAPSE)
	{
		TBrowserFillDummy traverse(treeCtrl, 0L, item, 0L);
		kTraversal res = FileTraverse(path, traverse);
	}
	else if(pNMTreeView->action & TVE_EXPAND)
	{
		CSortList<ENTNODE> entries(200, ENTNODE::Compare);
		TBrowserFill traverse(treeCtrl, 0L, item, entries);
		kTraversal res = FileTraverse(path, traverse);

		// add the missing folders
		Entries_SetMissing(entries);
		int numEntries = entries.NumOfElements();
		for(int i = 0; i < numEntries; i++)
		{
			const ENTNODE & theNode = entries.Get(i);
			EntnodeData *data = theNode.Data();
			if(!data->IsMissing() || data->GetType() != ENT_SUBDIR)
				continue;
			
			TV_INSERTSTRUCT tvstruct;
			tvstruct.hParent = item;
			tvstruct.hInsertAfter = TVI_SORT;
			tvstruct.item.iImage = kIconFolderIgnoreLost;
			tvstruct.item.iSelectedImage = kIconFolderIgnoreLost;
			tvstruct.item.pszText = (char *)(*data)[EntnodeData::kName];
			tvstruct.item.lParam = (long)data->Ref();
			tvstruct.item.mask = TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_TEXT | TVIF_PARAM;
			treeCtrl.InsertItem(&tvstruct);

		}
	}

	*pResult = 0;
}

void CWinCvsBrowser::OnUpdateCmd(CCmdUI* pCmdUI, BOOL needCvsInfos)
{
	CWincvsApp* app = (CWincvsApp *)AfxGetApp();
	if(DisableCommon())
	{
		pCmdUI->Enable(FALSE);
		return;
	}
	CTreeCtrl& treeCtrl = GetTreeCtrl();
	HTREEITEM selItem = treeCtrl.GetSelectedItem();
	if(selItem == 0L)
	{
		pCmdUI->Enable(FALSE);
		return;
	}
	
	CStr path;
	RetrievePath(selItem, path);
	if(!path.endsWith(kPathDelimiter))
		path << kPathDelimiter;
	path << "CVS";

	struct stat sb;
	if (stat(path, &sb) == -1 || !S_ISDIR(sb.st_mode))
		pCmdUI->Enable(!needCvsInfos);
	else
		pCmdUI->Enable(needCvsInfos);
}

void CWinCvsBrowser::OnRButtonDown(UINT nFlags, CPoint point) 
{
	// prevent from changing directory if cvs is running
	CWincvsApp* app = (CWincvsApp *)AfxGetApp();
	if(app->gCvsRunning)
		return;

	// prompt a menu when hitting a selection
	// with the right button...
	CTreeCtrl& treeCtrl = GetTreeCtrl();
	UINT pFlags;
	HTREEITEM testItem = treeCtrl.HitTest(point, &pFlags);
	if(testItem != 0L && (pFlags & (TVHT_ONITEM|TVHT_ONITEMBUTTON)) != 0)
	{
		treeCtrl.SelectItem(testItem);
		// Locate the submenu AFTER item selection
		CMenu* pMyMenu=::GetCustomMenu(kCustomBrowserMenu,this);
		if (pMyMenu)
		{
			ClientToScreen(&point);
			pMyMenu->TrackPopupMenu(TPM_LEFTALIGN|TPM_LEFTBUTTON|TPM_RIGHTBUTTON, point.x, point.y, this);
			delete pMyMenu;
			return;
		}
	}
	CTreeView::OnRButtonDown(nFlags, point);	//L->R
}

void CWinCvsBrowser::OnSelchanging(NMHDR* pNMHDR, LRESULT* pResult) 
{
	// prevent from changing directory if cvs is running
	CWincvsApp* app = (CWincvsApp *)AfxGetApp();
	if(app->gCvsRunning)
	{
		*pResult = TRUE;
		return;
	}

	CTreeCtrl& treeCtrl = GetTreeCtrl();
	NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
	HTREEITEM item = pNMTreeView->itemNew.hItem;
	
	EntnodeData *data = (EntnodeData *)treeCtrl.GetItemData(item);
	if(data != 0L && data->IsMissing())
	{
		*pResult = TRUE;
		return;
	}

	*pResult = 0;
}

void CWinCvsBrowser::OnSelchanged(NMHDR* pNMHDR, LRESULT* pResult) 
{
	NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
	m_isSelectionChanged = true;
	NotifySelectionChange();

	*pResult = 0;
}

void CWinCvsBrowser::NotifySelectionChange()
{
	if(!m_isSelectionNotificationLocked)
	{
		if (m_isSelectionChanged)
		{
			CStr path;
			HTREEITEM item = GetTreeCtrl().GetSelectedItem();
			if (item)
			{
				RetrievePath(item, path);
				CWincvsApp* app = (CWincvsApp *)AfxGetApp();
				CBrowseFileView *fileView = app->GetFileView();
				if (fileView)
				{
					fileView->ResetView(path);
				}
				gCvsPrefs.SetLastWorkingDir(path);
			}
		}
	}
}

void CWinCvsBrowser::OnUpdateViewAdd(CCmdUI* pCmdUI) 
{
	OnUpdateCmd(pCmdUI, FALSE);
}

void CWinCvsBrowser::OnUpdateViewUpdate(CCmdUI* pCmdUI) 
{
	OnUpdateCmd(pCmdUI, TRUE);
}

void CWinCvsBrowser::OnViewAdd() 
{
	CTreeCtrl& treeCtrl = GetTreeCtrl();
	HTREEITEM selItem = treeCtrl.GetSelectedItem();
	
	CStr path;
	RetrievePath(selItem, path);
	CvsCmdAddFolder(path);
}

void CWinCvsBrowser::OnViewCommit() 
{
	CTreeCtrl& treeCtrl = GetTreeCtrl();
	HTREEITEM selItem = treeCtrl.GetSelectedItem();
	
	CStr path;
	RetrievePath(selItem, path);
	CvsCmdCommitFolder(path);
}

void CWinCvsBrowser::OnViewUpdate() 
{
	CTreeCtrl& treeCtrl = GetTreeCtrl();
	HTREEITEM selItem = treeCtrl.GetSelectedItem();
	
	CStr path;
	RetrievePath(selItem, path);
	CvsCmdUpdateFolder(path);
}

void CWinCvsBrowser::OnViewQueryUpdate() 
{
	CTreeCtrl& treeCtrl = GetTreeCtrl();
	HTREEITEM selItem = treeCtrl.GetSelectedItem();
	
	CStr path;
	RetrievePath(selItem, path);
	CvsCmdUpdateFolder(path, true);
}

void CWinCvsBrowser::OnViewDiff() 
{
	CTreeCtrl& treeCtrl = GetTreeCtrl();
	HTREEITEM selItem = treeCtrl.GetSelectedItem();
	
	CStr path;
	RetrievePath(selItem, path);
	CvsCmdDiffFolder(path);
}

void CWinCvsBrowser::OnViewLog() 
{
	CTreeCtrl& treeCtrl = GetTreeCtrl();
	HTREEITEM selItem = treeCtrl.GetSelectedItem();
	
	CStr path;
	RetrievePath(selItem, path);
	CvsCmdLogFolder(path);
}

void CWinCvsBrowser::OnViewStatus() 
{
	CTreeCtrl& treeCtrl = GetTreeCtrl();
	HTREEITEM selItem = treeCtrl.GetSelectedItem();
	
	CStr path;
	RetrievePath(selItem, path);
	CvsCmdStatusFolder(path);
}

void CWinCvsBrowser::OnViewLock() 
{
	CTreeCtrl& treeCtrl = GetTreeCtrl();
	HTREEITEM selItem = treeCtrl.GetSelectedItem();
	
	CStr path;
	RetrievePath(selItem, path);
	CvsCmdLockFolder(path);
}

void CWinCvsBrowser::OnViewUnlock() 
{
	CTreeCtrl& treeCtrl = GetTreeCtrl();
	HTREEITEM selItem = treeCtrl.GetSelectedItem();
	
	CStr path;
	RetrievePath(selItem, path);
	CvsCmdUnlockFolder(path);
}

void CWinCvsBrowser::OnViewWatchOn() 
{
	CTreeCtrl& treeCtrl = GetTreeCtrl();
	HTREEITEM selItem = treeCtrl.GetSelectedItem();
	
	CStr path;
	RetrievePath(selItem, path);
	CvsCmdWatchOnFolder(path);
}

void CWinCvsBrowser::OnViewWatchOff() 
{
	CTreeCtrl& treeCtrl = GetTreeCtrl();
	HTREEITEM selItem = treeCtrl.GetSelectedItem();
	
	CStr path;
	RetrievePath(selItem, path);
	CvsCmdWatchOffFolder(path);
}

void CWinCvsBrowser::OnViewEdit() 
{
	CTreeCtrl& treeCtrl = GetTreeCtrl();
	HTREEITEM selItem = treeCtrl.GetSelectedItem();
	
	CStr path;
	RetrievePath(selItem, path);
	CvsCmdEditFolder(path);
}

void CWinCvsBrowser::OnViewUnedit() 
{
	CTreeCtrl& treeCtrl = GetTreeCtrl();
	HTREEITEM selItem = treeCtrl.GetSelectedItem();
	
	CStr path;
	RetrievePath(selItem, path);
	CvsCmdUneditFolder(path);
}

void CWinCvsBrowser::OnViewWatchers() 
{
	CTreeCtrl& treeCtrl = GetTreeCtrl();
	HTREEITEM selItem = treeCtrl.GetSelectedItem();
	
	CStr path;
	RetrievePath(selItem, path);
	CvsCmdWatchersFolder(path);
}

void CWinCvsBrowser::OnViewEditors() 
{
	CTreeCtrl& treeCtrl = GetTreeCtrl();
	HTREEITEM selItem = treeCtrl.GetSelectedItem();
	
	CStr path;
	RetrievePath(selItem, path);
	CvsCmdEditorsFolder(path);
}

void CWinCvsBrowser::OnViewRelease() 
{
	CTreeCtrl& treeCtrl = GetTreeCtrl();
	HTREEITEM selItem = treeCtrl.GetSelectedItem();
	
	CStr path;
	RetrievePath(selItem, path);
	CvsCmdReleaseFolder(path);
}

void CWinCvsBrowser::OnViewTagNew() 
{
	CTreeCtrl& treeCtrl = GetTreeCtrl();
	HTREEITEM selItem = treeCtrl.GetSelectedItem();
	
	CStr path;
	RetrievePath(selItem, path);
	CvsCmdTagCreateFolder(path);
}

void CWinCvsBrowser::OnViewTagDelete() 
{
	CTreeCtrl& treeCtrl = GetTreeCtrl();
	HTREEITEM selItem = treeCtrl.GetSelectedItem();
	
	CStr path;
	RetrievePath(selItem, path);
	CvsCmdTagDeleteFolder(path);
}

void CWinCvsBrowser::OnViewTagBranch() 
{
	CTreeCtrl& treeCtrl = GetTreeCtrl();
	HTREEITEM selItem = treeCtrl.GetSelectedItem();
	
	CStr path;
	RetrievePath(selItem, path);
	CvsCmdTagBranchFolder(path);
}

static void onIgnoreChanged(CObject *obj)
{
	((CWinCvsBrowser *)obj)->OnIgnoreChanged();
}

void CWinCvsBrowser::OnInitialUpdate() 
{
	CTreeView::OnInitialUpdate();
	
	CTreeCtrl& treeCtrl = GetTreeCtrl();
	HTREEITEM root = treeCtrl.GetRootItem( );
	if(root != 0L)
	{
		treeCtrl.Expand(root, TVE_EXPAND);
		VERIFY(treeCtrl.Select(root, TVGN_CARET));
	}

	if (CWincvsApp* app = (CWincvsApp *)AfxGetApp())
    { 
		app->GetIgnoreModel()->GetNotificationManager()->CheckIn(this, onIgnoreChanged);
    }
}

void CWinCvsBrowser::OnViewExplore() 
{
	CTreeCtrl& treeCtrl = GetTreeCtrl();
	HTREEITEM selItem = treeCtrl.GetSelectedItem();
	
	CStr path;
	RetrievePath(selItem, path);
	HINSTANCE hInst = ShellExecute(*AfxGetMainWnd(), "explore", path,
			0L, 0L, SW_SHOWDEFAULT);
	if((long)hInst < 32)
	{
		cvs_err("Unable to explore '%s' (error %d)\n", (char *)path, GetLastError());
	}
}

void CWinCvsBrowser::OnUpdateViewExplore(CCmdUI* pCmdUI) 
{
	CWincvsApp* app = (CWincvsApp *)AfxGetApp();
	if(DisableCommon())
	{
		pCmdUI->Enable(FALSE);
		return;
	}
	CTreeCtrl& treeCtrl = GetTreeCtrl();
	HTREEITEM selItem = treeCtrl.GetSelectedItem();
	if(selItem == 0L)
	{
		pCmdUI->Enable(FALSE);
		return;
	}
	pCmdUI->Enable(TRUE);
}

bool CWinCvsBrowser::StepToLocation(const char *path, bool notifyView)
{
	bool bRes = false;	//return

	CStr root(m_root);
	if(!root.endsWith(kPathDelimiter))
		root << kPathDelimiter;
	CStr subpath(path);
	if(!subpath.endsWith(kPathDelimiter))
		subpath << kPathDelimiter;

	// check if it is a sub-path

	//JK - for windows case insensitive seems better
	if(strnicmp(root, subpath, root.length()) != 0)
		return false;

	CTreeCtrl& treeCtrl = GetTreeCtrl();
	HTREEITEM item = treeCtrl.GetRootItem();
	HTREEITEM lastitem = 0L;
	if(item == 0L)
		return false;

	if (stricmp(root, subpath) == 0)
	{
		// must select root node
		lastitem = item;
		treeCtrl.Expand(item, TVE_EXPAND);
	}
	else
	{	
		// step inside synchronized
		const char *tmp = (const char *)subpath + root.length() - 1;
		while((tmp = strchr(tmp, kPathDelimiter)) != 0L)
	    {
		    const char *name = ++tmp;
		    if(name[0] == '\0')
			    break;

		    CStr subname;
		    const char *tmp2 = strchr(name, kPathDelimiter);
		    if(tmp2 == 0L)
			    subname = name;
		    else
			    subname.set(name, tmp2 - name);

		    HTREEITEM childItem = treeCtrl.GetNextItem(item, TVGN_CHILD);

		    // find the subitem which matches this name
		    while(childItem != 0L)
		    {
			    EntnodeData *data = (EntnodeData *)treeCtrl.GetItemData(childItem);
			    if(data != 0L)
			    {
				    if(_stricmp((*data)[EntnodeData::kName], subname) == 0)
				    {
					    // found it !
					    lastitem = item = childItem;
					    treeCtrl.Expand(item, TVE_EXPAND);
					    break;
				    }
			    }

			    childItem = treeCtrl.GetNextItem(childItem, TVGN_NEXT);
		    }
	    }
    }

	// in case this is called by the view, turn off notifying
	// when the item gets selected
	// JK - {!= 0} is for: Compiler Warning (level 3) C4800
	if(lastitem != 0L)
	{
		CSelectionChangeNotifier notifier(this, notifyView); 
		VERIFY( bRes = (treeCtrl.Select(lastitem, TVGN_CARET) != 0) );
	}

	return bRes;
}

HTREEITEM CWinCvsBrowser::GetItemByLocation(const char *path)
{
	CStr root(m_root);
	if(!root.endsWith(kPathDelimiter))
		root << kPathDelimiter;
	CStr subpath(path);
	if(!subpath.endsWith(kPathDelimiter))
		subpath << kPathDelimiter;

	// check if it is a sub-path
	if(strncmp(root, subpath, root.length()) != 0)
		return 0L;

	CTreeCtrl& treeCtrl = GetTreeCtrl();
	HTREEITEM item = treeCtrl.GetRootItem();
	if(item == 0L)
		return 0L;

	// step inside synchronized
	const char *tmp = (const char *)subpath + root.length() - 1;
	while((tmp = strchr(tmp, kPathDelimiter)) != 0L)
	{
		const char *name = ++tmp;
		if(name[0] == '\0')
			break;

		CStr subname;
		const char *tmp2 = strchr(name, kPathDelimiter);
		if(tmp2 == 0L)
			subname = name;
		else
			subname.set(name, tmp2 - name);

		HTREEITEM childItem = treeCtrl.GetNextItem(item, TVGN_CHILD);

		// find the subitem which matches this name
		while(childItem != 0L)
		{
			EntnodeData *data = (EntnodeData *)treeCtrl.GetItemData(childItem);
			if(data != 0L)
			{
				if(_stricmp((*data)[EntnodeData::kName], subname) == 0)
				{
					item = childItem;
					break;
				}
			}

			childItem = treeCtrl.GetNextItem(childItem, TVGN_NEXT);
		}
		if(childItem == 0L)
			return 0L;
	}

	return item;
}

void CWinCvsBrowser::StoreExpanded(std::vector<CStr> & allExpanded, HTREEITEM root)
{
	HTREEITEM item = root;
	CTreeCtrl& treeCtrl = GetTreeCtrl();
	if(item == 0L)
		item = treeCtrl.GetRootItem();
	if(item == 0L)
		return;

	TV_ITEM pItem;
	pItem.hItem = item;
	pItem.mask = TVIF_STATE;
	VERIFY(treeCtrl.GetItem(&pItem));

	if(pItem.state & TVIS_EXPANDED)
	{
		CStr path;
		RetrievePath(item, path);
		allExpanded.push_back(path);
	}

	HTREEITEM childItem = treeCtrl.GetNextItem(item, TVGN_CHILD);

	while(childItem != 0L)
	{
		StoreExpanded(allExpanded, childItem);
		childItem = treeCtrl.GetNextItem(childItem, TVGN_NEXT);
	}
}

void CWinCvsBrowser::ResetView(bool forceReload, bool notifyView)
{
	// no outstanding request to reset the view any more
	m_isResetViewPending = false;

	CWaitCursor doWait;

	bool contentChanged = false;
	vector<CStr> allExpanded;
	StoreExpanded(allExpanded);
	CTreeCtrl& treeCtrl = GetTreeCtrl();
	CStr selPath;
	HTREEITEM selItem = treeCtrl.GetSelectedItem();
	if(selItem != 0L)
		RetrievePath(selItem, selPath);

	if(forceReload)
	{
		ResetBrowser(m_root);
		contentChanged = true;
	}

	if(contentChanged)
	{
		// restore the expanded paths and the selected item
		vector<CStr>::const_iterator i;
		for(i = allExpanded.begin(); i != allExpanded.end(); ++i)
		{
			StepToLocation(*i);
		}
		if(!selPath.empty())
		{
			selItem = GetItemByLocation(selPath);
			if(selItem != 0L)
				VERIFY(treeCtrl.Select(selItem, TVGN_CARET));
		}
	}

	// we don't need to notify the file view if we force the reload
	// because that's already done
	if(notifyView)
	{
		CWincvsApp* app = (CWincvsApp *)AfxGetApp();
		app->GetFileView()->ResetView();
	}
}

void CWinCvsBrowser::OnUpdateViewReload(CCmdUI* pCmdUI) 
{
	CWincvsApp* app = (CWincvsApp *)AfxGetApp();
	pCmdUI->Enable(!app->gCvsRunning);
}

void CWinCvsBrowser::OnViewReload() 
{
	ResetView(true, true);
}

BOOL CWinCvsBrowser::OnMacroSel(UINT nID)
{
	CTcl_Interp interp;
	CMacroEntry & entry = gMacrosSel.entries[nID - ID_MACRO_SEL];
	CStr path = entry.path;
	CTcl_Interp::Unixfy(path);

	CTreeCtrl& treeCtrl = GetTreeCtrl();
	HTREEITEM selItem = treeCtrl.GetSelectedItem();
	if(selItem == 0L)
		return 1;

	CStr selPath;
	RetrievePath(selItem, selPath);
	CStr uppath, folder;
	SplitPath(selPath, uppath, folder);

	bool deleteData = false;
	EntnodeData *data = (EntnodeData *)treeCtrl.GetItemData(selItem);
	if(data == 0L)
	{
		deleteData = true;
		EntnodePath *thePath = new EntnodePath(uppath);
		if(thePath == 0L)
			throw ENOMEM;
		data = new EntnodeFolder(folder, thePath);
		thePath->UnRef();
		if(data == 0L)
			throw ENOMEM;
	}

	TclBrowserReset();
	TclBrowserAppend(uppath, data);

	interp.DoScriptVar("source \"%s\"", (const char *)path);

	if(deleteData)
		data->UnRef();

	return 1;
}

void CWinCvsBrowser::OnUpdateMacroSel(CCmdUI* pCmdUI) 
{
	CWincvsApp* app = (CWincvsApp *)AfxGetApp();
	CTreeCtrl& treeCtrl = GetTreeCtrl();
	HTREEITEM selItem = treeCtrl.GetSelectedItem();
	BOOL bCanRunTclMacros = TRUE;					//we are hoping for the best...
	CString strMenuText = "Tcl is not avalaible !";	//but preparing for the worst
	
	if(DisableCommon() || selItem == 0L || !CTcl_Interp::IsAvail())
	{
		if( selItem == 0L && CTcl_Interp::IsAvail() )
		{
			strMenuText = "No selection...";
		}
		
		bCanRunTclMacros = FALSE;
	}

	if( app->gCvsRunning )
	{
		strMenuText = "CVS is running...";
	}
	
	if(pCmdUI->m_pSubMenu != 0L || pCmdUI->m_pMenu == 0L)
		return;

	int iMRUMax = pCmdUI->m_pMenu->GetMenuItemCount();
	for (int iMRU = 0; iMRU < iMRUMax; iMRU++)
		pCmdUI->m_pMenu->DeleteMenu(pCmdUI->m_nID + iMRU, MF_BYCOMMAND);

    //check if we should enable the macros
	if( !bCanRunTclMacros )
	{
		pCmdUI->m_pMenu->InsertMenu( -1,
			MF_STRING | MF_BYPOSITION, 
			ID_MACRO_SEL,
			strMenuText );
		pCmdUI->Enable(FALSE);
		
		return;
	}
	
	vector<CMacroEntry>::const_iterator i;
	for(i = gMacrosSel.entries.begin(); i != gMacrosSel.entries.end(); ++i)
	{
		pCmdUI->m_pMenu->InsertMenu(pCmdUI->m_nIndex++,
			MF_STRING | MF_BYPOSITION, pCmdUI->m_nID++,
			(*i).name);
	}

	pCmdUI->m_nIndexMax = pCmdUI->m_pMenu->GetMenuItemCount();
	pCmdUI->m_nIndex--; // point to last menu added
	pCmdUI->m_bEnableChanged = TRUE;    // all the added items are enabled
}

BOOL CWinCvsBrowser::PreCreateWindow(CREATESTRUCT& cs) 
{
	cs.style |= WS_TABSTOP | TVS_SHOWSELALWAYS;

	return CTreeView::PreCreateWindow(cs);
}

bool CWinCvsBrowser::DisableCommon()
{
	CWincvsApp *app = (CWincvsApp *)AfxGetApp();
	return app->gCvsRunning || gCvsPrefs.empty();
}


void CWinCvsBrowser::OnViewCheckout() 
{
	CvsCmdCheckoutModule();
}

void CWinCvsBrowser::OnUpdateViewCheckout(CCmdUI* pCmdUI) 
{
	if(DisableCommon())
	{
		pCmdUI->Enable(FALSE);
		return;
	}
	pCmdUI->Enable(TRUE);
	pCmdUI->SetText("Chec&kout module...");
}

void CWinCvsBrowser::OnViewImport() 
{
	CvsCmdImportModule();
}

void CWinCvsBrowser::OnUpdateViewImport(CCmdUI* pCmdUI) 
{
	if(DisableCommon())
	{
		pCmdUI->Enable(FALSE);
		return;
	}
	pCmdUI->Enable(TRUE);
	pCmdUI->SetText("&Import module...");
}

BOOL CWinCvsBrowser::OnCustomize(UINT nID)
{
	CustomizeMenus(nID == ID_CUST_FILES_MENU ? kCustomFilesMenu : kCustomBrowserMenu);
	return TRUE;
}

void CWinCvsBrowser::OnUpdateCustomize(CCmdUI* pCmdUI)
{
	pCmdUI->Enable(TRUE);
}

BOOL CWinCvsBrowser::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) 
{
	CWincvsApp* app = (CWincvsApp *)AfxGetApp();
	if(app->gCvsRunning)
	{
		SetCursor(AfxGetApp()->LoadStandardCursor(IDC_WAIT));
		return TRUE;
	}
	
	return CTreeView::OnSetCursor(pWnd, nHitTest, message);
}

void CWinCvsBrowser::OnDestroy() 
{
	if (CWincvsApp* app = (CWincvsApp *)AfxGetApp())
    { 
		app->GetIgnoreModel()->GetNotificationManager()->CheckOut(this);
    }

	CTreeView::OnDestroy();
}

void CWinCvsBrowser::OnIgnoreChanged()
{
	if (!m_isResetViewPending)
	{
		m_isResetViewPending = true;
		PostMessage(WM_RESETVIEW);
	}	
}

LRESULT CWinCvsBrowser::OnResetView(WPARAM, LPARAM)
{
	ResetView(true, false);
	return 0;
}
