// 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"
#include "PythonGlue.h"
#include "BrowseViewHandlers.h"

#include "umain.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;

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_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_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_COMMAND(ID_VIEW_RESERVEDEDIT, OnViewReservededit)
	ON_COMMAND(ID_VIEW_FORCEEDIT, OnViewForceedit)
	ON_COMMAND(ID_APP_CMDLINE, OnAppCmdline)
	ON_UPDATE_COMMAND_UI(ID_VIEW_DIFF, OnUpdateViewUpdate)
	ON_UPDATE_COMMAND_UI(ID_VIEW_EDIT, OnUpdateViewUpdate)
	ON_UPDATE_COMMAND_UI(ID_VIEW_RESERVEDEDIT, OnUpdateViewUpdate)
	ON_UPDATE_COMMAND_UI(ID_VIEW_FORCEEDIT, OnUpdateViewUpdate)
	ON_UPDATE_COMMAND_UI(ID_VIEW_EDITORS, 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)
	ON_UPDATE_COMMAND_UI(ID_APP_CMDLINE, OnUpdateViewUpdate)
	ON_COMMAND(ID_APP_RTAGBRANCH, OnAppRtagbranch)
	ON_COMMAND(ID_APP_RTAGCREATE, OnAppRtagcreate)
	ON_COMMAND(ID_APP_RTAGDELETE, OnAppRtagdelete)
	ON_UPDATE_COMMAND_UI(ID_APP_RTAGBRANCH, OnUpdateViewUpdate)
	ON_UPDATE_COMMAND_UI(ID_APP_RTAGCREATE, OnUpdateViewUpdate)
	ON_UPDATE_COMMAND_UI(ID_APP_RTAGDELETE, OnUpdateViewUpdate)
	//}}AFX_MSG_MAP
	ON_UPDATE_COMMAND_UI_RANGE(ID_MACRO_START, ID_MACRO_END, OnUpdateMacro)
	ON_COMMAND_EX_RANGE(ID_MACRO_START, ID_MACRO_END, OnMacro)
	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));

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

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

	// set the initial root
	CMainFrame* mainFrm = app->GetMainFrame();
	CHistComboBoxEx& hist = mainFrm->GetBrowseHistory();
	CStr newpath((const char *)hist.GetOldLoc());
	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;
	bool m_hascvs;
	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_hascvs(false), 
			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, m_hascvs, 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 )
		{
			m_hascvs = true;
			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, m_hascvs, 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();
	CMainFrame* mainFrm = app->GetMainFrame();
	CHistComboBoxEx& hist = mainFrm->GetBrowseHistory();
	hist.NewRoot(m_root);

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

/*!
	Process the selection handler command
	\param handler Command selection handler
	\return true if the command was processed, false otherwise
*/
bool CWinCvsBrowser::ProcessSelectionCommand(KiSelectionHandler* handler)
{
	bool res = false;

	CTreeCtrl& treeCtrl = GetTreeCtrl();
	HTREEITEM selItem = treeCtrl.GetSelectedItem();
	
	if( selItem )
	{
		CStr path;

		RetrievePath(selItem, path);
		handler->OnFolder(path);

		res = true;
	}

	return res;
}

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

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

void CWinCvsBrowser::OnViewAdd() 
{
	KoAddHandler handler;
	ProcessSelectionCommand(&handler);
}

void CWinCvsBrowser::OnViewCommit() 
{
	KoCommitHandler handler;
	ProcessSelectionCommand(&handler);
}

void CWinCvsBrowser::OnViewUpdate() 
{
	KoUpdateHandler handler;
	ProcessSelectionCommand(&handler);
}

void CWinCvsBrowser::OnViewQueryUpdate() 
{
	KoQueryUpdateHandler handler;
	ProcessSelectionCommand(&handler);
}

void CWinCvsBrowser::OnViewDiff() 
{
	KoDiffHandler handler;
	ProcessSelectionCommand(&handler);
}

void CWinCvsBrowser::OnViewLog() 
{
	KoLogHandler handler;
	ProcessSelectionCommand(&handler);
}

void CWinCvsBrowser::OnViewStatus() 
{
	KoStatusHandler handler;
	ProcessSelectionCommand(&handler);
}

void CWinCvsBrowser::OnViewUnlock() 
{
	KoUnlockHandler handler;
	ProcessSelectionCommand(&handler);
}

void CWinCvsBrowser::OnViewWatchOn() 
{
	KoWatchOnHandler handler;
	ProcessSelectionCommand(&handler);
}

void CWinCvsBrowser::OnViewWatchOff() 
{
	KoWatchOffHandler handler;
	ProcessSelectionCommand(&handler);
}

void CWinCvsBrowser::OnViewEdit() 
{
	KoEditHandler handler;
	ProcessSelectionCommand(&handler);
}

void CWinCvsBrowser::OnViewReservededit() 
{
	KoReservedEditHandler handler;
	ProcessSelectionCommand(&handler);
}

void CWinCvsBrowser::OnViewForceedit() 
{
	KoForceEditHandler handler;
	ProcessSelectionCommand(&handler);
}

void CWinCvsBrowser::OnViewUnedit() 
{
	KoUneditHandler handler;
	ProcessSelectionCommand(&handler);
}

void CWinCvsBrowser::OnViewWatchers() 
{
	KoWatchersHandler handler;
	ProcessSelectionCommand(&handler);
}

void CWinCvsBrowser::OnViewEditors() 
{
	KoEditorsHandler handler;
	ProcessSelectionCommand(&handler);
}

void CWinCvsBrowser::OnViewRelease() 
{
	KoReleaseHandler handler;
	ProcessSelectionCommand(&handler);
}

void CWinCvsBrowser::OnViewTagNew() 
{
	KoCreateTagHandler handler;
	ProcessSelectionCommand(&handler);
}

void CWinCvsBrowser::OnViewTagDelete() 
{
	KoDeleteTagHandler handler;
	ProcessSelectionCommand(&handler);
}

void CWinCvsBrowser::OnViewTagBranch() 
{
	KoBranchTagHandler handler;
	ProcessSelectionCommand(&handler);
}

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
	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
	// {!= 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;
	CWndRedrawMngr redrawMngr(this);

	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));
				treeCtrl.EnsureVisible(selItem);
			}
		}
	}

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

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

BOOL CWinCvsBrowser::OnMacro(UINT nID)
{
	PyDoPython(WINCMD_to_UCMD(nID));

	return 1;
}

void CWinCvsBrowser::OnUpdateMacro(CCmdUI* pCmdUI) 
{
	if( DisableCommon() )
	{
		pCmdUI->Enable(FALSE);
		return;
	}

	// fill the cache with current selection
	if( !PyIsUICacheValid() )
	{
		CTreeCtrl& treeCtrl = GetTreeCtrl();
		HTREEITEM selItem = treeCtrl.GetSelectedItem();
		if( selItem != 0L )
		{
			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);
				data = new EntnodeFolder(folder, thePath);
				thePath->UnRef();
			}

			PyAppendCache(data);

			if( deleteData )
				data->UnRef();
		}
	}

	UCmdUI ucmdui(WINCMD_to_UCMD(pCmdUI->m_nID));
	ucmdui.pCmdUI = pCmdUI;
	PyDoCmdUI(&ucmdui);
}

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

	return CTreeView::PreCreateWindow(cs);
}

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

void CWinCvsBrowser::OnViewCheckout() 
{
	KoCheckoutHandler handler;
	ProcessSelectionCommand(&handler);
}

void CWinCvsBrowser::OnUpdateViewCheckout(CCmdUI* pCmdUI) 
{
	if( DisableCommon() )
	{
		pCmdUI->Enable(FALSE);
		return;
	}

	pCmdUI->Enable(TRUE);
	pCmdUI->SetText("Chec&kout module...");
}

void CWinCvsBrowser::OnViewImport() 
{
	KoImportHandler handler;
	ProcessSelectionCommand(&handler);
}

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

void CWinCvsBrowser::OnAppCmdline() 
{
	KoCommandLineHandler handler;
	ProcessSelectionCommand(&handler);
}

void CWinCvsBrowser::OnAppRtagbranch() 
{
	KoRtagBranchHandler handler;
	ProcessSelectionCommand(&handler);
}

void CWinCvsBrowser::OnAppRtagcreate() 
{
	KoRtagCreateHandler handler;
	ProcessSelectionCommand(&handler);
}

void CWinCvsBrowser::OnAppRtagdelete() 
{
	KoRtagDeleteHandler handler;
	ProcessSelectionCommand(&handler);
}
