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

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

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

/*
 * Author : Alexandre Parenteau <aubonbeurre@hotmail.com> --- April 1998
 */

/*
 * BrowseFileView.cpp -- Implements the view with all the files
 * 
 */

#include "stdafx.h"
#include <process.h>
#include <vector>
#include <IO.h>
#include "wincvs.h"

#include "BrowseFileView.h"
#include "FileTraversal.h"
#include "CvsCommands.h"
#include "MultiFiles.h"
#include "CvsArgs.h"
#include "AppConsole.h"
#include "CvsPrefs.h"
#include "CvsIgnore.h"
#include "TclGlue.h"
#include "MacrosSetup.h"
#include "MoveToTrash.h"
#include "WinCvsBrowser.h"
#include "MainFrm.h"
#include "wincvs_winutil.h"
#include "BrowseViewModel.h"
#include "BrowseViewHandlers.h"
#include "FileViewWatcher.h"
#include "BrowseViewColumn.h"
#include "PythonGlue.h"

#include "umain.h"

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

static const int FILECHANGE_TIMER = 999;
static const int kSpc = 3;
static const int WM_RESETVIEW = WM_APP + 12;

// persistent values
CPersistentInt gFileViewSort("P_FileViewSort", EntnodeData::kName);
CPersistentBool gFileViewSortAsc("P_FileViewSortAsc", false);

/////////////////////////////////////////////////////////////////////////////
// CBrowseFileView

IMPLEMENT_DYNCREATE(CBrowseFileView, CListView)

BEGIN_MESSAGE_MAP(CBrowseFileView, CListView)
	//{{AFX_MSG_MAP(CBrowseFileView)
	ON_COMMAND(ID_VIEW_SMALLICONS, OnViewSmallIcons)
	ON_COMMAND(ID_VIEW_FULLLIST, OnViewList)
	ON_COMMAND(ID_VIEW_ROWDETAILS, OnViewFullRowDetails)
	ON_UPDATE_COMMAND_UI(ID_VIEW_SMALLICONS, OnUpdateViewSmallIcons)
	ON_UPDATE_COMMAND_UI(ID_VIEW_ADD, OnUpdateViewAdd)
	ON_UPDATE_COMMAND_UI(ID_VIEW_ADDB, OnUpdateViewAddB)
	ON_UPDATE_COMMAND_UI(ID_VIEW_COMMIT, OnUpdateViewUpdate)
	ON_UPDATE_COMMAND_UI(ID_VIEW_RELEASE, OnUpdateViewRelease)
	ON_UPDATE_COMMAND_UI(ID_VIEW_RMV, OnUpdateViewRmv)
	ON_UPDATE_COMMAND_UI(ID_VIEW_GRAPH, OnUpdateViewGraph)
	ON_UPDATE_COMMAND_UI(ID_VIEW_FULLLIST, OnUpdateViewList)
	ON_UPDATE_COMMAND_UI(ID_VIEW_EDITSELDEF, OnUpdateViewEditseldef)
	ON_UPDATE_COMMAND_UI(ID_VIEW_ROWDETAILS, OnUpdateViewFullRowDetails)
	ON_WM_LBUTTONDOWN()
	ON_WM_LBUTTONDBLCLK()
	ON_NOTIFY_REFLECT(LVN_COLUMNCLICK, OnColumnclick)
	ON_COMMAND(ID_VIEW_ADD, OnViewAdd)
	ON_COMMAND(ID_VIEW_ADDB, OnViewAddb)
	ON_COMMAND(ID_VIEW_COMMIT, OnViewCommit)
	ON_COMMAND(ID_VIEW_RMV, OnViewRmv)
	ON_COMMAND(ID_VIEW_UPDATE, OnViewUpdate)
	ON_COMMAND(ID_VIEW_QUERYUPDATE, OnViewQueryUpdate)
	ON_UPDATE_COMMAND_UI(ID_VIEW_RELOAD, OnUpdateViewReload)
	ON_COMMAND(ID_VIEW_RELOAD, OnViewReload)
	ON_NOTIFY_REFLECT(LVN_KEYDOWN, OnKeydown)
	ON_COMMAND(ID_VIEW_UPONE, OnViewUpone)
	ON_UPDATE_COMMAND_UI(ID_VIEW_UPONE, OnUpdateViewUpone)
	ON_COMMAND(ID_VIEW_TRASH, OnViewTrash)
	ON_UPDATE_COMMAND_UI(ID_VIEW_TRASH, OnUpdateViewTrash)
	ON_COMMAND(ID_VIEW_DIFF, OnViewDiff)
	ON_COMMAND(ID_VIEW_LOG, OnViewLog)
	ON_COMMAND(ID_VIEW_GRAPH, OnViewGraph)
	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_WM_RBUTTONDOWN()
	ON_COMMAND(ID_VIEW_EXPLORE, OnViewExplore)
	ON_UPDATE_COMMAND_UI(ID_VIEW_EXPLORE, OnUpdateViewExplore)
	ON_WM_TIMER()
	ON_COMMAND(ID_VIEW_EDITSEL, OnViewEditsel)
	ON_COMMAND(ID_VIEW_EDITSELDEF, OnViewEditseldef)
	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_NOTIFY_REFLECT(LVN_BEGINDRAG, OnBegindrag)
	ON_NOTIFY_REFLECT(LVN_BEGINRDRAG, OnBeginrdrag)
	ON_WM_SETCURSOR()	
	ON_NOTIFY_REFLECT(LVN_DELETEITEM, OnDeleteitem)
	ON_WM_DESTROY()
	ON_UPDATE_COMMAND_UI(ID_QUERY_ANNOTATE, OnUpdateQueryAnnotate)
	ON_COMMAND(ID_QUERY_ANNOTATE, OnQueryAnnotate)
	ON_COMMAND(ID_EDIT_SELECT_ALL, OnEditSelectAll)
	ON_COMMAND(ID_VIEW_RESERVEDEDIT, OnViewReservededit)
	ON_COMMAND(ID_VIEW_FORCEEDIT, OnViewForceedit)
	ON_COMMAND(ID_VIEW_ADDU, OnViewAddu)
	ON_COMMAND(ID_APP_CMDLINE, OnAppCmdline)
	ON_UPDATE_COMMAND_UI(ID_APP_CMDLINE, OnUpdateCvsCmd)
	ON_UPDATE_COMMAND_UI(ID_VIEW_ADDU, OnUpdateViewAddB)
	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_TAGNEW, OnUpdateViewUpdate)
	ON_UPDATE_COMMAND_UI(ID_VIEW_TAGDELETE, OnUpdateViewUpdate)
	ON_UPDATE_COMMAND_UI(ID_VIEW_TAGBRANCH, 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_EDITSEL, OnUpdateViewTrash)
	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, OnUpdateCvsCmd)
	ON_UPDATE_COMMAND_UI(ID_APP_RTAGCREATE, OnUpdateCvsCmd)
	ON_UPDATE_COMMAND_UI(ID_APP_RTAGDELETE, OnUpdateCvsCmd)
	//}}AFX_MSG_MAP
	ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)
	ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)
	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(KoWatcher::WM_FILECHANGE, OnFileChange)
	ON_MESSAGE(WM_RESETVIEW, OnResetView)
END_MESSAGE_MAP()

CImageList CBrowseFileView::m_SmallImageList;
CImageList CBrowseFileView::m_StateImageList;

// context for traversing
class KoTraversalContext
{	
    bool m_isRecursive;
	bool m_isShowIgnored;
	KiFilterModel* m_filter;

public:
	CBrowseFileView* m_view;

    
	KoTraversalContext(CBrowseFileView* view)
	: m_view(view), m_filter(NULL), m_isRecursive(false), m_isShowIgnored(false)
	{
		if (CWincvsApp* app = (CWincvsApp *)AfxGetApp())
        { 
			m_filter = app->GetFilterModel();
			m_isRecursive = app->GetRecursionModel()->IsShowRecursive();
			m_isShowIgnored = app->GetIgnoreModel()->IsShowIgnored();
        }
	}

	bool IsRecursive() const
	{
		return m_isRecursive;
	}

	bool IsShowIgnored() const
	{
		return m_isShowIgnored;
	}

	bool IsMatch(EntnodeData* data) const
	{
		return m_filter->IsMatch(data);
	}
};

class TViewFill : public TraversalReport
{
public:
	CSortList<ENTNODE> m_entries;
	vector<CStr> m_ignlist;
	const KoTraversalContext* m_context;

	TViewFill(const KoTraversalContext* context) 
	: m_context(context)
	, m_entries(200, ENTNODE::Compare)
    { }

	CBrowseFileView* GetView() const 
	{
		return m_context->m_view;
	}

	CListCtrl& GetListCtrl() const
	{
		return GetView()->GetListCtrl();
	}

	bool IsRecursive() const
	{
		return m_context->IsRecursive();
	}

	bool IsShowIgnored() const
	{
		return m_context->IsShowIgnored();
	}

	virtual kTraversal EnterDirectory(const char *fullpath, const char *dirname, const FSSpec * macspec)
	{
		Entries_Open (m_entries, fullpath);
		BuildIgnoredList(m_ignlist, fullpath);
		return kContinueTraversal;
	}

	virtual kTraversal ExitDirectory(const char *fullpath)
	{
		// add the missing files
		Entries_SetMissing(m_entries);

		int numEntries = m_entries.NumOfElements();
		for(int i = 0; i < numEntries; i++)
		{
			const ENTNODE & theNode = m_entries.Get(i);
			EntnodeData *data = theNode.Data();
			if (data->IsMissing() && m_context->IsMatch(data))
			{
				if (!IsRecursive() || data->GetType() == ENT_FILE)
				{
					int iImage = data->GetType() == ENT_FILE ?
								(data->IsRemoved() ? kFileIconRemoved : kFileIconMiss) : kFolderIconMiss;
					InitItem(data, iImage);
				}
			}
		}

		m_ignlist.clear();
		return kContinueTraversal;
	}

	// initializes and adds new item to list view
	int InitItem(EntnodeData *data, int iImage, const char *tag = 0L)
	{
		CListCtrl& listCtrl = GetListCtrl();
		KoColumnContext context(GetView()->GetPath());
		TCHAR buf[256];
		GetView()->GetColumnModel()->GetAt(0)->GetText(&context, data, buf, 256);

		LV_ITEM lvi;			
		lvi.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM;
		lvi.iItem = listCtrl.GetItemCount();
		lvi.iSubItem = 0;
		lvi.pszText = buf;
		lvi.iImage = iImage;
		lvi.lParam = (LPARAM)(LPVOID)data;
		int pos = listCtrl.InsertItem(&lvi);
		if (pos >= 0)
		{
			// note ownership addition
			data->Ref();

			// set item text for additional columns			
			KiColumnModel* model = GetView()->GetColumnModel();
			int columnCount = model->GetCount();
			for(int j = 1; j < columnCount; j++)
			{
				TCHAR buf[256];
				model->GetAt(j)->GetText(&context, data, buf, 256);
				listCtrl.SetItemText(pos, j, buf);
			}

			if(tag != 0L && tag[0] != '\0' && !IsRecursive())
				listCtrl.SetItemText(pos, EntnodeFile::kTag, tag);
		}
		return pos;
	}


	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;

   		EntnodeData *data = Entries_SetVisited(fullpath, m_entries, name, dir, true, &m_ignlist);
		if (!IsShowIgnored() && data->IsIgnored())
		{
			return kSkipFile;
		}
		
        if (IsRecursive())
        {
            if (!data->IsIgnored() && !data->IsUnknown() && !data->IsMissing()) 
			{
				TViewFill traverse(m_context);
				FileTraverse(fullname, traverse);
			}
			return kSkipFile;			
        }

		// get the tag
		CStr subCVS;
		CStr tagName;
		subCVS = fullname;
		if(!subCVS.endsWith(kPathDelimiter))
			subCVS << kPathDelimiter;
		subCVS << "CVS";
		Tag_Open(tagName, subCVS);

		InitItem(data, CBrowseFileView::GetImageForEntry(data), tagName);
		return kSkipFile;
	}

	virtual kTraversal OnAlias(const char *fullpath,
		const char *fullname,
		const char *name,
		const struct stat & dir, const FSSpec * macspec)
	{
		return OnFile(fullpath, fullname, name, dir, macspec);
	}

	virtual kTraversal OnFile(const char *fullpath,
		const char *fullname,
		const char *name,
		const struct stat & dir, const FSSpec * macspec)
	{
		EntnodeData *data = Entries_SetVisited(fullpath, m_entries, name, dir, false, &m_ignlist);
		if(!IsShowIgnored() && data->IsIgnored())
			return kContinueTraversal;

		if (m_context->IsMatch(data))
		{
			int pos = InitItem(data, CBrowseFileView::GetImageForEntry(data));
			if (pos >=	0)
			{
				UINT state = data->IsLocked() ? INDEXTOSTATEIMAGEMASK(2) : INDEXTOSTATEIMAGEMASK(1);
				CListCtrl& listCtrl = GetListCtrl();
				listCtrl.SetItemState(pos, state, LVIS_STATEIMAGEMASK);
			}
		}
		return kContinueTraversal;
	}
};

/////////////////////////////////////////////////////////////////////////////
// CBrowseFileView construction/destruction

CBrowseFileView::CBrowseFileView() 
: m_checkChanges(false)
, m_watch(NULL)
, m_isResetViewLocked(false)
, m_isFilesChange(false)
, m_isResetViewPending(false)
, m_uiFilechangeTimer(0)
{
	m_sort = (int)gFileViewSort;
	SetRecursive(false);
	m_ascendant = (bool)gFileViewSortAsc;
	m_entriesMod = 0;
	m_entriesLogMod = 0;	
}

CBrowseFileView::~CBrowseFileView()
{
	gFileViewSortAsc = m_ascendant;
	gFileViewSort = m_sort;
}

void CBrowseFileView::InvalidateView(void)
{
	m_entriesMod = 0;
}

BOOL CBrowseFileView::PreCreateWindow(CREATESTRUCT& cs)
{
	cs.style |= WS_TABSTOP | LVS_SHOWSELALWAYS | LVS_REPORT;
	cs.dwExStyle |= LVS_EX_FULLROWSELECT;

	return CListView::PreCreateWindow(cs);
}

int CBrowseFileView::GetImageForEntry(EntnodeData *data)
{
	int result;
	if(data->GetType() == ENT_FILE)
	{
		const char *info = 0L;
		if(data->IsIgnored())
		{
			result = kFileIconIgnored;
		}
		else if(data->IsUnknown())
		{
			result = kFileIconUnknown;
		}
		else if(data->GetConflict() != 0L)
		{
			result = kFileIconConflict;
		}
		else if(data->IsRemoved())
		{
			result = kFileIconRemoved;
		}
		else if((info = (*data)[EntnodeFile::kOption]) != 0L && strcmp(info, "-kb") == 0)
		{
			const char* vn = (*data)[EntnodeFile::kVN];
			bool isAdded = vn && (strcmp(vn, "0") == 0);

			result = data->IsUnmodified() ? kFileIconBinary : 
				isAdded ? kFileIconBinaryAdded : kFileIconBinaryMod;
		}
		else if((info = (*data)[EntnodeFile::kOption]) != 0L && strcmp(info, "-ku") == 0)
		{
			const char* vn = (*data)[EntnodeFile::kVN];
			bool isAdded = vn && (strcmp(vn, "0") == 0);

			result = data->IsUnmodified() ? kFileIconUnicode : 
				isAdded ? kFileIconUnicodeAdded : kFileIconUnicodeMod;
		}
		else
		{
			const char* vn = (*data)[EntnodeFile::kVN];
			bool isAdded = vn && (strcmp(vn, "0") == 0);

			result = data->IsUnmodified() ? kFileIconText : 
				isAdded ? kFileIconAdded : kFileIconTextMod;
		}
	}
	else
	{
		if(data->IsIgnored())
		{
			result = kFolderIconIgnored;
		}
		else if(data->IsUnknown())
		{
			result = kFolderIconUnknown;
		}
		else
		{
			result = kFolderIcon;
		}
	}

	return result;
}

void CBrowseFileView::SetRecursive(bool isRecursive)
{
	m_isRecursive = isRecursive;
	m_modelColumns = (m_isRecursive) ? KiColumnModel::GetRecursiveModel() : KiColumnModel::GetRegularModel();
}

static void onFilteringChanged(CObject *obj)
{
	((CBrowseFileView *)obj)->OnFilteringChanged();
}

void CBrowseFileView::OnInitialUpdate()
{
	CListView::OnInitialUpdate();

	CListCtrl& ListCtrl = GetListCtrl();

	// Replace standard header with sort header
	if (HWND hWnd = ListView_GetHeader(ListCtrl.m_hWnd))
	{
		m_ctrlHeader.SubclassWindow(hWnd);
	}
	ListCtrl.SetExtendedStyle(LVS_EX_HEADERDRAGDROP);

// set image lists
	if(m_SmallImageList.m_hImageList == 0L)
		m_SmallImageList.Create(IDB_SMALLICONS, 16, 1, RGB(255, 255, 255));
	if(m_StateImageList.m_hImageList == 0L)
		m_StateImageList.Create(IDR_STATEICONS, 16, 1, RGB(255, 0, 0));
	
	ListCtrl.SetImageList(&m_SmallImageList, LVSIL_SMALL);
	ListCtrl.SetImageList(&m_StateImageList, LVSIL_STATE);

	KoTraversalContext context(this);
	SetRecursive(context.IsRecursive());	
	AddColumns();
	SetFullRowSel(TRUE);

	LoadColumns();

	// register for notifications on filter change
	if (CWincvsApp* app = (CWincvsApp *)AfxGetApp())
    { 
		app->GetFilterModel()->GetNotificationManager()->CheckIn(this, onFilteringChanged);
		app->GetRecursionModel()->GetNotificationManager()->CheckIn(this, onFilteringChanged);
		app->GetIgnoreModel()->GetNotificationManager()->CheckIn(this, onFilteringChanged);
    }

	m_uiFilechangeTimer = SetTimer(FILECHANGE_TIMER, 200, 0L);
	if( 0L == m_uiFilechangeTimer )
	{
		cvs_err("Can't start the file change notification timer.\n");
	}
}

void CBrowseFileView::AddColumns()
{	
	KiColumnModel* model = GetColumnModel();
	int columnCount = model->GetCount();
	CListCtrl& listCtrl = GetListCtrl();
	for(int i = 0; i<columnCount; i++)
	{
		LV_COLUMN lvc;
		model->GetAt(i)->GetSetupData(&lvc);
		lvc.iSubItem = i;
		lvc.mask |= LVCF_SUBITEM;
		listCtrl.InsertColumn(i,&lvc);
	}
}
	
void CBrowseFileView::DeleteColumns()
{
	KiColumnModel* model = GetColumnModel();
	int columnCount = model->GetCount();	
	CListCtrl& listCtrl = GetListCtrl();
	for(int i = 0; i<columnCount; i++)
	{
		listCtrl.DeleteColumn(0);
	}
}

void CBrowseFileView::GetEntriesModTime(time_t & newEntriesMod,
										time_t & newEntriesLogMod)
{
	newEntriesMod = 0;
	newEntriesLogMod = 0;
	if(chdir(m_path) != 0)
		return;
	if(chdir("CVS") != 0)
		return;

	struct stat sb;
	if (stat("Entries", &sb) != -1)
		newEntriesMod = sb.st_mtime;
	if (stat("Entries.log", &sb) != -1)
		newEntriesLogMod = sb.st_mtime;
	chdir(m_path);
}

class CRecursionLock
{
	bool & m_semaphore;
public:
	CRecursionLock(bool & semaphore) 
	: m_semaphore(semaphore)
	{
		m_semaphore = true;
	}

	~CRecursionLock()
	{
		m_semaphore = false;
	}
};

void CBrowseFileView::ResetView(bool notifyBrowser, const char *selectFile)
{
	// no outstanding request to reset the view any more
	m_isResetViewPending = false;

	if (m_isResetViewLocked)
	{
		// no recursion
		return;
	}
	CRecursionLock recursionLock(m_isResetViewLocked);

	// reset dirty indicators
	m_isFilesChange = false;
	GetEntriesModTime(m_entriesMod, m_entriesLogMod);

	CWaitCursor doWait;

	bool bPartialOK = true;

	CListCtrl& ListCtrl = GetListCtrl();
	int i;

	// get the selection to later try to restore it
	CvsArgs selection(false);
	if (!selectFile)
	{
		int nItem = -1;
		UStr buf;
		while((nItem = ListCtrl.GetNextItem(nItem, LVNI_SELECTED)) != -1)
		{
			EntnodeData *data = (EntnodeData *)ListCtrl.GetItemData(nItem);
			selection.add(data->GetFullPathName(buf));
		}
	}

	// delete all items
	VERIFY( ListCtrl.DeleteAllItems() );

	// make sure columns are ok
	KoTraversalContext context(this);
	if ((context.IsRecursive() && !m_isRecursive) || (!context.IsRecursive() && m_isRecursive))
	{
		
		KiColumnModel* oldModel = GetColumnModel();
		int oldCount = oldModel->GetCount();
		const KiColumn* oldColumn = (0 <= m_sort && m_sort < oldCount) ? oldModel->GetAt(m_sort) : NULL;
		DeleteColumns();
		SetRecursive(context.IsRecursive());
		AddColumns();
		if (oldColumn == NULL)
		{
			m_sort = 0;
		}
		else
		{
			KiColumnModel* newModel = GetColumnModel();
			int newCount = newModel->GetCount();
			int sort = -1;
			for (int pos = 0; pos < newCount && sort == -1; pos++)
			{
				if (newModel->GetAt(pos) == oldColumn)
				{
					sort = pos;
				}
			}
			m_sort = (sort >= 0) ? sort : 0;
		}
	}

	// refetch all items
	TViewFill traverse(&context);
	kTraversal res = FileTraverse(m_path, traverse);

	//JK - find the file for selection
	if( selectFile != 0L )
	{		
		int count = ListCtrl.GetItemCount();
		for (int i = 0; i < count; i++)
		{
			EntnodeData *data = (EntnodeData *)ListCtrl.GetItemData(i);

			//files only
			if(data->GetType() == ENT_SUBDIR)
				continue;

			UStr buf;
			const char* currentFile = data->GetFullPathName(buf);
			if( stricmp( selectFile, currentFile) == 0 )
			{
				selection.reset(false); //we don't want any other selection
				selection.add(currentFile);
				bPartialOK = FALSE;
				break;
			}
		}
	}

	Resort();

	// now restore the selection
	int argc = selection.Argc();
	char * const *argv = selection.Argv();
	bool isFirst = true;
	bool focusSet = false;
	int count = ListCtrl.GetItemCount();
	//If there are no selections, then skip the looping  
	if( argc )
	{
		for (int nItem = 0; nItem < count; nItem++)
		{
			EntnodeData *data = (EntnodeData *)ListCtrl.GetItemData(nItem);
			UStr buf;
			const char* currentFile = data->GetFullPathName(buf);
			for(i = 0; i < argc; i++)
			{
				const char* fn = argv[i];
				if (stricmp(fn, currentFile) == 0)
				{
					VERIFY( ListCtrl.SetItemState(nItem, LVIS_SELECTED, LVIS_SELECTED) );
					if(isFirst)
					{
						isFirst = false;
						if( !bPartialOK )
						{
							//move to the bottom first, so the selection will be on top
							ListCtrl.EnsureVisible(ListCtrl.GetItemCount()-1 , bPartialOK);
						}
						//make sure the first selected file is visible
						VERIFY( ListCtrl.EnsureVisible(nItem, bPartialOK/*TRUE*/) );
						VERIFY( focusSet = (ListCtrl.SetItemState(nItem, LVIS_FOCUSED, LVIS_FOCUSED)!=0) );
					}
					break;
				}
			}
		}
	}
	
	if( !focusSet && ListCtrl.GetItemCount())
	{
		//VERIFY( ListCtrl.SetItemState(0, LVIS_SELECTED, LVIS_SELECTED) );
		VERIFY( focusSet = (ListCtrl.SetItemState(0, LVIS_FOCUSED, LVIS_FOCUSED)!=0) );
	}

	if(notifyBrowser)
	{
		// notify the tree
		CWincvsApp* app = (CWincvsApp *)AfxGetApp();
		if( app )
		{
			app->GetBrowserView()->ResetView(true);
		}
	}

	// start monitoring file changes
	if (!m_watch)
	{
		m_watch = new KoWatcher(m_hWnd);
	}
	m_watch->SignalStartWatch(m_path, m_isRecursive);
}

void CBrowseFileView::ResetView(const char *path, bool notifyBrowser, const char *selectFile)
{
	m_path = path;
	ResetView(false, selectFile);
	CWincvsApp* app = (CWincvsApp *)AfxGetApp();
	app->GetDocument()->SetTitle(m_path);

	if(notifyBrowser)
	{
		// notify the tree
		app->GetBrowserView()->StepToLocation(path);
	}
}

/////////////////////////////////////////////////////////////////////////////
// CBrowseFileView diagnostics

#ifdef _DEBUG
void CBrowseFileView::AssertValid() const
{
	CListView::AssertValid();
}

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

BOOL CBrowseFileView::SetViewType(DWORD dwViewType)
{
	return(ModifyStyle(LVS_TYPEMASK,dwViewType & LVS_TYPEMASK));
}

DWORD CBrowseFileView::GetViewType()
{
	return(GetStyle() & LVS_TYPEMASK);
}

void CBrowseFileView::OnViewSmallIcons() 
{
	if (GetViewType() != LVS_SMALLICON)
		SetViewType(LVS_SMALLICON);
}

void CBrowseFileView::OnViewList() 
{
	if (GetViewType() != LVS_LIST)
		SetViewType(LVS_LIST);
}

void CBrowseFileView::OnViewFullRowDetails() 
{
	if (GetViewType() != LVS_REPORT)
		SetViewType(LVS_REPORT);
}

void CBrowseFileView::OnUpdateViewSmallIcons(CCmdUI* pCmdUI) 
{
	pCmdUI->SetCheck(GetViewType() == LVS_SMALLICON);
}

void CBrowseFileView::OnUpdateViewList(CCmdUI* pCmdUI) 
{
	pCmdUI->SetCheck(GetViewType() == LVS_LIST);
}

void CBrowseFileView::OnUpdateViewFullRowDetails(CCmdUI* pCmdUI) 
{
	pCmdUI->SetCheck(GetViewType() == LVS_REPORT);
}

void CBrowseFileView::OnLButtonDown(UINT nFlags, CPoint point) 
{
	CListView::OnLButtonDown(nFlags, point);
}

void CBrowseFileView::EditSel(EntnodeData *data, bool useDefault)
{
	UStr buf;
	const char* fullpath = data->GetFullPathName(buf);
	if(data->GetType() == ENT_SUBDIR)
	{
		struct stat st;
		if (stat(fullpath, &st) != 0 && errno == ENOENT)
		{
			// Assume we're looking at directory previously in CVS, 
			// but not currently in the filesystem.
			// If we ever run across another case (permissions perhaps)
			// then you might want to add code here.
			cvs_err("'%s' is not accessible\n", (char*)fullpath);
		}
		else
		{
			ResetView(fullpath, true);
			gCvsPrefs.SetLastWorkingDir(fullpath);
		}
	}
	else
	{
		LaunchViewer(fullpath, useDefault);
	}
}

void CBrowseFileView::OnLButtonDblClk(UINT nFlags, CPoint point) 
{
	CWincvsApp* app = (CWincvsApp *)AfxGetApp();
	if( app->IsCvsRunning() )
		return;

	CListCtrl& ListCtrl = GetListCtrl();

	UINT pFlags;
	int nItem = ListCtrl.HitTest(point, &pFlags);
	if(nItem != -1)
	{
		EntnodeData *data = (EntnodeData *)ListCtrl.GetItemData(nItem);
		EditSel(data);
	}

	CListView::OnLButtonDblClk(nFlags, point);
}

bool CBrowseFileView::DisableCommon(bool bCheckSelection /*= true*/)
{
	CWincvsApp* app = (CWincvsApp *)AfxGetApp();
	CListCtrl& ListCtrl = GetListCtrl();
	bool bCommon = app->IsCvsRunning() || gCvsPrefs.empty();
	bool bSelection = ListCtrl.GetSelectedCount() == 0 || this != CWnd::GetFocus();

	return bCheckSelection ? bCommon || bSelection : bCommon;
}

void CBrowseFileView::OnUpdateViewRelease(CCmdUI* pCmdUI)
{
	CWincvsApp* app = (CWincvsApp *)AfxGetApp();
	CListCtrl& ListCtrl = GetListCtrl();

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

	int nItem = -1;
	BOOL res = TRUE;
	int numFiles = 0;
	int numFolders = 0;

	while((nItem = ListCtrl.GetNextItem(nItem, LVNI_SELECTED)) != -1)
	{
		EntnodeData *data = (EntnodeData *)ListCtrl.GetItemData(nItem);
		if(data->IsUnknown() || data->GetType() != ENT_SUBDIR)
		{
			res = FALSE;
			break;
		}
		numFolders++;

		if(numFolders > 1)
		{
			res = FALSE;
			break;
		}
	}

	pCmdUI->Enable(res);
}

void CBrowseFileView::OnUpdateViewAdd(CCmdUI* pCmdUI)
{
	CWincvsApp* app = (CWincvsApp *)AfxGetApp();
	CListCtrl& ListCtrl = GetListCtrl();

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

	int nItem = -1;
	BOOL res = TRUE;
	int numFiles = 0;
	int numFolders = 0;

	while((nItem = ListCtrl.GetNextItem(nItem, LVNI_SELECTED)) != -1)
	{
		EntnodeData *data = (EntnodeData *)ListCtrl.GetItemData(nItem);
		if(!(data->IsUnknown() || (data->IsRemoved() && data->IsMissing())))
		{
			res = FALSE;
			break;
		}
		if(data->GetType() == ENT_FILE)
		{
			numFiles++;
		}
		else
			numFolders++;

		if((numFiles != 0 && numFolders != 0) || numFolders > 1)
		{
			res = FALSE;
			break;
		}
	}

	pCmdUI->Enable(res);
}

void CBrowseFileView::OnUpdateViewAddB(CCmdUI* pCmdUI)
{
	CWincvsApp* app = (CWincvsApp *)AfxGetApp();
	CListCtrl& ListCtrl = GetListCtrl();

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

	int nItem = -1;
	BOOL res = TRUE;

	while((nItem = ListCtrl.GetNextItem(nItem, LVNI_SELECTED)) != -1)
	{
		EntnodeData *data = (EntnodeData *)ListCtrl.GetItemData(nItem);
		if(data->GetType() != ENT_FILE)
		{
			res = FALSE;
			break;
		}
		if(!(data->IsUnknown() || (data->IsRemoved() && data->IsMissing())))
		{
			res = FALSE;
			break;
		}
	}

	pCmdUI->Enable(res);
}

void CBrowseFileView::OnUpdateViewUpdate(CCmdUI* pCmdUI)
{
	CWincvsApp* app = (CWincvsApp *)AfxGetApp();
	CListCtrl& ListCtrl = GetListCtrl();

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

	int nItem = -1;
	BOOL res = TRUE;
	int numFiles = 0;
	int numFolders = 0;

	while((nItem = ListCtrl.GetNextItem(nItem, LVNI_SELECTED)) != -1)
	{
		EntnodeData *data = (EntnodeData *)ListCtrl.GetItemData(nItem);
		if(data->IsUnknown())
		{
			res = FALSE;
			break;
		}
		if(data->GetType() == ENT_FILE)
			numFiles++;
		else
			numFolders++;

		if((numFiles != 0 && numFolders != 0) || numFolders > 1)
		{
			res = FALSE;
			break;
		}
	}

	pCmdUI->Enable(res);
}

void CBrowseFileView::OnUpdateViewGraph(CCmdUI* pCmdUI)
{
	CWincvsApp* app = (CWincvsApp *)AfxGetApp();
	CListCtrl& ListCtrl = GetListCtrl();

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

	int nItem = -1;
	BOOL res = TRUE;

	while((nItem = ListCtrl.GetNextItem(nItem, LVNI_SELECTED)) != -1)
	{
		EntnodeData *data = (EntnodeData *)ListCtrl.GetItemData(nItem);
		if(data->IsUnknown() || data->GetType() == ENT_SUBDIR)
		{
			res = FALSE;
			break;
		}
	}

	pCmdUI->Enable(res);
}

void CBrowseFileView::OnUpdateViewRmv(CCmdUI* pCmdUI)
{
	CWincvsApp* app = (CWincvsApp *)AfxGetApp();
	CListCtrl& ListCtrl = GetListCtrl();

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

	int nItem = -1;
	BOOL res = TRUE;

	while((nItem = ListCtrl.GetNextItem(nItem, LVNI_SELECTED)) != -1)
	{
		EntnodeData *data = (EntnodeData *)ListCtrl.GetItemData(nItem);
		if(data->IsUnknown() || data->IsRemoved() || data->GetType() == ENT_SUBDIR)
		{
			res = FALSE;
			break;
		}
	}

	pCmdUI->Enable(res);
}

void CBrowseFileView::Resort(void)
{
	CListCtrl& ListCtrl = GetListCtrl();
	KoColumnContext context(GetPath());
	KiColumnModel* model = GetColumnModel();
	CSortParam sortParam(m_ascendant, true, model->GetAt(m_sort), model->GetAt(0), &context);
	m_ctrlHeader.SetSortImage(m_sort, m_ascendant);

	ListCtrl.SortItems( _Compare, (LPARAM)&sortParam);
}

void CBrowseFileView::OnColumnclick(NMHDR* pNMHDR, LRESULT* pResult) 
{
	NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
	CListCtrl& ListCtrl = GetListCtrl();

	if(pNMListView->iSubItem == m_sort)
		m_ascendant = !m_ascendant;
	else
	{
		m_ascendant = true;
		m_sort = pNMListView->iSubItem;
		KiColumnModel* model = GetColumnModel();
		m_ascendant = model->GetAt(m_sort)->IsDefaultAscending();
	}

	Resort();
	*pResult = 0;
}

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

	CListCtrl& ListCtrl = GetListCtrl();
	int nItem = -1;
	int pathLen = GetPath().length();

	MultiFiles mf;
	
	// first add the folders
	mf.newdir(GetPath());
	
	//  process selection
	while( (nItem = ListCtrl.GetNextItem(nItem, LVNI_SELECTED)) != -1 )
	{
		EntnodeData* data = (EntnodeData*)ListCtrl.GetItemData(nItem);
		UStr buf;
		const char* fullpath = data->GetFullPathName(buf);
		
		if( data->GetType() == ENT_SUBDIR )
		{						
			handler->OnFolder(fullpath);
			res = true;

			break;
		}
		else
		{
			const char* fn = data->GetFullPathName(buf) + pathLen;
			if( *fn == '\\' )
			{
				fn++;
			}

			mf.newfile(fn, 0, (*data)[EntnodeFile::kVN]);
		}
	}

	if( mf.NumFiles() != 0 )
	{
		handler->OnFiles(&mf);
		res = true;
	}

	return res;
}

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

void CBrowseFileView::OnViewAddb() 
{
	KoAddBinaryHandler handler;
	ProcessSelectionCommand(&handler);
}

void CBrowseFileView::OnViewAddu() 
{
	KoAddUnicodeHandler handler;
	ProcessSelectionCommand(&handler);
}

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

void CBrowseFileView::OnViewRmv() 
{
	KoFileRemoveHandler handler;
	ProcessSelectionCommand(&handler);
}

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

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

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

void CBrowseFileView::OnViewReload() 
{
	ResetView(true);
}

void CBrowseFileView::OnKeydown(NMHDR* pNMHDR, LRESULT* pResult) 
{
	CWincvsApp* app = (CWincvsApp *)AfxGetApp();
	if( app->IsCvsRunning() )
		return;

	CListCtrl& ListCtrl = GetListCtrl();
	LV_KEYDOWN* pLVKeyDow = (LV_KEYDOWN*)pNMHDR;

	if(pLVKeyDow->wVKey == VK_BACK)
	{
		OnViewUpone();
	}
	else if(pLVKeyDow->wVKey == VK_RETURN && ListCtrl.GetSelectedCount() == 1)
	{
		int nItem = -1;
		if((nItem = ListCtrl.GetNextItem(nItem, LVNI_SELECTED)) != -1)
		{
			EntnodeData *data = (EntnodeData *)ListCtrl.GetItemData(nItem);
			if(data->GetType() == ENT_SUBDIR)
			{
				UStr buf;
				const char* fullpath = data->GetFullPathName(buf);
				ResetView(fullpath, true);
			}
			else
			{
				EditSel(data);
			}
		}
	}
	
	*pResult = 0;
}

static CString GetRoot()
{
	CWincvsApp* app = (CWincvsApp *)AfxGetApp();
	return app->GetRoot();
}

static int GetPathLength(LPCTSTR path)
{
	int len = _tcslen(path);
	return (len > 0 && path[len - 1] == kPathDelimiter) ? (len - 1) : len;
}

static bool IsSamePath(LPCTSTR path1, LPCTSTR path2)
{	 
	int len1 = GetPathLength(path1);
	int len2 = GetPathLength(path2);
	return len1 == len2 && _tcsicmp(path1, path2) == 0;
}

void CBrowseFileView::OnViewUpone() 
{
	if(!IsSamePath(m_path, GetRoot()) && chdir(m_path) == 0 && chdir("..") == 0)
	{
		char newpath[1024];
		getcwd(newpath, 1023);
		ResetView(newpath, true);
	}
	else
	{
		MessageBeep(MB_ICONHAND);
	}
}

void CBrowseFileView::OnUpdateViewUpone(CCmdUI* pCmdUI) 
{
	CWincvsApp* app = (CWincvsApp *)AfxGetApp();
	pCmdUI->Enable(!app->IsCvsRunning() && !IsSamePath(m_path, GetRoot()));
}

void CBrowseFileView::OnViewTrash() 
{
	KoMoveToTrashHandler handler;
	ProcessSelectionCommand(&handler);
}

void CBrowseFileView::OnUpdateViewTrash(CCmdUI* pCmdUI) 
{
	bool enabled = !DisableCommon();
	if(enabled)
	{
		KoUpdateMoveToTrashHandler handler;
		ProcessSelectionCommand(&handler);
		enabled = handler.IsEnabled();
	}
	pCmdUI->Enable(enabled);
}

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

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

void CBrowseFileView::OnViewGraph() 
{
	KoGraphHandler handler;
	ProcessSelectionCommand(&handler);
}

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

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

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

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

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

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

void CBrowseFileView::OnViewForceedit() 
{
	KoForceEditHandler handler;
	ProcessSelectionCommand(&handler);
}
void CBrowseFileView::OnViewUnedit()
{
	KoUneditHandler handler;
	ProcessSelectionCommand(&handler);
}

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

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

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

void CBrowseFileView::OnRButtonDown(UINT nFlags, CPoint point) 
{
	CListView::OnRButtonDown(nFlags, point);

	CWincvsApp* app = (CWincvsApp *)AfxGetApp();
	CListCtrl& ListCtrl = GetListCtrl();

	if(DisableCommon())
		return;

	UINT pFlags;
	int testItem = ListCtrl.HitTest(point, &pFlags);
	if(testItem == -1)
		return;

	if(ListCtrl.GetItemState(testItem, LVIS_SELECTED) == 0)
		return;

	if((pFlags & (LVHT_ONITEMICON|LVHT_ONITEMLABEL|LVHT_ONITEMSTATEICON)) == 0)
		return;

	CMenu* pMyMenu=::GetCustomMenu(kCustomFilesMenu,this);
	if (pMyMenu)
	{
		ClientToScreen(&point);
		pMyMenu->TrackPopupMenu(TPM_LEFTALIGN|TPM_LEFTBUTTON|TPM_RIGHTBUTTON, point.x, point.y, this);
		delete pMyMenu;
	}
}

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

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

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

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

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

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

	return 1;
}

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

	// fill the cache with current selection
	if(!PyIsUICacheValid())
	{
		CListCtrl& ListCtrl = GetListCtrl();
		int nItem = -1;
		while((nItem = ListCtrl.GetNextItem(nItem, LVNI_SELECTED)) != -1)
		{
			EntnodeData *data = (EntnodeData *)ListCtrl.GetItemData(nItem);
			PyAppendCache(data);
		}
	}

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

void CBrowseFileView::OnViewExplore() 
{
	const char* fullpath = m_path;

	CListCtrl& ListCtrl = GetListCtrl();
	int nItem = -1;

	// first add the folders
	while((nItem = ListCtrl.GetNextItem(nItem, LVNI_SELECTED)) != -1)
	{
		EntnodeData *data = (EntnodeData *)ListCtrl.GetItemData(nItem);
		if(data->GetType() == ENT_SUBDIR)
		{
			UStr buf;
			fullpath = data->GetFullPathName(buf);
			break;
		}
	}

	HINSTANCE hInst = ShellExecute(*AfxGetMainWnd(), "explore", fullpath,
			0L, 0L, SW_SHOWDEFAULT);
	if((long)hInst < 32)
	{
		cvs_err("Unable to explore '%s' (error %d)\n", (char *)m_path, GetLastError());
	}
}

void CBrowseFileView::OnUpdateViewExplore(CCmdUI* pCmdUI) 
{
	CWincvsApp* app = (CWincvsApp *)AfxGetApp();
	CListCtrl& ListCtrl = GetListCtrl();
	if(DisableCommon())
	{
		pCmdUI->Enable(FALSE);
		return;
	}

	int nItem = -1;
	int numItem = 0;
	BOOL res = TRUE;

	while((nItem = ListCtrl.GetNextItem(nItem, LVNI_SELECTED)) != -1)
	{
		EntnodeData *data = (EntnodeData *)ListCtrl.GetItemData(nItem);
		if(data->IsMissing() || ++numItem > 1)
		{
			res = FALSE;
			break;
		}
	}

	pCmdUI->Enable(res);
}

void CBrowseFileView::OnTimer(UINT nIDEvent) 
{
	if( nIDEvent == m_uiFilechangeTimer )
	{
		CWincvsApp* app = (CWincvsApp*)AfxGetApp();
		if( !app->IsCvsRunning() )
		{
			// cvs command finished
			if( m_checkChanges )
			{
				// Refresh the directory tree and file view
				m_checkChanges = false;
				ResetView(true);
				
				return;
			}

			// file change
			if( m_isFilesChange )
			{				
				ResetView();
			}

			// check if we need to reload
			time_t newEntriesMod;
			time_t newEntriesLogMod;
			GetEntriesModTime(newEntriesMod, newEntriesLogMod);
			
			if( m_entriesMod != newEntriesMod || m_entriesLogMod != newEntriesLogMod )
			{
				ResetView();
			}
		}

		return;
	}

	CListView::OnTimer(nIDEvent);
}

LRESULT CBrowseFileView::OnFileChange(WPARAM, LPARAM)
{
	m_isFilesChange = true;
	return 0;
}

void CBrowseFileView::SetFullRowSel(BOOL bFullRowSel)
{
	CListCtrl& ListCtrl = GetListCtrl();
	DWORD dwExStyle = ListView_GetExtendedListViewStyle(ListCtrl.m_hWnd);
	if (bFullRowSel)
		(dwExStyle |= LVS_EX_FULLROWSELECT);
	else
		(dwExStyle &= ~LVS_EX_FULLROWSELECT);
	ListView_SetExtendedListViewStyle(ListCtrl.m_hWnd, dwExStyle);
}

void CBrowseFileView::OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo) 
{
	CListView::OnBeginPrinting(pDC, pInfo);

	int nPageHeight = pDC->GetDeviceCaps(VERTRES);
	TEXTMETRIC tm;
	pDC->GetTextMetrics(&tm);

	// compute the bounds
	CListCtrl& ListCtrl = GetListCtrl();
	m_numberOfLines = ListCtrl.GetItemCount();
	m_heightOfLines = 2 * tm.tmHeight;
	m_numberByPages = nPageHeight / m_heightOfLines;
	m_numberOfPages = m_numberOfLines / m_numberByPages + 1;

	pInfo->SetMaxPage(m_numberOfPages);
}

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

	return TRUE;
}

void CBrowseFileView::OnPrint(CDC* pDC, CPrintInfo* pInfo) 
{
	if (!pDC->IsPrinting()) 
		return;

	// now compute the first item to draw
	CListCtrl& ListCtrl = GetListCtrl();
	int nFirstItem = -1;
	int numToIgnore = m_numberByPages * (pInfo->m_nCurPage - 1);
	while(numToIgnore-- && (nFirstItem = ListCtrl.GetNextItem(nFirstItem, LVNI_ALL)) != -1)
	{
	}
	
	TEXTMETRIC tm;
	pDC->GetTextMetrics(&tm);

	int nItem = nFirstItem, n = 0;
	int horz = pInfo->m_rectDraw.Width();
	int vert = pInfo->m_rectDraw.Height();
	// measure for each column
	KiColumnModel* model = GetColumnModel();
	int columnCount = model->GetCount();
	std::vector<int> widths(columnCount, 0);
	int totalwidth = 0;
	int j;
	KoColumnContext context(GetPath());
	while(n++ < m_numberByPages && (nItem = ListCtrl.GetNextItem(nItem, LVNI_ALL)) != -1)
	{
		EntnodeData *data = (EntnodeData *)ListCtrl.GetItemData(nItem);
		for(j = 0; j < columnCount; j++)
		{
			TCHAR buffer[256];
			model->GetAt(j)->GetText(&context, data, buffer, 256);
			int width = pDC->GetTextExtent(buffer, _tcslen(buffer)).cx + kSpc;
			widths[j] = max(widths[j], width);
		}
	}

	for(j = 0; j < columnCount; j++)
	{
		totalwidth += widths[j];
	}

	for(j = 0; j < columnCount; j++)
	{
		widths[j] += (horz - totalwidth) / columnCount;
	}

	// compute the margins
	for(j = columnCount - 1; j >= 0; j--)
	{
		int margin = 0;
		for(int i = j - 1; i >= 0; i--)
		{
			margin += widths[i];
		}
		widths[j] = margin;
	}
	// now print
	nItem = nFirstItem;
	n = 0;
	CPoint off = pInfo->m_rectDraw.TopLeft();
	while(n++ < m_numberByPages && (nItem = ListCtrl.GetNextItem(nItem, LVNI_ALL)) != -1)
	{
		EntnodeData *data = (EntnodeData *)ListCtrl.GetItemData(nItem);
		for(j = 0; j < columnCount; j++)
		{
			const char *info = (*data)[j];
			if(info == 0L)
				continue;

			CPoint where = off;
			where.x += widths[j];
			CString s(info);
			CRect r(0,0,0,0);
			pDC->DrawText(s, -1, &r, DT_CALCRECT);
			r.OffsetRect(where);
			pDC->DrawText(s, -1, &r, DT_CENTER);
		}
		off += CPoint(0, m_heightOfLines);
	}
	//CListView::OnPrint(pDC, pInfo);
}

void CBrowseFileView::OnViewEditsel() 
{
	CListCtrl& ListCtrl = GetListCtrl();
	int nItem = -1;

	// first add the folders
	while((nItem = ListCtrl.GetNextItem(nItem, LVNI_SELECTED)) != -1)
	{
		EntnodeData *data = (EntnodeData *)ListCtrl.GetItemData(nItem);
		EditSel(data);
	}
}

void CBrowseFileView::OnViewEditseldef() 
{
	CListCtrl& ListCtrl = GetListCtrl();
	int nItem = -1;

	// first add the folders
	while((nItem = ListCtrl.GetNextItem(nItem, LVNI_SELECTED)) != -1)
	{
		EntnodeData *data = (EntnodeData *)ListCtrl.GetItemData(nItem);
		EditSel(data, true);
	}
}

void CBrowseFileView::OnUpdateViewEditseldef(CCmdUI* pCmdUI)
{
	CWincvsApp* app = (CWincvsApp *)AfxGetApp();
	CListCtrl& ListCtrl = GetListCtrl();
	if(DisableCommon())
	{
		pCmdUI->Enable(FALSE);
		return;
	}
	
	int nItem = -1;
	BOOL res = TRUE;
	
	while((nItem = ListCtrl.GetNextItem(nItem, LVNI_SELECTED)) != -1)
	{
		EntnodeData *data = (EntnodeData *)ListCtrl.GetItemData(nItem);
		if(data->GetType() == ENT_SUBDIR || data->IsMissing())
		{
			res = FALSE;
			break;
		}
	}
	
	CString str;
	if( FormatMenuString(pCmdUI->m_nID, str) && pCmdUI->m_pParentMenu )
	{
		pCmdUI->SetText(str);
	}

	pCmdUI->Enable(res);
}

void CBrowseFileView::OnViewCheckout() 
{
	KoCheckoutHandler handler;

	if( !ProcessSelectionCommand(&handler) )
	{
		handler.OnFolder(m_path);
	}
}

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

void CBrowseFileView::OnViewImport() 
{
	KoImportHandler handler;

	if( !ProcessSelectionCommand(&handler) )
	{
		handler.OnFolder(m_path);
	}
}

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

// Drag and drop.
// Implemented by Nikita Jorniak <nikita@tanner.com>
// Comment: This works to drag files from WinCVS and drop them to any external viewer/editor,
// which supports Windows Shell standard CF_HDROP Clipboard style.
// Directories are intentionally left out.
// Tested: MS DevStudio, Windows Explorer.
// Tested on Windows NT 4.0 (Service Pack 6)
void CBrowseFileView::DoDrag()
{
	CListCtrl& ListCtrl = GetListCtrl();
	int nItem=-1;
	int nSize = 0;
	while((nItem = ListCtrl.GetNextItem(nItem, LVNI_SELECTED)) != -1)
	{
		EntnodeData *data = (EntnodeData *)ListCtrl.GetItemData(nItem);
		if(data->GetType()!=ENT_SUBDIR)
		{
			UStr buf;
			const char* fullpath = data->GetFullPathName(buf);
			nSize += strlen(fullpath) + 1;
		}
	}
	if(nSize>0)
	{
		HGLOBAL hMem = GlobalAlloc(GMEM_FIXED, sizeof(DROPFILES)+nSize+1);
		if(hMem!=NULL)
		{
			DROPFILES& df = *reinterpret_cast<DROPFILES*>(hMem);
			df.pFiles = sizeof(DROPFILES);
			df.fNC = FALSE;
			df.pt.x = df.pt.y = 0;
			df.fWide = FALSE;

			nItem=-1;
			int Offset = sizeof(DROPFILES);
			while((nItem = ListCtrl.GetNextItem(nItem, LVNI_SELECTED)) != -1)
			{
				EntnodeData *data = (EntnodeData *)ListCtrl.GetItemData(nItem);
				if(data->GetType()!=ENT_SUBDIR)
				{
					UStr buf;
					const char* fullpath = data->GetFullPathName(buf);
					int len = strlen(fullpath) + 1;
					memcpy(reinterpret_cast<BYTE*>(hMem)+Offset, fullpath, len);
					Offset+=len;
				}
			}
			reinterpret_cast<BYTE*>(hMem)[Offset]=0;

			COleDataSource ds;
			ds.CacheGlobalData( CF_HDROP, hMem );
			ds.DoDragDrop( DROPEFFECT_COPY );
		}
	}
}

void CBrowseFileView::OnBegindrag(NMHDR* pNMHDR, LRESULT* pResult) 
{
	DoDrag();
	*pResult = 0;
}

void CBrowseFileView::OnBeginrdrag(NMHDR* pNMHDR, LRESULT* pResult) 
{
	DoDrag();
	*pResult = 0;
}

void CBrowseFileView::OnDestroy() 
{
	SaveColumns();
	if(m_watch)
	{
		m_watch->SignalTerminate();
		m_watch = NULL;
	}

	// unregister notifications 
	if (CWincvsApp* app = (CWincvsApp *)AfxGetApp())
    { 
		app->GetFilterModel()->GetNotificationManager()->CheckOut(this);
		app->GetRecursionModel()->GetNotificationManager()->CheckOut(this);
		app->GetIgnoreModel()->GetNotificationManager()->CheckOut(this);
    }

	//kill the timer
	if( m_uiFilechangeTimer )
	{
		KillTimer(m_uiFilechangeTimer);
	}

	CListView::OnDestroy();
}

void CBrowseFileView::SaveColumns()
{
	AfxGetApp()->WriteProfileString("FileDetailView", NULL, NULL);

	char buffer[2048];	
	char* cp = buffer;
	int count = m_ctrlHeader.GetItemCount();
	for (int i = 0; i < count; ++i)
	{
		int width = GetListCtrl().GetColumnWidth(i);
		cp += sprintf(cp, "%d ", width);
	}
	AfxGetApp()->WriteProfileString("FileDetailView", "Column Widths", buffer);

	int order[128];
	if (m_ctrlHeader.GetOrderArray(order, count))
	{
		cp = buffer;
		for (int i = 0; i < count; ++i)
		{
			cp += sprintf(cp, "%d ", order[i]);
		}
		AfxGetApp()->WriteProfileString("FileDetailView", "Column Order", buffer);
	}
}

bool CBrowseFileView::LoadColumns()
{
	CString widths = AfxGetApp()->GetProfileString("FileDetailView", "Column Widths");
	CString orders  = AfxGetApp()->GetProfileString("FileDetailView", "Column Order");
	if (widths.IsEmpty() || orders.IsEmpty())
	{
		return false;
	}

	int count = m_ctrlHeader.GetItemCount();
	int order[128];
	for (int i = 0; i < count; ++i)
	{
		CString strSubString;
		AfxExtractSubString( strSubString, widths, i, _T(' '));
		int width =  _ttoi( strSubString );
		GetListCtrl().SetColumnWidth(i, width);

		AfxExtractSubString( strSubString, orders, i, _T(' '));
		order[i] =  _ttoi( strSubString );
	}
	m_ctrlHeader.SetOrderArray(count, order);
	
	return true;
}

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


void CBrowseFileView::OnFilteringChanged()
{
	if (!m_isResetViewPending)
	{
		m_isResetViewPending = true;
		PostMessage(WM_RESETVIEW);
	}    
}

void CBrowseFileView::OnDeleteitem(NMHDR* pNMHDR, LRESULT* pResult) 
{
	NM_LISTVIEW* p = (NM_LISTVIEW*)pNMHDR;
	if (p->lParam) 
	{
		EntnodeData* data = (EntnodeData*)(LPVOID)p->lParam;
		data->UnRef();
	}
	*pResult = 0;
}

LRESULT CBrowseFileView::OnResetView(WPARAM, LPARAM)
{
	ResetView(false, NULL);
	return 0;
}

void CBrowseFileView::OnUpdateQueryAnnotate(CCmdUI* pCmdUI) 
{
	CWincvsApp* app = (CWincvsApp *)AfxGetApp();
	CListCtrl& ListCtrl = GetListCtrl();

	if(DisableCommon())
	{
		pCmdUI->Enable(FALSE);
		return;
	}
	
	int nItem = -1;
	BOOL res = TRUE;
	
	while((nItem = ListCtrl.GetNextItem(nItem, LVNI_SELECTED)) != -1)
	{
		EntnodeData *data = (EntnodeData *)ListCtrl.GetItemData(nItem);
		if(data->IsUnknown() || data->GetType() == ENT_SUBDIR)
		{
			res = FALSE;
			break;
		}
	}
	
	pCmdUI->Enable(res);
}

void CBrowseFileView::OnQueryAnnotate() 
{
	KoAnnotateHandler handler;
	ProcessSelectionCommand(&handler);
}

void CBrowseFileView::OnEditSelectAll() 
{
	CListCtrl& ListCtrl = GetListCtrl();
	
	for( int nIndex = 0; nIndex < ListCtrl.GetItemCount(); nIndex++ )
	{
		EntnodeData *data = (EntnodeData*)ListCtrl.GetItemData(nIndex);
		if( data->GetType() == ENT_FILE )
		{
			ListCtrl.SetItemState(nIndex, LVIS_SELECTED, LVIS_SELECTED);
		}
	}
	
	if( ListCtrl.GetSelectedCount() )
	{
		ListCtrl.SetFocus();
	}
}

void CBrowseFileView::OnAppCmdline() 
{
	KoCommandLineHandler handler;

	if( !ProcessSelectionCommand(&handler) )
	{
		handler.OnFolder(m_path);
	}
}

void CBrowseFileView::OnUpdateCvsCmd(CCmdUI* pCmdUI) 
{
	CWincvsApp* app = (CWincvsApp*)AfxGetApp();
	pCmdUI->Enable(app->IsCvsRunning() || gCvsPrefs.empty() ? FALSE : TRUE);
}

void CBrowseFileView::OnAppRtagbranch() 
{
	KoRtagBranchHandler handler;

	if( !ProcessSelectionCommand(&handler) )
	{
		handler.OnFolder(m_path);
	}
}

void CBrowseFileView::OnAppRtagcreate() 
{
	KoRtagCreateHandler handler;

	if( !ProcessSelectionCommand(&handler) )
	{
		handler.OnFolder(m_path);
	}
}

void CBrowseFileView::OnAppRtagdelete() 
{
	KoRtagDeleteHandler handler;

	if( !ProcessSelectionCommand(&handler) )
	{
		handler.OnFolder(m_path);
	}
}
