/*
** 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 "wincvs.h"

#include <process.h>
#include <vector>
#include <IO.h>

#include "BrowseFileView.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 "FilterMaskOptDlg.h"
#include "LaunchHandlers.h"

#include "umain.h"

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

/// Default value for file change detection timer
static const int FILECHANGE_TIMER = 999;

/// Extra space for printed text
static const int kSpc = 3;

/// Application message to reset the view
static const int WM_RESETVIEW = WM_APP + 12;

// Persistent values
CPersistentInt gFileViewSort("P_FileViewSort", EntnodeData::kName);
CPersistentBool gFileViewSortAsc("P_FileViewSortAsc", false);
CPersistentInt gFileViewFilterMasksReactionSpeed("P_FileViewFilterMasksReactionSpeed", 60 * 1000);


/*!
	Filtering changed notification function
	\param obj Browse file view to be notified
*/
static void OnFilteringChanged(CObject* obj)
{
	((CBrowseFileView*)obj)->OnFilteringChanged();
}

/*!
	Filter Bar changed notification function
	\param obj Browse file view to be notified
*/
static void OnFilterBarChanged(CObject* obj)
{
	((CBrowseFileView*)obj)->OnFilterBarChanged();
}

//////////////////////////////////////////////////////////////////////////
// CRecursionLock

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

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

//////////////////////////////////////////////////////////////////////////
// KoFileViewTraversalContext

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

/// Get the recursive flag
bool KoFileViewTraversalContext::IsRecursive() const
{
	return m_isRecursive;
}

/// Get the show ignored flag
bool KoFileViewTraversalContext::IsShowIgnored() const
{
	return m_isShowIgnored;
}

/// Get the unknown flag
bool KoFileViewTraversalContext::IsShowUnknown() const
{
	return (m_filter->GetFilters() & kFilterUnknown) != 0;
}

/// Get the hide unknown flag
bool KoFileViewTraversalContext::IsHideUnknown() const
{
	return (m_filter->GetFilters() & kFilterHideUnknown) != 0;
}

/// Get the show missing flag
bool KoFileViewTraversalContext::IsShowMissing() const
{
	return (m_filter->GetFilters() & kFilterMissing) != 0;
}

/// Get the show missing flag
bool KoFileViewTraversalContext::IsShowModified() const
{
	return (m_filter->GetFilters() & kFilterModified) != 0;
}

/// Get the file view
CBrowseFileView* KoFileViewTraversalContext::GetView() const
{
	return m_view;
}

/*!
	Check the node's match
	\param data Node data to be checked
	\return true if node matches, false otherwise
*/
bool KoFileViewTraversalContext::IsMatch(EntnodeData* data) const
{
	return m_filter->IsMatch(data);
}

//////////////////////////////////////////////////////////////////////////
// TViewFill

TViewFill::TViewFill(const KoFileViewTraversalContext* context) 
	: m_context(context), m_entries(1000, ENTNODE::Compare)
{
}

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

kTraversal TViewFill::ExitDirectory(const char* fullpath)
{
	// Add the missing files
	Entries_SetMissing(m_entries);
	
	const 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( !m_context->IsRecursive() || data->GetType() == ENT_FILE )
			{
				const int iImage = data->GetType() == ENT_FILE ?
					(data->IsRemoved() ? kFileIconRemoved : kFileIconMiss) : kFolderIconMiss;
				
				InitItem(data, iImage);
			}
		}
	}
	
	m_ignlist.clear();

	return kContinueTraversal;
}

/*!
	Initialize and add new item to list view
	\param data Node to be added
	\param iImage Image for the node
	\param tag Tag
	\return Index of the new inserted item
*/
int TViewFill::InitItem(EntnodeData* data, int iImage, const char* tag /*= 0L*/)
{
	CListCtrl& listCtrl = m_context->GetView()->GetListCtrl();
	KoColumnContext context(m_context->GetView()->GetPath());
	
	TCHAR buf[256];
	m_context->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;
	
	const int pos = listCtrl.InsertItem(&lvi);
	if( pos >= 0 )
	{
		// Note ownership addition
		data->Ref();
		
		// Set item text for additional columns			
		KiColumnModel* model = m_context->GetView()->GetColumnModel();
		int columnCount = model->GetCount();

		for(int j = 1; j < columnCount; j++)
		{
			model->GetAt(j)->GetText(&context, data, buf, 256);
			listCtrl.SetItemText(pos, j, buf);
		}
		
		if( tag != 0L && tag[0] != '\0' && !m_context->IsRecursive() )
		{
			listCtrl.SetItemText(pos, EntnodeFile::kTag, tag);
		}
	}

	return pos;
}

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

kTraversal TViewFill::OnIdle(const char* fullpath)
{
	// Allow to stop traversing using the escape key
	SHORT escState = GetAsyncKeyState(VK_ESCAPE);
	if( escState & 0x8000 )
	{
		return kStopTraversal;
	}

	return kContinueTraversal;
}

kTraversal TViewFill::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( m_context->IsRecursive() )
	{
		if( (!data->IsIgnored() || m_context->IsShowIgnored()) && !data->IsMissing() )
		{
			TViewFill traverse(m_context);
			FileTraverse(fullname, traverse);
		}

		return kSkipFile;			
	}
	
	if( data->IsIgnored() )
	{
		// Ignore ignored
		if( !m_context->IsShowIgnored() )
		{
			return kSkipFile;			
		}
	}
	else if( data->IsUnknown() )
	{
		// Hide unknown
		if( m_context->IsHideUnknown() )
		{
			return kSkipFile;			
		}
	}

	if( m_context->IsShowModified() )
	{
		// Hide folders unless some of the matching show filters are set
		if( !(data->IsIgnored() && m_context->IsShowIgnored() || data->IsUnknown() && m_context->IsShowUnknown()) )
		{
			return kSkipFile;			
		}
	}

	if( m_context->IsShowMissing() && !data->IsMissing() )
	{
		// Show only missing unless any matching show filters are set
		if( !(data->IsIgnored() && m_context->IsShowIgnored() || data->IsUnknown() && m_context->IsShowUnknown()) )
		{
			return kSkipFile;			
		}
	}

	if( !data->IsUnknown() && m_context->IsShowUnknown() )
	{
		// Don't show CVS files if show unknown is set
		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;
}

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

kTraversal TViewFill::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( !m_context->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 = m_context->GetView()->GetListCtrl();
			listCtrl.SetItemState(pos, state, LVIS_STATEIMAGEMASK);
		}
	}

	return kContinueTraversal;
}


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

CImageList CBrowseFileView::m_smallImageList;
CImageList CBrowseFileView::m_stateImageList;

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_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_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_OPENSEL, OnViewOpensel)
	ON_COMMAND(ID_VIEW_OPENSELAS, OnViewOpenselas)
	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, OnUpdateViewAnnotate)
	ON_COMMAND(ID_QUERY_ANNOTATE, OnAnnotate)
	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_COMMAND(ID_APP_RTAGBRANCH, OnAppRtagbranch)
	ON_COMMAND(ID_APP_RTAGCREATE, OnAppRtagcreate)
	ON_COMMAND(ID_APP_RTAGDELETE, OnAppRtagdelete)
	ON_WM_CONTEXTMENU()
	ON_COMMAND(ID_VIEW_FILTERBAR_ENABLE, OnViewFilterbarEnable)
	ON_COMMAND(ID_VIEW_FILTERBAR_CLEARALL, OnViewFilterbarClearall)
	ON_COMMAND(ID_VIEW_FILTERBAR_OPTIONS, OnViewFilterbarOptions)
	ON_NOTIFY_REFLECT(LVN_ITEMCHANGED, OnItemchanged)
	ON_NOTIFY_REFLECT(LVN_DELETEALLITEMS, OnDeleteallitems)
	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_UPDATE_COMMAND_UI(ID_VIEW_OPENSEL, OnUpdateViewTrash)
	ON_UPDATE_COMMAND_UI(ID_VIEW_OPENSELAS, OnUpdateViewTrash)
	ON_UPDATE_COMMAND_UI(ID_APP_RTAGBRANCH, OnUpdateCvsCmd)
	ON_UPDATE_COMMAND_UI(ID_APP_RTAGCREATE, OnUpdateCvsCmd)
	ON_UPDATE_COMMAND_UI(ID_APP_RTAGDELETE, OnUpdateCvsCmd)
	ON_UPDATE_COMMAND_UI(ID_QUERY_SHELLACTION_DEFAULT, OnUpdateViewTrash)
	ON_COMMAND(ID_QUERY_SHELLACTION_DEFAULT, OnQueryShellactionDefault)
	//}}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()

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

/// PreCreateWindow virtual override, modify the window style
BOOL CBrowseFileView::PreCreateWindow(CREATESTRUCT& cs)
{
	cs.style |= WS_TABSTOP | LVS_SHOWSELALWAYS | LVS_REPORT;
	cs.dwExStyle |= LVS_EX_FULLROWSELECT;

	return CListView::PreCreateWindow(cs);
}

/*!
	Get the ID of the image which reflects the state of the file/directory
	\param data Node data to get the image for
	\return The ID of the image
*/
int CBrowseFileView::GetImageForEntry(EntnodeData* data)
{
	int result = -1;

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

/*!
	Set the recursive flag and model
	\param isRecursive New recursive state
*/
void CBrowseFileView::SetRecursive(bool isRecursive)
{
	m_isRecursive = isRecursive;
	m_modelColumns = m_isRecursive ? KiColumnModel::GetRecursiveModel() : KiColumnModel::GetRegularModel();
}

/// OnInitialUpdate virtual override, initialize list control and start the refresh timer
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);
		
		CMainFrame* pMainFrame = (CMainFrame*)AfxGetMainWnd();
		if( pMainFrame->GetSafeHwnd() )
		{
			m_ctrlHeader.EnableHeaderFilters(pMainFrame->IsFilterMasksEnable());
			m_ctrlHeader.SetFilterChangeTimeout(gFileViewFilterMasksReactionSpeed);
		}

		m_ctrlHeader.GetNotificationManager()->CheckIn(this, ::OnFilterBarChanged);
	}

	SetListCtrlExtendedStyle(&ListCtrl, LVS_EX_FULLROWSELECT | LVS_EX_HEADERDRAGDROP | LVS_EX_LABELTIP);

	// Set image lists
	if( !m_smallImageList.m_hImageList )
		m_smallImageList.Create(IDB_SMALLICONS, 16, 1, RGB(255, 255, 255));

	if( !m_stateImageList.m_hImageList )
		m_stateImageList.Create(IDR_STATEICONS, 16, 1, RGB(255, 0, 0));
	
	ListCtrl.SetImageList(&m_smallImageList, LVSIL_SMALL);
	ListCtrl.SetImageList(&m_stateImageList, LVSIL_STATE);

	KoFileViewTraversalContext context(this);
	SetRecursive(context.IsRecursive());	
	
	AddColumns();

	LoadColumns(context.IsRecursive());

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

/*!
	Add columns to view
*/
void CBrowseFileView::AddColumns()
{	
	KiColumnModel* model = GetColumnModel();
	const int columnCount = model->GetCount();
	CListCtrl& listCtrl = GetListCtrl();
	
	// Add columns
	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);
	}
}

/*!
	Delete all columns
*/	
void CBrowseFileView::DeleteColumns()
{
	// Clear the filters
	if( m_ctrlHeader.GetSafeHwnd() )
	{
		Header_ClearAllFilters(m_ctrlHeader.GetSafeHwnd());

		CMainFrame* pMainFrame = (CMainFrame*)AfxGetMainWnd();
		if( pMainFrame->GetSafeHwnd() )
		{
			pMainFrame->SetFilterBarMask(m_ctrlHeader, GetColumnModel());
		}
	}

	CListCtrl& listCtrl = GetListCtrl();
	const int nCount = listCtrl.GetHeaderCtrl()->GetItemCount();
	
	// Delete columns
	for(int nIndex = 0; nIndex < nCount; nIndex++)
	{
		listCtrl.DeleteColumn(0);
	}
}

/*!
	Get the modification time of the CVS/Entries and CVS/Entries.log files
	\param newEntriesMod Return the modification time of the CVS/Entries file
	\param newEntriesLogMod Return the modification time of the CVS/Entries.log file
*/
void CBrowseFileView::GetEntriesModTime(time_t& newEntriesMod,  time_t& newEntriesLogMod)
{
	char* szOriginalPath = getcwd(NULL, MAX_PATH);

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

	// Restore the original path to prevent locking the folder we've been checking
	if( szOriginalPath )
	{
		chdir(szOriginalPath);
		free(szOriginalPath);
		szOriginalPath = NULL;
	}
}

/*!
	Reload and check for changes 
	\param notifyBrowser true to notify the directories tree
	\param selectFile If specified place the selection on this file
	\note Should not be called from another thread
*/
void CBrowseFileView::ResetView(bool notifyBrowser /*= false*/, const char* selectFile /*= 0L*/)
{
	// No outstanding request to reset the view any more
	m_isResetViewPending = false;

	if( m_isResetViewLocked )
	{
		// No recursion
		return;
	}

	CRecursionLock recursionLock(m_isResetViewLocked);

	CWndRedrawMngr redrawMngr(this);

	// Reset dirty indicators
	m_isFilesChange = false;
	m_entriesMod = 0;
	m_entriesLogMod = 0;

	GetEntriesModTime(m_entriesMod, m_entriesLogMod);

	CWaitCursor doWait;

	bool bPartialOK = true;

	CListCtrl& ListCtrl = GetListCtrl();

	// 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 preserved after the flat/recursive mode switch
	KoFileViewTraversalContext context(this);
	if( (context.IsRecursive() && !m_isRecursive) || (!context.IsRecursive() && m_isRecursive) )
	{
		KiColumnModel* oldModel = GetColumnModel();
		const int oldCount = oldModel->GetCount();
		
		// Preserve the sort column
		const KiColumn* oldColumn = (0 <= m_sort && m_sort < oldCount) ? oldModel->GetAt(m_sort) : NULL;

		// Preserve any filter bar text
		for(int nIndex = 0; nIndex < oldCount; nIndex++)
		{
			std::string filterText = m_ctrlHeader.GetFilterText(nIndex);
			
			if( !filterText.empty() )
			{
				const int filterType = oldModel->GetType(nIndex);
				m_filterBarFilters.insert(std::make_pair(filterType, filterText));
			}
		}

		// Save and delete columns
		SaveColumns(m_isRecursive);
		DeleteColumns();
		
		// Add and load new columns
		SetRecursive(context.IsRecursive());
		
		AddColumns();
		LoadColumns(m_isRecursive);

		// Set the sort mark
		if( oldColumn == NULL )
		{
			m_sort = 0;
		}
		else
		{
			KiColumnModel* newModel = GetColumnModel();
			const int newCount = newModel->GetCount();
			int sort = -1;
		
			for(int pos = 0; pos < newCount && sort == -1; pos++)
			{
				if( newModel->GetAt(pos) == oldColumn )
				{
					sort = pos;
					break;
				}
			}

			m_sort = (sort >= 0) ? sort : 0;
		}

		// Set any preserved filter bar text
		if( !m_filterBarFilters.empty() )
		{
			KiColumnModel* newModel = GetColumnModel();
			const int newCount = newModel->GetCount();

			for(std::map<int, std::string>::const_iterator i = m_filterBarFilters.begin(); i != m_filterBarFilters.end(); ++i)
			{
				const int filterType = (*i).first;

				for(int pos = 0; pos < newCount; pos++)
				{
					if( filterType == newModel->GetType(pos) )
					{
						m_ctrlHeader.SetFilterText(pos, (*i).second.c_str());
						break;
					}
				}
			}

			CMainFrame* pMainFrame = (CMainFrame*)AfxGetMainWnd();
			if( pMainFrame->GetSafeHwnd() )
			{
				pMainFrame->SetFilterBarMask(m_ctrlHeader, GetColumnModel());
			}
		}
	}

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

	// 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(int 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(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);
	
	// FIXME -> temporary fix for PWD problems
	chdir(m_path);
}

/*!
	Reset the view to the given path
	\param path Path to reset the view
	\param notifyBrowser true to notify the directories tree
	\param selectFile If specified place the selection on this file
*/
void CBrowseFileView::ResetView(const char* path, bool notifyBrowser, const char* selectFile)
{
	m_path = path;

	CWnd* pWnd = AfxGetMainWnd();
	if( pWnd )
	{
		CMainFrame* pMainFrame = (CMainFrame*)pWnd;
		
		if( pMainFrame->GetSafeHwnd() )
		{
			KoColumnContext context(m_path);
			
			// Update the column context
			pMainFrame->SetContext(&context);

			// Update the filter bar display
			m_ctrlHeader.EnableHeaderFilters(pMainFrame->IsFilterMasksEnable());
			m_ctrlHeader.SetFilterChangeTimeout(gFileViewFilterMasksReactionSpeed);
		}
	}

	ResetView(false, selectFile);
	
	CWincvsApp* app = (CWincvsApp*)AfxGetApp();
	if( app )
	{
		app->GetDocument()->SetTitle(m_path);
		
		if( notifyBrowser )
		{
			// Notify the dir 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

/*!
	Set the view type
	\param dwViewType New view type
	\return TRUE on success, FALSE otherwise
*/
BOOL CBrowseFileView::SetViewType(DWORD dwViewType)
{
	return ModifyStyle(LVS_TYPEMASK, dwViewType & LVS_TYPEMASK);
}

/*!
	Get the view type
	\return The view type
*/
DWORD CBrowseFileView::GetViewType() const
{
	return GetStyle() & LVS_TYPEMASK;
}

/*!
	Set the small icons view
*/
void CBrowseFileView::OnViewSmallIcons() 
{
	if( GetViewType() != LVS_SMALLICON )
		SetViewType(LVS_SMALLICON);
}

/*!
	Set the list view
*/
void CBrowseFileView::OnViewList() 
{
	if( GetViewType() != LVS_LIST )
		SetViewType(LVS_LIST);
}

/*!
	Set the full row details view
*/
void CBrowseFileView::OnViewFullRowDetails() 
{
	if( GetViewType() != LVS_REPORT )
		SetViewType(LVS_REPORT);
}

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

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

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

/*!
	Enter the folder if it exists
	\param fullpath Full path of the folder to enter
*/
void CBrowseFileView::EnterFolder(const char* fullpath)
{
	struct stat st;
	if( stat(fullpath, &st) != 0 && errno == ENOENT )
	{
		// Assume we're looking at directory previously in CVS, 
		// but not currently in the file system.
		// 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);
	}
}

/// WM_LBUTTONDBLCLK message handler, edit the selection
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);
		if( data->GetType() == ENT_SUBDIR )
		{
			UStr buf;
			EnterFolder(data->GetFullPathName(buf));
		}
		else
		{
			KoFileActionHandler handler;
			ProcessSelectionCommand(&handler);
		}
	}

	CListView::OnLButtonDblClk(nFlags, point);
}

/*!
	Check whether the commands should be disabled
	\param bCheckSelection true if selection should be taken into consideration
	\return true if commands should be disabled, false otherwise
*/
bool CBrowseFileView::DisableCommon(bool bCheckSelection /*= true*/)
{
	CWincvsApp* app = (CWincvsApp*)AfxGetApp();
	CListCtrl& ListCtrl = GetListCtrl();
	bool bCommon = app->IsCvsRunning()
#if INTERNAL_AUTHEN
		|| gCvsPrefs.empty()
#endif /* INTERNAL_AUTHEN */
		;


	bool bSelection = ListCtrl.GetSelectedCount() == 0 || this != CWnd::GetFocus();

	return bCheckSelection ? bCommon || bSelection : bCommon;
}

/// UPDATE_COMMAND_UI handler
void CBrowseFileView::OnUpdateViewRelease(CCmdUI* pCmdUI)
{
	if( DisableCommon() )
	{
		pCmdUI->Enable(FALSE);
		return;
	}

	CListCtrl& ListCtrl = GetListCtrl();
	
	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);
}

/// UPDATE_COMMAND_UI handler
void CBrowseFileView::OnUpdateViewAdd(CCmdUI* pCmdUI)
{
	if( DisableCommon() )
	{
		pCmdUI->Enable(FALSE);
		return;
	}

	CListCtrl& ListCtrl = GetListCtrl();
	
	int nItem = -1;
	BOOL res = TRUE;
	int numFiles = 0;
	int numFolders = 0;

	CStr strPath;

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

			if( strPath.empty() )
			{
				strPath = data->GetFolderPath();
			}
			else
			{
				if( strPath != data->GetFolderPath() )
				{
					res = FALSE;
					break;
				}
			}
		}
		else
		{
			numFolders++;
		}

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

	pCmdUI->Enable(res);
}

/// UPDATE_COMMAND_UI handler
void CBrowseFileView::OnUpdateViewAddB(CCmdUI* pCmdUI)
{
	if( DisableCommon() )
	{
		pCmdUI->Enable(FALSE);
		return;
	}

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

	CStr strPath;

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

		if( !(data->IsUnknown() || (data->IsRemoved() && data->IsMissing())) )
		{
			res = FALSE;
			break;
		}
	}

	pCmdUI->Enable(res);
}

/// UPDATE_COMMAND_UI handler
void CBrowseFileView::OnUpdateViewUpdate(CCmdUI* pCmdUI)
{
	if( DisableCommon() )
	{
		pCmdUI->Enable(FALSE);
		return;
	}

	CListCtrl& ListCtrl = GetListCtrl();
	
	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);
}

/// UPDATE_COMMAND_UI handler
void CBrowseFileView::OnUpdateViewGraph(CCmdUI* pCmdUI)
{
	if( DisableCommon() )
	{
		pCmdUI->Enable(FALSE);
		return;
	}

	CListCtrl& ListCtrl = GetListCtrl();
	
	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);
}

/// UPDATE_COMMAND_UI handler
void CBrowseFileView::OnUpdateViewRmv(CCmdUI* pCmdUI)
{
	if( DisableCommon() )
	{
		pCmdUI->Enable(FALSE);
		return;
	}

	CListCtrl& ListCtrl = GetListCtrl();
	
	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);
}

/*!
	Resort according to the current key
*/
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);
}

/// LVN_COLUMNCLICK notification handler, sort items
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.TotalNumFiles() != 0 )
	{
		handler->OnFiles(&mf);
		res = true;
	}

	return res;
}

/// WM_COMMAND message handler, <b>cvs add</b> selection
void CBrowseFileView::OnViewAdd() 
{
	KoAddHandler handler;
	ProcessSelectionCommand(&handler);
}

/// WM_COMMAND message handler, <b>cvs add -kb</b> selection
void CBrowseFileView::OnViewAddb() 
{
	KoAddBinaryHandler handler;
	ProcessSelectionCommand(&handler);
}

/// WM_COMMAND message handler, <b>cvs add -ku</b> selection
void CBrowseFileView::OnViewAddu() 
{
	KoAddUnicodeHandler handler;
	ProcessSelectionCommand(&handler);
}

/// WM_COMMAND message handler, <b>cvs commit</b> selection
void CBrowseFileView::OnViewCommit() 
{
	KoCommitHandler handler;
	ProcessSelectionCommand(&handler);
}

/// WM_COMMAND message handler, <b>cvs remove</b> selection
void CBrowseFileView::OnViewRmv() 
{
	KoFileRemoveHandler handler;
	ProcessSelectionCommand(&handler);
}

/// WM_COMMAND message handler, <b>cvs update</b> selection
void CBrowseFileView::OnViewUpdate() 
{
	KoUpdateHandler handler;
	ProcessSelectionCommand(&handler);
}

/// WM_COMMAND message handler, <b>cvs -n update</b> selection
void CBrowseFileView::OnViewQueryUpdate() 
{
	KoQueryUpdateHandler handler;
	ProcessSelectionCommand(&handler);
}

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

/// WM_COMMAND message handler, refresh view
void CBrowseFileView::OnViewReload() 
{
	ResetView(true);
}

/// LVN_KEYDOWN notification handler, handle selected keys
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;
				EnterFolder(data->GetFullPathName(buf));
			}
			else
			{
				KoFileActionHandler handler;
				ProcessSelectionCommand(&handler);
			}
		}
	}
	
	*pResult = 0;
}

/*!
	Get the path length
	\param path The path to get the length for
	\return true on success, false otherwise
	\note Takes the ending path delimiter into consideration
*/
int CBrowseFileView::GetPathLength(LPCTSTR path)
{
	int len = _tcslen(path);
	return (len > 0 && path[len - 1] == kPathDelimiter) ? (len - 1) : len;
}

/*!
	Compare two paths
	\param path1 First path
	\param path2 Seconds path
	\return true if paths are same, false otherwise
*/
bool CBrowseFileView::IsSamePath(LPCTSTR path1, LPCTSTR path2)
{	 
	int len1 = GetPathLength(path1);
	int len2 = GetPathLength(path2);
	return len1 == len2 && _tcsicmp(path1, path2) == 0;
}

/// WM_COMMAND message handler, go up one directory level unless we are at the root already
void CBrowseFileView::OnViewUpone() 
{
	CWincvsApp* app = (CWincvsApp*)AfxGetApp();
	if( !IsSamePath(m_path, app->GetRoot()) && chdir(m_path) == 0 && chdir("..") == 0 )
	{
		char newpath[1024];
		getcwd(newpath, 1023);

		ResetView(newpath, true);
	}
	else
	{
		MessageBeep(MB_ICONHAND);
	}
}

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

/// WM_COMMAND message handler, move the selection to trash
void CBrowseFileView::OnViewTrash() 
{
	KoMoveToTrashHandler handler;
	ProcessSelectionCommand(&handler);
}

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

	pCmdUI->Enable(enabled);
}

/// WM_COMMAND message handler, <b>[cvs] diff</b> selection
void CBrowseFileView::OnViewDiff() 
{
	KoDiffHandler handler;
	ProcessSelectionCommand(&handler);
}

/// WM_COMMAND message handler, <b>cvs log</b> selection
void CBrowseFileView::OnViewLog() 
{
	KoLogHandler handler;
	ProcessSelectionCommand(&handler);
}

/// WM_COMMAND message handler, graph selection
void CBrowseFileView::OnViewGraph() 
{
	KoGraphHandler handler;
	ProcessSelectionCommand(&handler);
}

/// WM_COMMAND message handler, <b>cvs status</b> selection
void CBrowseFileView::OnViewStatus() 
{
	KoStatusHandler handler;
	ProcessSelectionCommand(&handler);
}

/// WM_COMMAND message handler, <b>cvs admin -u</b> selection
void CBrowseFileView::OnViewUnlock() 
{
	KoUnlockHandler handler;
	ProcessSelectionCommand(&handler);
}

/// WM_COMMAND message handler, <b>cvs watch add</b> selection
void CBrowseFileView::OnViewWatchOn() 
{
	KoWatchOnHandler handler;
	ProcessSelectionCommand(&handler);
}

/// WM_COMMAND message handler, <b>cvs watch remove</b> selection
void CBrowseFileView::OnViewWatchOff()
{
	KoWatchOffHandler handler;
	ProcessSelectionCommand(&handler);
}

/// WM_COMMAND message handler, <b>cvs edit</b> selection
void CBrowseFileView::OnViewEdit()
{
	KoEditHandler handler;
	ProcessSelectionCommand(&handler);
}

/// WM_COMMAND message handler, <b>cvs edit -c</b> selection
void CBrowseFileView::OnViewReservededit() 
{
	KoReservedEditHandler handler;
	ProcessSelectionCommand(&handler);
}

/// WM_COMMAND message handler, <b>cvs edit -f</b> selection
void CBrowseFileView::OnViewForceedit() 
{
	KoForceEditHandler handler;
	ProcessSelectionCommand(&handler);
}

/// WM_COMMAND message handler, <b>cvs unedit</b> selection
void CBrowseFileView::OnViewUnedit()
{
	KoUneditHandler handler;
	ProcessSelectionCommand(&handler);
}

/// WM_COMMAND message handler, <b>cvs release</b> selection
void CBrowseFileView::OnViewRelease()
{
	KoReleaseHandler handler;
	ProcessSelectionCommand(&handler);
}

/// WM_COMMAND message handler, <b>cvs watchers</b> selection
void CBrowseFileView::OnViewWatchers()
{
	KoWatchersHandler handler;
	ProcessSelectionCommand(&handler);
}

/// WM_COMMAND message handler, <b>cvs editors</b> selection
void CBrowseFileView::OnViewEditors()
{
	KoEditorsHandler handler;
	ProcessSelectionCommand(&handler);
}

/// WM_CONTEXTMENU message handler, display the custom menu
void CBrowseFileView::OnContextMenu(CWnd* pWnd, CPoint point) 
{
	if( DisableCommon() )
	{
		return;
	}

	CListCtrl& listCtrl = GetListCtrl();
	if( listCtrl.GetSelectedCount() > 0 )
	{
		// Try to find a selected and focused item
		int item = listCtrl.GetNextItem(-1, LVNI_SELECTED | LVNI_FOCUSED);
		if( item == -1 )
		{
			// Get the first selected item then
			item = listCtrl.GetNextItem(-1, LVNI_SELECTED);
		}

		if( item > -1 )
		{
			// Set the track point in case it's not the mouse click
			if( -1 == point.x && -1 == point.y )
			{
				// Get the icon's rectangle
				CRect rectItem;
				if( listCtrl.GetItemRect(item, &rectItem, LVIR_ICON) )
				{
					point = rectItem.CenterPoint();
					ClientToScreen(&point);
				}
			}
			
			// Display the pop-up menu
			std::auto_ptr<CMenu> pCustomMenu(::GetCustomMenu(kCustomFilesMenu, this));
			if( pCustomMenu.get() )
			{
				pCustomMenu->TrackPopupMenu(TPM_LEFTALIGN | TPM_LEFTBUTTON | TPM_RIGHTBUTTON, point.x, point.y, this);
			}
		}
	}
}

/// WM_COMMAND message handler, <b>cvs tag</b> selection
void CBrowseFileView::OnViewTagNew() 
{
	KoCreateTagHandler handler;
	ProcessSelectionCommand(&handler);
}

/// WM_COMMAND message handler, <b>cvs tag -d</b> selection
void CBrowseFileView::OnViewTagDelete() 
{
	KoDeleteTagHandler handler;
	ProcessSelectionCommand(&handler);
}

/// WM_COMMAND message handler, <b>cvs tag -b</b> selection
void CBrowseFileView::OnViewTagBranch() 
{
	KoBranchTagHandler handler;
	ProcessSelectionCommand(&handler);
}

/// WM_COMMAND message handler, customize menus
BOOL CBrowseFileView::OnCustomize(UINT nID)
{
	CustomizeMenus(nID == ID_CUST_FILES_MENU ? kCustomFilesMenu : kCustomBrowserMenu);
	return TRUE;
}

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

/// WM_COMMAND message handler, call the macro on selection
BOOL CBrowseFileView::OnMacro(UINT nID)
{
	PyDoPython(WINCMD_to_UCMD(nID));

	return 1;
}

/// UPDATE_COMMAND_UI handler
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);
}

/// WM_COMMAND message handler, explore the selection
void CBrowseFileView::OnViewExplore() 
{
	CListCtrl& ListCtrl = GetListCtrl();
	bool pathExplore = false;

	if( ListCtrl.GetSelectedCount() )
	{
		int nItem = -1;

		while( (nItem = ListCtrl.GetNextItem(nItem, LVNI_SELECTED)) != -1 )
		{
			EntnodeData* data = (EntnodeData*)ListCtrl.GetItemData(nItem);
			if( data->IsMissing() )
			{
				continue;
			}

			if( data->GetType() == ENT_SUBDIR )
			{
				UStr path;
				data->GetFullPathName(path);
				
				if( path.endsWith(kPathDelimiter) )
				{
					path[path.length() - 1] = '\0';
				}
				
				LaunchExplore(path);
			}
			else if( !pathExplore )
			{
				pathExplore = true;
			}
		}

		if( pathExplore )
		{
			LaunchExplore(m_path);
		}
	}
	else
	{
		LaunchExplore(m_path);
	}
}

/// UPDATE_COMMAND_UI handler
void CBrowseFileView::OnUpdateViewExplore(CCmdUI* pCmdUI) 
{
	if( DisableCommon() )
	{
		pCmdUI->Enable(FALSE);
		return;
	}

	bool enable = false;

	CListCtrl& listCtrl = GetListCtrl();
	int nItem = -1;
	
	if( listCtrl.GetSelectedCount() )
	{
		while( (nItem = listCtrl.GetNextItem(nItem, LVNI_SELECTED)) != -1 )
		{
			EntnodeData* data = (EntnodeData*)listCtrl.GetItemData(nItem);
			if( !data->IsMissing() )
			{
				enable = true;
				break;
			}
		}
	}
	else
	{
		CWinApp* app = AfxGetApp();
		CWinCvsBrowser* browser = ((CWincvsApp*)app)->GetBrowserView();
		
		enable = browser->GetTreeCtrl().GetRootItem() != NULL;
	}

	pCmdUI->Enable(enable);
}

/// WM_TIMER message handler, check for the file changes
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 = m_entriesMod;
			time_t newEntriesLogMod = m_entriesLogMod;

			GetEntriesModTime(newEntriesMod, newEntriesLogMod);
			
			if( m_entriesMod != newEntriesMod || m_entriesLogMod != newEntriesLogMod )
			{
				ResetView();
			}
		}

		return;
	}

	CListView::OnTimer(nIDEvent);
}

/// KoWatcher::WM_FILECHANGE message handler, mark the files change
LRESULT CBrowseFileView::OnFileChange(WPARAM, LPARAM)
{
	m_isFilesChange = true;

	return 0;
}

/// OnBeginPrinting virtual override
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);
}

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

	return TRUE;
}

/// OnPrint virtual override
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);
}

/// WM_COMMAND message handler, edit selection
void CBrowseFileView::OnViewEditsel() 
{
	KoLaunchEditHandler handler;
	ProcessSelectionCommand(&handler);
}

/// WM_COMMAND message handler, edit selection
void CBrowseFileView::OnViewEditseldef() 
{
	KoLaunchDefaultEditHandler handler;
	ProcessSelectionCommand(&handler);
}

/// WM_COMMAND message handler, open selection
void CBrowseFileView::OnViewOpensel() 
{
	KoLaunchOpenHandler handler;
	ProcessSelectionCommand(&handler);
}

/// WM_COMMAND message handler, open selection using Open With... dialog
void CBrowseFileView::OnViewOpenselas() 
{
	KoLaunchOpenAsHandler handler;
	ProcessSelectionCommand(&handler);
}

/// WM_COMMAND message handler, launch default associated shell action
void CBrowseFileView::OnQueryShellactionDefault() 
{
	KoLaunchDefaultHandler handler;
	ProcessSelectionCommand(&handler);
}

/// UPDATE_COMMAND_UI handler
void CBrowseFileView::OnUpdateViewEditseldef(CCmdUI* pCmdUI)
{
	if( DisableCommon() )
	{
		pCmdUI->Enable(FALSE);
		return;
	}
	
	CListCtrl& ListCtrl = GetListCtrl();
	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;
		}
	}
	
	pCmdUI->Enable(res);
}

/// WM_COMMAND message handler, <b>cvs co</b>
void CBrowseFileView::OnViewCheckout() 
{
	KoCheckoutHandler handler;

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

/// UPDATE_COMMAND_UI handler
void CBrowseFileView::OnUpdateViewCheckout(CCmdUI* pCmdUI) 
{
	if( DisableCommon(false) )
	{
		pCmdUI->Enable(FALSE);
		return;
	}

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

/// WM_COMMAND message handler, <b>cvs import</b> selection
void CBrowseFileView::OnViewImport() 
{
	KoImportHandler handler;

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

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

/*!
	Drag and drop
	\note Implemented by Nikita Jorniak <nikita@tanner.com>
	Directories are intentionally left out
	This works to drag files from WinCVS and drop them to any external viewer/editor which supports Windows Shell standard CF_HDROP Clipboard style
*/
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 );
		}
	}
}

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

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

/// WM_DESTROY message handler, save settings, unregister change notifications and kill timers
void CBrowseFileView::OnDestroy() 
{
	// Kill the timer
	if( m_uiFilechangeTimer )
	{
		KillTimer(m_uiFilechangeTimer);
	}

	// Stop file watch
	if( m_watch )
	{
		m_watch->SignalTerminate();
		m_watch = NULL;
	}
	
	// Save settings
	SaveColumns(m_isRecursive);

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

	m_ctrlHeader.GetNotificationManager()->CheckOut(this);

	CListView::OnDestroy();
}

/*!
	Format the entry prefix
	\param isRecursive Recursive state
	\return Formatted entry prefix
*/
CString CBrowseFileView::FormatEntrryPrefix(bool isRecursive) const
{
	return isRecursive ? STR_FILEDETAILVIEW_COLUMN_PREFIX_FLAT : STR_FILEDETAILVIEW_COLUMN_PREFIX_REG;
}

/*!
	Save column state
	\param isRecursive Recursive state
*/
void CBrowseFileView::SaveColumns(bool isRecursive)
{
	const int columnCount = m_ctrlHeader.GetItemCount();

	CString columnsWidth;
	CString columnsOrder;

	CString strNum;

	for(int nIndex = 0; nIndex < columnCount; nIndex++)
	{
		HDITEM headerItem = { 0 };
		headerItem.mask = HDI_WIDTH;

		m_ctrlHeader.GetItem(nIndex, &headerItem);

		strNum.Format("%d", headerItem.cxy);
		columnsWidth += strNum + CHR_FILEDETAILVIEW_COLUMN_DATASEP;
	}

	int* orderArray = new int[columnCount];

	if( orderArray && m_ctrlHeader.GetOrderArray(orderArray, columnCount) )
	{
		for(int nIndex = 0; nIndex < columnCount; nIndex++)
		{
			strNum.Format("%d", orderArray[nIndex]);
			columnsOrder += strNum + CHR_FILEDETAILVIEW_COLUMN_DATASEP;
		}
		
		delete [] orderArray;
	}

	AfxGetApp()->WriteProfileString(STR_FILEDETAILVIEW_COLUMN_SECTION, 
		FormatEntrryPrefix(isRecursive) + STR_FILEDETAILVIEW_COLUMN_ENTRY_WIDTH, 
		columnsWidth);

	AfxGetApp()->WriteProfileString(STR_FILEDETAILVIEW_COLUMN_SECTION, 
		FormatEntrryPrefix(isRecursive) + STR_FILEDETAILVIEW_COLUMN_ENTRY_ORDER, 
		columnsOrder);
}

/*!
	Load column state
	\param isRecursive Recursive state
	\return true on success, false otherwise
*/
bool CBrowseFileView::LoadColumns(bool isRecursive)
{
	CString columnsWidth = AfxGetApp()->GetProfileString(STR_FILEDETAILVIEW_COLUMN_SECTION, 
		FormatEntrryPrefix(isRecursive) + STR_FILEDETAILVIEW_COLUMN_ENTRY_WIDTH);

	CString columnsOrder = AfxGetApp()->GetProfileString(STR_FILEDETAILVIEW_COLUMN_SECTION, 
		FormatEntrryPrefix(isRecursive) + STR_FILEDETAILVIEW_COLUMN_ENTRY_ORDER);

	if( columnsWidth.IsEmpty() || columnsOrder.IsEmpty() )
	{
		return false;
	}

	const int columnCount = m_ctrlHeader.GetItemCount();

	int* orderArray = new int[columnCount];
	if( orderArray )
	{
		CString strSubString;

		for(int nIndex = 0; nIndex < columnCount; nIndex++)
		{
			AfxExtractSubString(strSubString, columnsWidth, nIndex, CHR_FILEDETAILVIEW_COLUMN_DATASEP);
			GetListCtrl().SetColumnWidth(nIndex, atoi(strSubString));
			
			AfxExtractSubString(strSubString, columnsOrder, nIndex, CHR_FILEDETAILVIEW_COLUMN_DATASEP);
			orderArray[nIndex] = atoi(strSubString);
		}
		
		m_ctrlHeader.SetOrderArray(columnCount, orderArray);
		
		delete [] orderArray;
	}
	
	return true;
}

/// WM_SETCURSOR message handler, set the hourglass cursor when cvs command is running
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);
}

/*!
	Notify the view that app's filtering has changed
*/
void CBrowseFileView::OnFilteringChanged()
{
	if( !m_isResetViewPending )
	{
		m_isResetViewPending = true;
		PostMessage(WM_RESETVIEW);
	}    
}

/*!
	Notify the view that app's filtering has changed
*/
void CBrowseFileView::OnFilterBarChanged()
{
	if( !m_isResetViewPending )
	{
		m_isResetViewPending = true;
		
		CMainFrame* pMainFrame = (CMainFrame*)AfxGetMainWnd();
		if( pMainFrame->GetSafeHwnd() )
		{
			pMainFrame->SetFilterBarMask(m_ctrlHeader, GetColumnModel());
		}

		PostMessage(WM_RESETVIEW);
	}    
}

/// LVN_DELETEITEM notification handler
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;
}

/// WM_RESETVIEW message handler, reset view
LRESULT CBrowseFileView::OnResetView(WPARAM, LPARAM)
{
	ResetView(false, NULL);
	return 0;
}

/// UPDATE_COMMAND_UI handler
void CBrowseFileView::OnUpdateViewAnnotate(CCmdUI* pCmdUI) 
{
	if( DisableCommon() )
	{
		pCmdUI->Enable(FALSE);
		return;
	}
	
	CListCtrl& ListCtrl = GetListCtrl();
	
	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);
}

/// WM_COMMAND message handler, <b>cvs annotate</b> selection
void CBrowseFileView::OnAnnotate() 
{
	KoAnnotateHandler handler;
	ProcessSelectionCommand(&handler);
}

/// WM_COMMAND message handler, select all file items
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();
	}
}

/// WM_COMMAND message handler, display command line dialog
void CBrowseFileView::OnAppCmdline() 
{
	KoCommandLineHandler handler;

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

/// UPDATE_COMMAND_UI handler
void CBrowseFileView::OnUpdateCvsCmd(CCmdUI* pCmdUI) 
{
	CWincvsApp* app = (CWincvsApp*)AfxGetApp();
	pCmdUI->Enable(app->IsCvsRunning() 
#if INTERNAL_AUTHEN
		|| gCvsPrefs.empty() 
#endif /* INTERNAL_AUTHEN */
		? FALSE : TRUE);
}

/// WM_COMMAND message handler, <b>cvs rtag -b</b> selection
void CBrowseFileView::OnAppRtagbranch() 
{
	KoRtagBranchHandler handler;

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

/// WM_COMMAND message handler, <b>cvs rtag</b> selection
void CBrowseFileView::OnAppRtagcreate() 
{
	KoRtagCreateHandler handler;

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

/// WM_COMMAND message handler, <b>cvs rtag -d</b> selection
void CBrowseFileView::OnAppRtagdelete() 
{
	KoRtagDeleteHandler handler;

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

/// WM_COMMAND message handler, enable/disable filter bar
void CBrowseFileView::OnViewFilterbarEnable() 
{
	CMainFrame* pMainFrame = (CMainFrame*)AfxGetMainWnd();
	if( pMainFrame->GetSafeHwnd() )
	{
		pMainFrame->ToggleFilterMasksEnable();
		m_ctrlHeader.EnableHeaderFilters(pMainFrame->IsFilterMasksEnable());
		m_ctrlHeader.SetFilterChangeTimeout(gFileViewFilterMasksReactionSpeed);
	}
	
	PostMessage(WM_RESETVIEW);
}

/// WM_COMMAND message handler, clear all filter bar filters
void CBrowseFileView::OnViewFilterbarClearall() 
{
	if( m_ctrlHeader.GetSafeHwnd() )
	{
		Header_ClearAllFilters(m_ctrlHeader.GetSafeHwnd());
	}
}

/// WM_COMMAND message handler, clear all filter bar filters
void CBrowseFileView::OnViewFilterbarOptions() 
{
	int reactionSpeed = gFileViewFilterMasksReactionSpeed;

	if( CompatGetFilterMaskOptions(reactionSpeed) )
	{
		gFileViewFilterMasksReactionSpeed = reactionSpeed;
		m_ctrlHeader.SetFilterChangeTimeout(gFileViewFilterMasksReactionSpeed);
	}
}

/// LVN_ITEMCHANGED notification handler, report selected items
void CBrowseFileView::OnItemchanged(NMHDR* pNMHDR, LRESULT* pResult) 
{
	NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;

	if( pNMListView->uChanged & LVIF_STATE && 
		(pNMListView->uNewState & LVIS_SELECTED || pNMListView->uOldState & LVIS_SELECTED) )
	{
		CStr str;
		
		const UINT cnt = GetListCtrl().GetSelectedCount();
		if( cnt > 0 )
		{
			str << (int)cnt << (cnt == 1 ? " object " : " objects") << " selected";
		}

		CMainFrame* pMainFrame = (CMainFrame*)AfxGetMainWnd();
		if( pMainFrame )
		{
			pMainFrame->GetStatusBar()->SetWindowText(str);
		}
	}
	
	*pResult = 0;
}

/// LVN_DELETEALLITEMS notification handler, reset status bar text
void CBrowseFileView::OnDeleteallitems(NMHDR* pNMHDR, LRESULT* pResult) 
{
	NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
	
	CMainFrame* pMainFrame = (CMainFrame*)AfxGetMainWnd();
	if( pMainFrame )
	{
		CString strIdleString;
		strIdleString.LoadString(AFX_IDS_IDLEMESSAGE);

		pMainFrame->GetStatusBar()->SetWindowText(strIdleString);
	}

	*pResult = 0;
}
