/*
** 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> --- February 2000
 */

/*
 * 
 */

#include "stdafx.h"

#if qGTK
#	include <gtk/gtk.h>
#endif

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#ifdef HAVE_STRINGS_H
#include <strings.h>
#endif

#include "UCvsFiles.h"
#include "UCvsDialogs.h"
#include "UCvsFolders.h"
#include "UCvsCommands.h"
#include "UCvsFrame.h"
#include "UCvsApp.h"
#include "CvsPrefs.h"
#include "FileTraversal.h"
#include "MultiFiles.h"
#include "CvsCommands.h"
#include "CvsArgs.h"
#include "TclGlue.h"
#include "AppConsole.h"
#include "MoveToTrash.h"
#include "MacrosSetup.h"

#ifndef NAMESPACE
#       if defined(_MSC_VER) || defined(__MWERKS__) || (__GNUC__ > 2)
#               define NAMESPACE(w) w::
#       else
#               define NAMESPACE(w) ::
#       endif
#endif

UIMPLEMENT_DYNAMIC(UCvsFiles, UWidget)

UBEGIN_MESSAGE_MAP(UCvsFiles, UWidget)
	ON_UUPDATECMD(cmdUPDATE, UCvsFiles::OnCmdUIUpdate)
	ON_UUPDATECMD(cmdCOMMIT, UCvsFiles::OnCmdUIUpdate)
	ON_UUPDATECMD(cmdDIFF, UCvsFiles::OnCmdUIUpdate)
	ON_UUPDATECMD(cmdEDIT, UCvsFiles::OnCmdUIUpdate)
	ON_UUPDATECMD(cmdEDITORS, UCvsFiles::OnCmdUIUpdate)
	ON_UUPDATECMD(cmdLOCKF, UCvsFiles::OnCmdUIUpdate)
	ON_UUPDATECMD(cmdLOG, UCvsFiles::OnCmdUIUpdate)
	ON_UUPDATECMD(cmdTAGNEW, UCvsFiles::OnCmdUIUpdate)
	ON_UUPDATECMD(cmdTAGDELETE, UCvsFiles::OnCmdUIUpdate)
	ON_UUPDATECMD(cmdTAGBRANCH, UCvsFiles::OnCmdUIUpdate)
	ON_UUPDATECMD(cmdSTATUS, UCvsFiles::OnCmdUIUpdate)
	ON_UUPDATECMD(cmdUNEDIT, UCvsFiles::OnCmdUIUpdate)
	ON_UUPDATECMD(cmdQUERYUPDATE, UCvsFiles::OnCmdUIUpdate)
	ON_UUPDATECMD(cmdUNLOCKF, UCvsFiles::OnCmdUIUpdate)
	ON_UUPDATECMD(cmdWATCHERS, UCvsFiles::OnCmdUIUpdate)
	ON_UUPDATECMD(cmdWATCHON, UCvsFiles::OnCmdUIUpdate)
	ON_UUPDATECMD(cmdWATCHOFF, UCvsFiles::OnCmdUIUpdate)
	ON_UUPDATECMD(cmdADD, UCvsFiles::OnCmdUIAdd)
	ON_UUPDATECMD(cmdADDB, UCvsFiles::OnCmdUIAddB)
	ON_UUPDATECMD(cmdRELEASE, UCvsFiles::OnCmdUIRelease)
	ON_UUPDATECMD(cmdRMV, UCvsFiles::OnCmdUIRmv)
	ON_UUPDATECMD(cmdGRAPH, UCvsFiles::OnCmdUIGraph)
	ON_UUPDATECMD(cmdEDITSELDEF, UCvsFiles::OnCmdUIEditseldef)
	ON_UUPDATECMD(cmdRELOAD, UCvsFiles::OnCmdUIReload)
	ON_UUPDATECMD(cmdUPFOLDER, UCvsFiles::OnCmdUIUpone)
	ON_UUPDATECMD(cmdTRASH, UCvsFiles::OnCmdUITrash)
	ON_UUPDATECMD(cmdEXPLORE, UCvsFiles::OnCmdUIExplore)
	ON_UUPDATECMD(cmdIGNORE, UCvsFiles::OnCmdUIIgnore)
	ON_UUPDATECMD(cmdEDITSEL, UCvsFiles::OnCmdUITrash)
	ON_UUPDATECMD_RANGE(cmdSELMACRO, cmdSELMACROEND, UCvsFiles::OnCmdUIMacroSel)
	ON_UCOMMAND(cmdUPDATE, UCvsFiles::OnCmdUpdate)
	ON_UCOMMAND(cmdADD, UCvsFiles::OnCmdAdd)
	ON_UCOMMAND(cmdADDB, UCvsFiles::OnCmdAddb)
	ON_UCOMMAND(cmdCOMMIT, UCvsFiles::OnCmdCommit)
	ON_UCOMMAND(cmdRMV, UCvsFiles::OnCmdRmv)
	ON_UCOMMAND(cmdQUERYUPDATE, UCvsFiles::OnCmdQueryUpdate)
	ON_UCOMMAND(cmdRELOAD, UCvsFiles::OnCmdReload)
	ON_UCOMMAND(cmdUPFOLDER, UCvsFiles::OnCmdUpone)
	ON_UCOMMAND(cmdTRASH, UCvsFiles::OnCmdTrash)
	ON_UCOMMAND(cmdDIFF, UCvsFiles::OnCmdDiff)
	ON_UCOMMAND(cmdLOG, UCvsFiles::OnCmdLog)
	ON_UCOMMAND(cmdGRAPH, UCvsFiles::OnCmdGraph)
	ON_UCOMMAND(cmdSTATUS, UCvsFiles::OnCmdStatus)
	ON_UCOMMAND(cmdLOCKF, UCvsFiles::OnCmdLock)
	ON_UCOMMAND(cmdUNLOCKF, UCvsFiles::OnCmdUnlock)
	ON_UCOMMAND(cmdWATCHON, UCvsFiles::OnCmdWatchOn)
	ON_UCOMMAND(cmdWATCHOFF, UCvsFiles::OnCmdWatchOff)
	ON_UCOMMAND(cmdEDIT, UCvsFiles::OnCmdEdit)
	ON_UCOMMAND(cmdUNEDIT, UCvsFiles::OnCmdUnedit)
	ON_UCOMMAND(cmdWATCHERS, UCvsFiles::OnCmdWatchers)
	ON_UCOMMAND(cmdEDITORS, UCvsFiles::OnCmdEditors)
	ON_UCOMMAND(cmdRELEASE, UCvsFiles::OnCmdRelease)
	ON_UCOMMAND(cmdTAGNEW, UCvsFiles::OnCmdTagNew)
	ON_UCOMMAND(cmdTAGDELETE, UCvsFiles::OnCmdTagDelete)
	ON_UCOMMAND(cmdTAGBRANCH, UCvsFiles::OnCmdTagBranch)
	ON_UCOMMAND(cmdEXPLORE, UCvsFiles::OnCmdExplore)
	ON_UCOMMAND(cmdIGNORE, UCvsFiles::OnCmdIgnore)
	ON_UCOMMAND(cmdEDITSEL, UCvsFiles::OnCmdEditsel)
	ON_UCOMMAND(cmdEDITSELDEF, UCvsFiles::OnCmdEditseldef)
	ON_UCOMMAND_RANGE(cmdSELMACRO, cmdSELMACROEND, UCvsFiles::OnMacroSel)
	ON_LIST_DBLCLICK(kUMainWidget, UCvsFiles::OnDblClick)
	ON_LIST_SELCOLUMN(kUMainWidget, UCvsFiles::OnSelColumn)
	ON_UDESTROY(UCvsFiles)
	ON_UCREATE(UCvsFiles)
UEND_MESSAGE_MAP()

#if 0
	ON_UUPDATECMD(cmdSMALLICONS, OnCmdUISmallIcons)
	ON_UUPDATECMD(cmdFULLLIST, OnCmdUIList)
	ON_UUPDATECMD(cmdROWDETAILS, OnCmdUIFullRowDetails)
	ON_COMMAND(ID_VIEW_SMALLICONS, OnViewSmallIcons)
	ON_COMMAND(ID_VIEW_FULLLIST, OnViewList)
	ON_COMMAND(ID_VIEW_ROWDETAILS, OnViewFullRowDetails)
	ON_WM_LBUTTONDOWN()
	ON_WM_RBUTTONDOWN()
	ON_NOTIFY_REFLECT(LVN_COLUMNCLICK, OnColumnclick)
	ON_NOTIFY_REFLECT(LVN_KEYDOWN, OnKeydown)
	ON_WM_TIMER()

	//}}AFX_MSG_MAP
	ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)
	ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)
#endif

static void *sFileIconTextIcon;
static void *sFileIconBinaryIcon;
static void *sFileIconUnknownIcon;
static void *sFileIconAddedIcon;
static void *sFileIconConflictIcon;
static void *sFileIconMissIcon;
static void *sFolderIconIcon;
static void *sFolderIconUnknownIcon;
static void *sFolderIconMissIcon;
static void *sFileIconTextModIcon;
static void *sFileIconBinaryModIcon;
static void *sFileIconIgnoredIcon;
static void *sFolderIconIgnoredIcon;
static void *sFileIconRemovedIcon;

/*
kFileIconText,cvsfile
kFileIconBinary,binfile
kFileIconUnknown,unkfile
kFileIconAdded,addfile
kFileIconConflict,conflict
kFileIconMiss,missfile
kFolderIcon,folder
kFolderIconUnknown,foldunk
kFolderIconMiss,foldmiss
kFileIconTextMod,modfile
kFileIconBinaryMod,modbin
kFileIconIgnored,ignfile
kFolderIconIgnored,foldign
kFileIconRemoved,delfile
*/

enum
{
	kFileIconText = 0,
	kFileIconBinary,
	kFileIconUnknown,
	kFileIconAdded,
	kFileIconConflict,
	kFileIconMiss,
	kFolderIcon,
	kFolderIconUnknown,
	kFolderIconMiss,
	kFileIconTextMod,
	kFileIconBinaryMod,
	kFileIconIgnored,
	kFolderIconIgnored,
	kFileIconRemoved
};

#define NUM_COLUMNS	7

static char *_gszColumnLabel[NUM_COLUMNS] =
{
	"Name", "Rev.", "Option", "Status",	"Tag", "Date", "Conflict"
};

static int _gnColumnWidth[NUM_COLUMNS] = 
{
	150, 50, 50, 100, 150, 150, 150
};

CPersistentBool gFileViewIgnore("P_FileViewIgnore", true);
CPersistentInt gFileViewSort("P_FileViewSort", EntnodeData::kName);
CPersistentBool gFileViewSortAsc("P_FileViewSortAsc", true);

class TViewFill : public TraversalReport
{
public:
	UCvsFiles *m_listCtrl;
	CSortList<ENTNODE> & m_entries;
	NAMESPACE(std) vector<CStr> m_ignlist;

	TViewFill(UCvsFiles *listCtrl, CSortList<ENTNODE> & entries) :
		m_listCtrl(listCtrl), m_entries(entries) {}

	virtual ~TViewFill() {}

	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)
	{
		m_ignlist.erase(m_ignlist.begin(), m_ignlist.end());
		return kContinueTraversal;
	}

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

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

	virtual kTraversal OnDirectory(const char *fullpath,
		const char *fullname,
		const char *name,
		const struct stat & dir, const FSSpec * macspec)
	{
#if qUnix
		if(strcmp(name, "CVS") == 0)
#else
		if(stricmp(name, "CVS") == 0)
#endif
			return kSkipFile;

		EntnodeData *data = Entries_SetVisited(fullpath, m_entries, name, dir, true, &m_ignlist);
		if(!(bool)gFileViewIgnore && data->IsIgnored())
			return kSkipFile;

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

		int rownum;
		UEventSendMessage(m_listCtrl->GetWidID(), EV_LIST_NEWROW, kUMainWidget, &rownum);
		UEventSendMessage(m_listCtrl->GetWidID(), EV_LIST_ROWSETDATA, UMAKEINT(kUMainWidget, rownum), data);
		ULIST_INSERT entry;
		entry.row = rownum;

		entry.col = 0;
		entry.title = name;
		entry.icon = UCvsFiles::GetImageForEntry(data);
		UEventSendMessage(m_listCtrl->GetWidID(), EV_LIST_INSERT, kUMainWidget, &entry);

		entry.icon = 0L;

		// set item text for additional columns
		for(int j = 1; j < NUM_COLUMNS; j++)
		{
			const char *info = (*data)[j];
			if(info != 0L)
			{
				entry.col = j;
				entry.title = info;
				UEventSendMessage(m_listCtrl->GetWidID(), EV_LIST_INSERT, kUMainWidget, &entry);
			}
		}
		entry.col = EntnodeData::kStatus;
		entry.title = data->GetDesc();
		UEventSendMessage(m_listCtrl->GetWidID(), EV_LIST_INSERT, kUMainWidget, &entry);
		entry.col = EntnodeFile::kTag;
		entry.title = tagName.empty() ? "" : (const char *)tagName;
		UEventSendMessage(m_listCtrl->GetWidID(), EV_LIST_INSERT, kUMainWidget, &entry);

		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(!(bool)gFileViewIgnore && data->IsIgnored())
			return kContinueTraversal;

		int rownum;
		UEventSendMessage(m_listCtrl->GetWidID(), EV_LIST_NEWROW, kUMainWidget, &rownum);
		UEventSendMessage(m_listCtrl->GetWidID(), EV_LIST_ROWSETDATA, UMAKEINT(kUMainWidget, rownum), data);
		ULIST_INSERT entry;
		entry.row = rownum;

		entry.col = 0;
		entry.title = name;
		entry.icon = UCvsFiles::GetImageForEntry(data);
		UEventSendMessage(m_listCtrl->GetWidID(), EV_LIST_INSERT, kUMainWidget, &entry);

		entry.icon = 0L;

#if 0 /* TODO */
		lvi.state = data->IsLocked() ?
			INDEXTOSTATEIMAGEMASK(2) : INDEXTOSTATEIMAGEMASK(1);
#endif

		// set item text for additional columns
		for(int j = 1; j < NUM_COLUMNS; j++)
		{
			const char *info = (*data)[j];
			if(info != 0L)
			{
				entry.col = j;
				entry.title = info;
				UEventSendMessage(m_listCtrl->GetWidID(), EV_LIST_INSERT, kUMainWidget, &entry);
			}
		}
		entry.col = EntnodeData::kStatus;
		entry.title = data->GetDesc();
		UEventSendMessage(m_listCtrl->GetWidID(), EV_LIST_INSERT, kUMainWidget, &entry);

		return kContinueTraversal;
	}
};

UCvsFiles::UCvsFiles() : UWidget(kUCvsFilesID), m_entries(200, ENTNODE::Compare), m_entriesMod(0), m_entriesLogMod(0)

{
	m_sort = (int)gFileViewSort;
	m_ascendant = (bool)gFileViewSortAsc;
}

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

void UCvsFiles::OnDestroy(void)
{
	delete this;
}

void UCvsFiles::OnCreate(void)
{
	if(sFileIconTextIcon == 0L)
	{
		sFileIconTextIcon = UCreate_pixmap(this, "cvsfile.xpm");
		sFileIconBinaryIcon = UCreate_pixmap(this, "binfile.xpm");
		sFileIconUnknownIcon = UCreate_pixmap(this, "unkfile.xpm");
		sFileIconAddedIcon = UCreate_pixmap(this, "addfile.xpm");
		sFileIconConflictIcon = UCreate_pixmap(this, "conflict.xpm");
		sFileIconMissIcon = UCreate_pixmap(this, "missfile.xpm");
		sFolderIconIcon = UCreate_pixmap(this, "folder.xpm");
		sFolderIconUnknownIcon = UCreate_pixmap(this, "foldunk.xpm");
		sFolderIconMissIcon = UCreate_pixmap(this, "foldmiss.xpm");
		sFileIconTextModIcon = UCreate_pixmap(this, "modfile.xpm");
		sFileIconBinaryModIcon = UCreate_pixmap(this, "modbin.xpm");
		sFileIconIgnoredIcon = UCreate_pixmap(this, "ignfile.xpm");
		sFolderIconIgnoredIcon = UCreate_pixmap(this, "foldign.xpm");
		sFileIconRemovedIcon = UCreate_pixmap(this, "delfile.xpm");
	}

	UEventSendMessage(GetWidID(), EV_LIST_SETFEEDBACK, UMAKEINT(kUMainWidget, 0), 0L);
	UEventSendMessage(GetWidID(), EV_LIST_ADDCOLUMNS, UMAKEINT(kUMainWidget, NUM_COLUMNS), 0L);
	for(int i = 0; i < NUM_COLUMNS; i++)
	{
		UEventSendMessage(GetWidID(), EV_LIST_SETCOLTITLE, UMAKEINT(kUMainWidget, i), _gszColumnLabel[i]);
		UEventSendMessage(GetWidID(), EV_LIST_SETCOLWIDTH, UMAKEINT(kUMainWidget, i), (void *)_gnColumnWidth[i]);
	}
	UEventSendMessage(GetWidID(), EV_LIST_SETFEEDBACK, UMAKEINT(kUMainWidget, 1), 0L);
}

void UCvsFiles::ResetView(const char *path, bool notifyBrowser)
{
	m_path = path;
	ResetView(true);

	UStr title(UCvsApp::gApp->GetAppName());
	title << ": ";
	title << m_path;
	UEventSendMessage(kUCvsFrameID, EV_SETTEXT, kUMainWidget, (void *)(const char *)title);

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

void UCvsFiles::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);
}

int UCvsFiles::Search(const char *title)
{
	int row = -1;
	while((row = UEventSendMessage(GetWidID(), EV_LIST_GETNEXT,
									 UMAKEINT(kUMainWidget, row), 0L)) != -1)
	{
		ULIST_INFO info;
		info.col = 0;
		info.row = row;
		UEventSendMessage(GetWidID(), EV_LIST_GETINFO, kUMainWidget, &info);
		if(info.title == 0L)
			continue;

#if qUnix
		if(strcmp(title, info.title) == 0)
#else
		if(stricmp(title, info.title) == 0)
#endif
			return row;
	}

	return -1;
}

void UCvsFiles::ResetView(bool forceReload, bool notifyBrowser)
{
	UWaitCursor wait;

	static bool sSemaphore = false;
	USemaphore policeman(sSemaphore);
	if(policeman.IsEnteredTwice())
		return;

	// - check if we really need to reload (forceReload == false)
	// - wait a bit for the watcher in order to let him tell us
	// if something was modified.
	time_t newEntriesMod;
	time_t newEntriesLogMod;
	GetEntriesModTime(newEntriesMod, newEntriesLogMod);
	if(!forceReload)
	{
		if(m_entriesMod == newEntriesMod && m_entriesLogMod == newEntriesLogMod)
			return;
	}	

	// reset all the watchers
	m_entriesMod = newEntriesMod;
	m_entriesLogMod = newEntriesLogMod;

	// get the selection to later try to restore it
	CvsArgs selection(false);
	int nItem = -1;

	while((nItem = UEventSendMessage(GetWidID(), EV_LIST_GETNEXTSEL,
									 UMAKEINT(kUMainWidget, nItem), 0L)) != -1)
	{
		EntnodeData *data;
		UEventSendMessage(GetWidID(), EV_LIST_ROWGETDATA, UMAKEINT(kUMainWidget, nItem), &data);
		selection.add((*data)[EntnodeData::kName]);
	}

	UEventSendMessage(GetWidID(), EV_LIST_RESETALL, kUMainWidget, 0L);

	// refetch all items
	TViewFill traverse(this, m_entries);
	/*kTraversal res = */FileTraverse(m_path, traverse);

	// 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 = ((ENTNODE *)&theNode)->Data();
		if(!data->IsMissing())
			continue;

		int rownum;
		UEventSendMessage(GetWidID(), EV_LIST_NEWROW, kUMainWidget, &rownum);
		UEventSendMessage(GetWidID(), EV_LIST_ROWSETDATA, UMAKEINT(kUMainWidget, rownum), data);
		ULIST_INSERT entry;
		entry.row = rownum;

		entry.col = 0;
		entry.title = (*data)[EntnodeData::kName];
		entry.icon = UCvsFiles::GetImageForEntry(data);
		UEventSendMessage(GetWidID(), EV_LIST_INSERT, kUMainWidget, &entry);

		entry.icon = 0L;

		// set item text for additional columns
		for(int j = 1; j < NUM_COLUMNS; j++)
		{
			const char *info = (*data)[j];
			if(info != 0L)
			{
				entry.col = j;
				entry.title = info;
				UEventSendMessage(GetWidID(), EV_LIST_INSERT, kUMainWidget, &entry);
			}
		}
		entry.col = EntnodeData::kStatus;
		entry.title = data->GetDesc();
		UEventSendMessage(GetWidID(), EV_LIST_INSERT, kUMainWidget, &entry);
	}

	Resort();

	// now restore the selection
	int argc = selection.Argc(), c;
	char * const *argv = selection.Argv();
	for(c = 0; c < argc; c++)
	{
		int row = Search(argv[c]);
		if(row != -1)
			UEventSendMessage(GetWidID(), EV_LIST_ADDSEL, UMAKEINT(kUMainWidget, row), 0L);
	}

	if(notifyBrowser)
	{
		// notify the tree
		UCvsApp::gApp->GetBrowserView()->ResetView(forceReload);
	}
}

void *UCvsFiles::GetImageForEntry(EntnodeData *data)
{
	int result;
	void *resIcon = 0L;

	if(data->GetType() == ENT_FILE)
	{
		const char *info = 0L;
		if(data->IsIgnored())
		{
			result = kFileIconIgnored;
		}
		else if(data->IsUnknown())
		{
			result = kFileIconUnknown;
		}
		else if(data->IsMissing())
		{
			result = data->IsRemoved() ? kFileIconRemoved : kFileIconMiss;
		}
		else if((*data)[EntnodeFile::kConflict] != 0L)
		{
			result = kFileIconConflict;
		}
		else if(data->IsRemoved())
		{
			result = kFileIconRemoved;
		}
		else if((info = (*data)[EntnodeFile::kOption]) != 0L && strcmp(info, "-kb") == 0)
		{
			result = data->IsUnmodified() ? kFileIconBinary : kFileIconBinaryMod;
		}
		else
		{
			result = data->IsUnmodified() ? kFileIconText : kFileIconTextMod;
		}
	}
	else
	{
		if(data->IsIgnored())
		{
			result = kFolderIconIgnored;
		}
		else if(data->IsUnknown())
		{
			result = kFolderIconUnknown;
		}
		else if(data->IsMissing())
		{
			result = kFolderIconMiss;
		}
		else
		{
			result = kFolderIcon;
		}
	}

#if qGTK
	void *pixmap = 0L;
	switch(result)
	{
	case kFileIconText:
		pixmap = sFileIconTextIcon;
		break;
	case kFileIconBinary:
		pixmap = sFileIconBinaryIcon;
		break;
	case kFileIconUnknown:
		pixmap = sFileIconUnknownIcon;
		break;
	case kFileIconAdded:
		pixmap = sFileIconAddedIcon;
		break;
	case kFileIconConflict:
		pixmap = sFileIconConflictIcon;
		break;
	case kFileIconMiss:
		pixmap = sFileIconMissIcon;
		break;
	case kFolderIcon:
		pixmap = sFolderIconIcon;
		break;
	case kFolderIconUnknown:
		pixmap = sFolderIconUnknownIcon;
		break;
	case kFolderIconMiss:
		pixmap = sFolderIconMissIcon;
		break;
	case kFileIconTextMod:
		pixmap = sFileIconTextModIcon;
		break;
	case kFileIconBinaryMod:
		pixmap = sFileIconBinaryModIcon;
		break;
	case kFileIconIgnored:
		pixmap = sFileIconIgnoredIcon;
		break;
	case kFolderIconIgnored:
		pixmap = sFolderIconIgnoredIcon;
		break;
	case kFileIconRemoved:
		pixmap = sFileIconRemovedIcon;
		break;
	}
	if(pixmap != 0L)
	{
		resIcon = gtk_pixmap_new (GTK_PIXMAP(pixmap)->pixmap, GTK_PIXMAP(pixmap)->mask);
	}
#endif

	return resIcon;
}


int UCvsFiles::OnCmdUpdate(void)
{
	int nItem = -1;

	MultiFiles mf;
	mf.newdir(m_path);

	// first add the folders
	while((nItem = UEventSendMessage(GetWidID(), EV_LIST_GETNEXTSEL, UMAKEINT(kUMainWidget, nItem), 0L)) != -1)
	{
		EntnodeData *data;
		UEventSendMessage(GetWidID(), EV_LIST_ROWGETDATA, UMAKEINT(kUMainWidget, nItem), &data);

		if(data->GetType() == ENT_SUBDIR)
		{
			CStr fullpath;
			fullpath = m_path;
			if(!fullpath.endsWith(kPathDelimiter))
				fullpath << kPathDelimiter;
			fullpath << (*data)[EntnodeData::kName];
			CvsCmdUpdateFolder(fullpath);
		}
		else
		{
			mf.newfile((*data)[EntnodeData::kName]);
		}
	}
	if(mf.NumFiles() != 0)
	{
		CvsCmdUpdateFiles(&mf);
	}
	return 0;
}

bool UCvsFiles::DisableGeneric()
{
	return UEventSendMessage(GetWidID(), EV_LIST_GETNEXTSEL, UMAKEINT(kUMainWidget, -1), 0L) == -1 ||
		UCvsApp::gApp->IsCvsRunning() || gCvsPrefs.empty() || !HasFocus();
}

void UCvsFiles::OnCmdUIUpdate(UCmdUI *pCmdUI)
{
	if(DisableGeneric())
	{
		pCmdUI->Enable(false);
		return;
	}

	int nItem = -1;
	bool res = true;
	int numFiles = 0;
	int numFolders = 0;

	while((nItem = UEventSendMessage(GetWidID(), EV_LIST_GETNEXTSEL, UMAKEINT(kUMainWidget, nItem), 0L)) != -1)
	{
		EntnodeData *data;
		UEventSendMessage(GetWidID(), EV_LIST_ROWGETDATA, UMAKEINT(kUMainWidget, nItem), &data);

		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 UCvsFiles::OnCmdUIAdd(UCmdUI *pCmdUI)
{
	if(DisableGeneric())
	{
		pCmdUI->Enable(false);
		return;
	}

	int nItem = -1;
	bool res = true;
	int numFiles = 0;
	int numFolders = 0;

	while((nItem = UEventSendMessage(GetWidID(), EV_LIST_GETNEXTSEL, UMAKEINT(kUMainWidget, nItem), 0L)) != -1)
	{
		EntnodeData *data;
		UEventSendMessage(GetWidID(), EV_LIST_ROWGETDATA, UMAKEINT(kUMainWidget, nItem), &data);

		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 UCvsFiles::OnCmdUIAddB(UCmdUI *pCmdUI)
{
	if(DisableGeneric())
	{
		pCmdUI->Enable(false);
		return;
	}

	int nItem = -1;
	bool res = true;

	while((nItem = UEventSendMessage(GetWidID(), EV_LIST_GETNEXTSEL, UMAKEINT(kUMainWidget, nItem), 0L)) != -1)
	{
		EntnodeData *data;
		UEventSendMessage(GetWidID(), EV_LIST_ROWGETDATA, UMAKEINT(kUMainWidget, nItem), &data);

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

	pCmdUI->Enable(res);
}

void UCvsFiles::OnCmdUIRelease(UCmdUI *pCmdUI)
{
	if(DisableGeneric())
	{
		pCmdUI->Enable(false);
		return;
	}

	int nItem = -1;
	bool res = true;
	int numFolders = 0;

	while((nItem = UEventSendMessage(GetWidID(), EV_LIST_GETNEXTSEL, UMAKEINT(kUMainWidget, nItem), 0L)) != -1)
	{
		EntnodeData *data;
		UEventSendMessage(GetWidID(), EV_LIST_ROWGETDATA, UMAKEINT(kUMainWidget, nItem), &data);

		if(data->IsUnknown() || data->GetType() != ENT_SUBDIR)
		{
			res = false;
			break;
		}
		numFolders++;

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

	pCmdUI->Enable(res);
}

void UCvsFiles::OnCmdUIRmv(UCmdUI *pCmdUI)
{
	if(DisableGeneric())
	{
		pCmdUI->Enable(false);
		return;
	}

	int nItem = -1;
	bool res = true;

	while((nItem = UEventSendMessage(GetWidID(), EV_LIST_GETNEXTSEL, UMAKEINT(kUMainWidget, nItem), 0L)) != -1)
	{
		EntnodeData *data;
		UEventSendMessage(GetWidID(), EV_LIST_ROWGETDATA, UMAKEINT(kUMainWidget, nItem), &data);

		if(data->IsUnknown() || data->IsRemoved() || data->GetType() == ENT_SUBDIR)
		{
			res = false;
			break;
		}
	}

	pCmdUI->Enable(res);
}

void UCvsFiles::OnCmdUIGraph(UCmdUI *pCmdUI)
{
	if(DisableGeneric())
	{
		pCmdUI->Enable(false);
		return;
	}

	int nItem = -1;
	bool res = true;

	while((nItem = UEventSendMessage(GetWidID(), EV_LIST_GETNEXTSEL, UMAKEINT(kUMainWidget, nItem), 0L)) != -1)
	{
		EntnodeData *data;
		UEventSendMessage(GetWidID(), EV_LIST_ROWGETDATA, UMAKEINT(kUMainWidget, nItem), &data);

		if(data->IsUnknown() || data->GetType() == ENT_SUBDIR)
		{
			res = false;
			break;
		}
	}

	pCmdUI->Enable(res);
}

void UCvsFiles::OnCmdUIEditseldef(UCmdUI *pCmdUI)
{
	if(DisableGeneric())
	{
		pCmdUI->Enable(false);
		return;
	}

	int nItem = -1;
	bool res = true;

	while((nItem = UEventSendMessage(GetWidID(), EV_LIST_GETNEXTSEL, UMAKEINT(kUMainWidget, nItem), 0L)) != -1)
	{
		EntnodeData *data;
		UEventSendMessage(GetWidID(), EV_LIST_ROWGETDATA, UMAKEINT(kUMainWidget, nItem), &data);

		if(data->GetType() == ENT_SUBDIR || data->IsMissing())
		{
			res = false;
			break;
		}
	}

	pCmdUI->Enable(res);
	if(gCvsPrefs.Viewer() != 0L)
	{
		CStr title("Edit with ");
		title << gCvsPrefs.Viewer();
		pCmdUI->SetText(title);
	}
}

void UCvsFiles::OnCmdUIReload(UCmdUI *pCmdUI)
{
	pCmdUI->Enable(!DisableGeneric());
}

void UCvsFiles::OnCmdUIUpone(UCmdUI *pCmdUI)
{
	pCmdUI->Enable(!DisableGeneric());
}

void UCvsFiles::OnCmdUITrash(UCmdUI *pCmdUI)
{
	if(DisableGeneric())
	{
		pCmdUI->Enable(false);
		return;
	}

	int nItem = -1;
	bool res = true;

	while((nItem = UEventSendMessage(GetWidID(), EV_LIST_GETNEXTSEL, UMAKEINT(kUMainWidget, nItem), 0L)) != -1)
	{
		EntnodeData *data;
		UEventSendMessage(GetWidID(), EV_LIST_ROWGETDATA, UMAKEINT(kUMainWidget, nItem), &data);
		if(data->GetType() == ENT_SUBDIR || data->IsMissing())
		{
			res = false;
			break;
		}
	}

	pCmdUI->Enable(res);
}

void UCvsFiles::OnCmdUIExplore(UCmdUI *pCmdUI)
{
	if(DisableGeneric())
	{
		pCmdUI->Enable(false);
		return;
	}

	int nItem = -1;
	bool res = true;
	int numItem = 0;

	while((nItem = UEventSendMessage(GetWidID(), EV_LIST_GETNEXTSEL, UMAKEINT(kUMainWidget, nItem), 0L)) != -1)
	{
		EntnodeData *data;
		UEventSendMessage(GetWidID(), EV_LIST_ROWGETDATA, UMAKEINT(kUMainWidget, nItem), &data);

		if(data->IsMissing() || ++numItem > 1)
		{
			res = false;
			break;
		}
	}

	pCmdUI->Enable(res);
}

void UCvsFiles::OnCmdUIIgnore(UCmdUI *pCmdUI)
{
	pCmdUI->Enable(!DisableGeneric());
	pCmdUI->Check(gFileViewIgnore);
}

void UCvsFiles::OnCmdUIMacroSel(int cmd, UCmdUI *pCmdUI)
{
	if(DisableGeneric())
	{
		pCmdUI->Enable(false);
		return;
	}

	int nItem = UEventSendMessage(GetWidID(), EV_LIST_GETNEXTSEL, UMAKEINT(kUMainWidget, -1), 0L);
	pCmdUI->Enable(nItem != -1 && CTcl_Interp::IsAvail());
}

int UCvsFiles::OnCmdAdd(void)
{
	int nItem = -1;

	MultiFiles mf;
	mf.newdir(m_path);

	// first add the folders
	while((nItem = UEventSendMessage(GetWidID(), EV_LIST_GETNEXTSEL, UMAKEINT(kUMainWidget, nItem), 0L)) != -1)
	{
		EntnodeData *data;
		UEventSendMessage(GetWidID(), EV_LIST_ROWGETDATA, UMAKEINT(kUMainWidget, nItem), &data);

		if(data->GetType() == ENT_SUBDIR)
		{
			CStr fullpath;
			fullpath = m_path;
			if(!fullpath.endsWith(kPathDelimiter))
				fullpath << kPathDelimiter;
			fullpath << (*data)[EntnodeData::kName];
			CvsCmdAddFolder(fullpath);
		}
		else
		{
			mf.newfile((*data)[EntnodeData::kName]);
		}
	}
	if(mf.NumFiles() != 0)
	{
		CvsCmdAddFiles(&mf);
	}
	return 0;
}

int UCvsFiles::OnCmdAddb(void)
{
	int nItem = -1;

	MultiFiles mf;
	mf.newdir(m_path);

	// first add the folders
	while((nItem = UEventSendMessage(GetWidID(), EV_LIST_GETNEXTSEL, UMAKEINT(kUMainWidget, nItem), 0L)) != -1)
	{
		EntnodeData *data;
		UEventSendMessage(GetWidID(), EV_LIST_ROWGETDATA, UMAKEINT(kUMainWidget, nItem), &data);

		if(data->GetType() == ENT_SUBDIR)
		{
			CStr fullpath;
			fullpath = m_path;
			if(!fullpath.endsWith(kPathDelimiter))
				fullpath << kPathDelimiter;
			fullpath << (*data)[EntnodeData::kName];
			CvsCmdAddFolder(fullpath);
		}
		else
		{
			mf.newfile((*data)[EntnodeData::kName]);
		}
	}
	if(mf.NumFiles() != 0)
	{
		CvsCmdAddBinaryFiles(&mf);
	}
	return 0;
}

int UCvsFiles::OnCmdCommit(void)
{
	int nItem = -1;

	MultiFiles mf;
	mf.newdir(m_path);

	// first add the folders
	while((nItem = UEventSendMessage(GetWidID(), EV_LIST_GETNEXTSEL, UMAKEINT(kUMainWidget, nItem), 0L)) != -1)
	{
		EntnodeData *data;
		UEventSendMessage(GetWidID(), EV_LIST_ROWGETDATA, UMAKEINT(kUMainWidget, nItem), &data);

		if(data->GetType() == ENT_SUBDIR)
		{
			CStr fullpath;
			fullpath = m_path;
			if(!fullpath.endsWith(kPathDelimiter))
				fullpath << kPathDelimiter;
			fullpath << (*data)[EntnodeData::kName];
			CvsCmdCommitFolder(fullpath);
		}
		else
		{
			mf.newfile((*data)[EntnodeData::kName]);
		}
	}
	if(mf.NumFiles() != 0)
	{
		CvsCmdCommitFiles(&mf);
	}
	return 0;
}

int UCvsFiles::OnCmdRmv(void)
{
	int nItem = -1;

	MultiFiles mf;
	mf.newdir(m_path);

	// first add the folders
	while((nItem = UEventSendMessage(GetWidID(), EV_LIST_GETNEXTSEL, UMAKEINT(kUMainWidget, nItem), 0L)) != -1)
	{
		EntnodeData *data;
		UEventSendMessage(GetWidID(), EV_LIST_ROWGETDATA, UMAKEINT(kUMainWidget, nItem), &data);

		if(data->GetType() != ENT_FILE)
			continue;

		mf.newfile((*data)[EntnodeData::kName]);
	}
	if(mf.NumFiles() != 0)
	{
		CvsCmdRemoveFiles(&mf);
	}
	return 0;
}

int UCvsFiles::OnCmdQueryUpdate(void)
{
	int nItem = -1;

	MultiFiles mf;
	mf.newdir(m_path);

	// first add the folders
	while((nItem = UEventSendMessage(GetWidID(), EV_LIST_GETNEXTSEL, UMAKEINT(kUMainWidget, nItem), 0L)) != -1)
	{
		EntnodeData *data;
		UEventSendMessage(GetWidID(), EV_LIST_ROWGETDATA, UMAKEINT(kUMainWidget, nItem), &data);

		if(data->GetType() == ENT_SUBDIR)
		{
			CStr fullpath;
			fullpath = m_path;
			if(!fullpath.endsWith(kPathDelimiter))
				fullpath << kPathDelimiter;
			fullpath << (*data)[EntnodeData::kName];
			CvsCmdUpdateFolder(fullpath, true);
		}
		else
		{
			mf.newfile((*data)[EntnodeData::kName]);
		}
	}
	if(mf.NumFiles() != 0)
	{
		CvsCmdUpdateFiles(&mf, true);
	}
	return 0;
}

int UCvsFiles::OnCmdReload(void)
{
	ResetView(true, true);
	return 0;
}

int UCvsFiles::OnCmdUpone(void)
{
	UStr uppath, filename;
	SplitPath(m_path, uppath, filename);
	ResetView(uppath, true);
	return 0;
}

int UCvsFiles::OnCmdTrash(void)
{
	int nItem = -1;

	MultiFiles mf;
	mf.newdir(m_path);

	// first add the folders
	while((nItem = UEventSendMessage(GetWidID(), EV_LIST_GETNEXTSEL, UMAKEINT(kUMainWidget, nItem), 0L)) != -1)
	{
		EntnodeData *data;
		UEventSendMessage(GetWidID(), EV_LIST_ROWGETDATA, UMAKEINT(kUMainWidget, nItem), &data);

		if(data->GetType() != ENT_FILE)
			continue;

		mf.newfile((*data)[EntnodeData::kName]);
	}

	if(mf.NumFiles() != 0)
	{
		CvsArgs args(false);
		mf.next();
		const char *dir = mf.add(args);
		char * const *argv = args.Argv();
		int argc = args.Argc();
		for(int i = 0; i < argc; i++)
		{
			CStr fullpath(dir);
			if(!fullpath.empty() && !fullpath.endsWith(kPathDelimiter))
			{
				fullpath << kPathDelimiter;
			}
			fullpath << argv[i];

			if(chmod(fullpath, 0666) != 0)
			{
				cvs_err("Unable to change permission on '%s' (error %d)\n", (const char *)fullpath, errno);
				continue;
			}
			
			if(!CompatMoveToTrash(argv[i], dir))
				cvs_err("Unable to remove '%s' (error %d)\n", (const char *)fullpath, errno);
			else
#ifdef WIN32
				cvs_out("'%s' has been moved successfully to the recycle bin...\n", (const char *)fullpath);
#else
				cvs_out("'%s' has been deleted successfully...\n", (const char *)fullpath);
#endif
			}

		ResetView(true);
	}
	return 0;
}

int UCvsFiles::OnCmdDiff(void)
{
	int nItem = -1;

	MultiFiles mf;
	mf.newdir(m_path);

	// first add the folders
	while((nItem = UEventSendMessage(GetWidID(), EV_LIST_GETNEXTSEL, UMAKEINT(kUMainWidget, nItem), 0L)) != -1)
	{
		EntnodeData *data;
		UEventSendMessage(GetWidID(), EV_LIST_ROWGETDATA, UMAKEINT(kUMainWidget, nItem), &data);

		if(data->GetType() == ENT_SUBDIR)
		{
			CStr fullpath;
			fullpath = m_path;
			if(!fullpath.endsWith(kPathDelimiter))
				fullpath << kPathDelimiter;
			fullpath << (*data)[EntnodeData::kName];
			CvsCmdDiffFolder(fullpath);
		}
		else
		{
			mf.newfile((*data)[EntnodeData::kName]);
		}
	}
	if(mf.NumFiles() != 0)
	{
		CvsCmdDiffFiles(&mf);
	}
	return 0;
}

int UCvsFiles::OnCmdLog(void)
{
	int nItem = -1;

	MultiFiles mf;
	mf.newdir(m_path);

	// first add the folders
	while((nItem = UEventSendMessage(GetWidID(), EV_LIST_GETNEXTSEL, UMAKEINT(kUMainWidget, nItem), 0L)) != -1)
	{
		EntnodeData *data;
		UEventSendMessage(GetWidID(), EV_LIST_ROWGETDATA, UMAKEINT(kUMainWidget, nItem), &data);

		if(data->GetType() == ENT_SUBDIR)
		{
			CStr fullpath;
			fullpath = m_path;
			if(!fullpath.endsWith(kPathDelimiter))
				fullpath << kPathDelimiter;
			fullpath << (*data)[EntnodeData::kName];
			CvsCmdLogFolder(fullpath);
		}
		else
		{
			mf.newfile((*data)[EntnodeData::kName]);
		}
	}
	if(mf.NumFiles() != 0)
	{
		CvsCmdLogFiles(&mf);
	}
	return 0;
}

int UCvsFiles::OnCmdGraph(void)
{
	int nItem = -1;

	MultiFiles mf;
	mf.newdir(m_path);

	// first add the folders
	while((nItem = UEventSendMessage(GetWidID(), EV_LIST_GETNEXTSEL, UMAKEINT(kUMainWidget, nItem), 0L)) != -1)
	{
		EntnodeData *data;
		UEventSendMessage(GetWidID(), EV_LIST_ROWGETDATA, UMAKEINT(kUMainWidget, nItem), &data);

		if(data->GetType() != ENT_FILE)
			continue;

		mf.newfile((*data)[EntnodeData::kName]);
	}
	if(mf.NumFiles() != 0)
	{
		CvsCmdLogFiles(&mf, true);
	}
	return 0;
}

int UCvsFiles::OnCmdStatus(void)
{
	int nItem = -1;

	MultiFiles mf;
	mf.newdir(m_path);

	// first add the folders
	while((nItem = UEventSendMessage(GetWidID(), EV_LIST_GETNEXTSEL, UMAKEINT(kUMainWidget, nItem), 0L)) != -1)
	{
		EntnodeData *data;
		UEventSendMessage(GetWidID(), EV_LIST_ROWGETDATA, UMAKEINT(kUMainWidget, nItem), &data);

		if(data->GetType() == ENT_SUBDIR)
		{
			CStr fullpath;
			fullpath = m_path;
			if(!fullpath.endsWith(kPathDelimiter))
				fullpath << kPathDelimiter;
			fullpath << (*data)[EntnodeData::kName];
			CvsCmdStatusFolder(fullpath);
		}
		else
		{
			mf.newfile((*data)[EntnodeData::kName]);
		}
	}
	if(mf.NumFiles() != 0)
	{
		CvsCmdStatusFiles(&mf);
	}
	return 0;
}

int UCvsFiles::OnCmdLock(void)
{
	int nItem = -1;

	MultiFiles mf;
	mf.newdir(m_path);

	// first add the folders
	while((nItem = UEventSendMessage(GetWidID(), EV_LIST_GETNEXTSEL, UMAKEINT(kUMainWidget, nItem), 0L)) != -1)
	{
		EntnodeData *data;
		UEventSendMessage(GetWidID(), EV_LIST_ROWGETDATA, UMAKEINT(kUMainWidget, nItem), &data);

		if(data->GetType() == ENT_SUBDIR)
		{
			CStr fullpath;
			fullpath = m_path;
			if(!fullpath.endsWith(kPathDelimiter))
				fullpath << kPathDelimiter;
			fullpath << (*data)[EntnodeData::kName];
			CvsCmdLockFolder(fullpath);
		}
		else
		{
			mf.newfile((*data)[EntnodeData::kName]);
		}
	}
	if(mf.NumFiles() != 0)
	{
		CvsCmdLockFiles(&mf);
	}
	return 0;
}

int UCvsFiles::OnCmdUnlock(void)
{
	int nItem = -1;

	MultiFiles mf;
	mf.newdir(m_path);

	// first add the folders
	while((nItem = UEventSendMessage(GetWidID(), EV_LIST_GETNEXTSEL, UMAKEINT(kUMainWidget, nItem), 0L)) != -1)
	{
		EntnodeData *data;
		UEventSendMessage(GetWidID(), EV_LIST_ROWGETDATA, UMAKEINT(kUMainWidget, nItem), &data);

		if(data->GetType() == ENT_SUBDIR)
		{
			CStr fullpath;
			fullpath = m_path;
			if(!fullpath.endsWith(kPathDelimiter))
				fullpath << kPathDelimiter;
			fullpath << (*data)[EntnodeData::kName];
			CvsCmdUnlockFolder(fullpath);
		}
		else
		{
			mf.newfile((*data)[EntnodeData::kName]);
		}
	}
	if(mf.NumFiles() != 0)
	{
		CvsCmdUnlockFiles(&mf);
	}
	return 0;
}

int UCvsFiles::OnCmdWatchOn(void)
{
	int nItem = -1;

	MultiFiles mf;
	mf.newdir(m_path);

	// first add the folders
	while((nItem = UEventSendMessage(GetWidID(), EV_LIST_GETNEXTSEL, UMAKEINT(kUMainWidget, nItem), 0L)) != -1)
	{
		EntnodeData *data;
		UEventSendMessage(GetWidID(), EV_LIST_ROWGETDATA, UMAKEINT(kUMainWidget, nItem), &data);

		if(data->GetType() == ENT_SUBDIR)
		{
			CStr fullpath;
			fullpath = m_path;
			if(!fullpath.endsWith(kPathDelimiter))
				fullpath << kPathDelimiter;
			fullpath << (*data)[EntnodeData::kName];
			CvsCmdWatchOnFolder(fullpath);
		}
		else
		{
			mf.newfile((*data)[EntnodeData::kName]);
		}
	}
	if(mf.NumFiles() != 0)
	{
		CvsCmdWatchOnFiles(&mf);
	}
	return 0;
}

int UCvsFiles::OnCmdWatchOff(void)
{
	int nItem = -1;

	MultiFiles mf;
	mf.newdir(m_path);

	// first add the folders
	while((nItem = UEventSendMessage(GetWidID(), EV_LIST_GETNEXTSEL, UMAKEINT(kUMainWidget, nItem), 0L)) != -1)
	{
		EntnodeData *data;
		UEventSendMessage(GetWidID(), EV_LIST_ROWGETDATA, UMAKEINT(kUMainWidget, nItem), &data);

		if(data->GetType() == ENT_SUBDIR)
		{
			CStr fullpath;
			fullpath = m_path;
			if(!fullpath.endsWith(kPathDelimiter))
				fullpath << kPathDelimiter;
			fullpath << (*data)[EntnodeData::kName];
			CvsCmdWatchOffFolder(fullpath);
		}
		else
		{
			mf.newfile((*data)[EntnodeData::kName]);
		}
	}
	if(mf.NumFiles() != 0)
	{
		CvsCmdWatchOffFiles(&mf);
	}
	return 0;
}

int UCvsFiles::OnCmdEdit(void)
{
	int nItem = -1;

	MultiFiles mf;
	mf.newdir(m_path);

	// first add the folders
	while((nItem = UEventSendMessage(GetWidID(), EV_LIST_GETNEXTSEL, UMAKEINT(kUMainWidget, nItem), 0L)) != -1)
	{
		EntnodeData *data;
		UEventSendMessage(GetWidID(), EV_LIST_ROWGETDATA, UMAKEINT(kUMainWidget, nItem), &data);

		if(data->GetType() == ENT_SUBDIR)
		{
			CStr fullpath;
			fullpath = m_path;
			if(!fullpath.endsWith(kPathDelimiter))
				fullpath << kPathDelimiter;
			fullpath << (*data)[EntnodeData::kName];
			CvsCmdEditFolder(fullpath);
		}
		else
		{
			mf.newfile((*data)[EntnodeData::kName]);
		}
	}
	if(mf.NumFiles() != 0)
	{
		CvsCmdEditFiles(&mf);
	}
	return 0;
}

int UCvsFiles::OnCmdUnedit(void)
{
	int nItem = -1;

	MultiFiles mf;
	mf.newdir(m_path);

	// first add the folders
	while((nItem = UEventSendMessage(GetWidID(), EV_LIST_GETNEXTSEL, UMAKEINT(kUMainWidget, nItem), 0L)) != -1)
	{
		EntnodeData *data;
		UEventSendMessage(GetWidID(), EV_LIST_ROWGETDATA, UMAKEINT(kUMainWidget, nItem), &data);

		if(data->GetType() == ENT_SUBDIR)
		{
			CStr fullpath;
			fullpath = m_path;
			if(!fullpath.endsWith(kPathDelimiter))
				fullpath << kPathDelimiter;
			fullpath << (*data)[EntnodeData::kName];
			CvsCmdUneditFolder(fullpath);
		}
		else
		{
			mf.newfile((*data)[EntnodeData::kName]);
		}
	}
	if(mf.NumFiles() != 0)
	{
		CvsCmdUneditFiles(&mf);
	}
	return 0;
}

int UCvsFiles::OnCmdWatchers(void)
{
	int nItem = -1;

	MultiFiles mf;
	mf.newdir(m_path);

	// first add the folders
	while((nItem = UEventSendMessage(GetWidID(), EV_LIST_GETNEXTSEL, UMAKEINT(kUMainWidget, nItem), 0L)) != -1)
	{
		EntnodeData *data;
		UEventSendMessage(GetWidID(), EV_LIST_ROWGETDATA, UMAKEINT(kUMainWidget, nItem), &data);

		if(data->GetType() == ENT_SUBDIR)
		{
			CStr fullpath;
			fullpath = m_path;
			if(!fullpath.endsWith(kPathDelimiter))
				fullpath << kPathDelimiter;
			fullpath << (*data)[EntnodeData::kName];
			CvsCmdWatchersFolder(fullpath);
		}
		else
		{
			mf.newfile((*data)[EntnodeData::kName]);
		}
	}
	if(mf.NumFiles() != 0)
	{
		CvsCmdWatchersFiles(&mf);
	}
	return 0;
}

int UCvsFiles::OnCmdEditors(void)
{
	int nItem = -1;

	MultiFiles mf;
	mf.newdir(m_path);

	// first add the folders
	while((nItem = UEventSendMessage(GetWidID(), EV_LIST_GETNEXTSEL, UMAKEINT(kUMainWidget, nItem), 0L)) != -1)
	{
		EntnodeData *data;
		UEventSendMessage(GetWidID(), EV_LIST_ROWGETDATA, UMAKEINT(kUMainWidget, nItem), &data);

		if(data->GetType() == ENT_SUBDIR)
		{
			CStr fullpath;
			fullpath = m_path;
			if(!fullpath.endsWith(kPathDelimiter))
				fullpath << kPathDelimiter;
			fullpath << (*data)[EntnodeData::kName];
			CvsCmdEditorsFolder(fullpath);
		}
		else
		{
			mf.newfile((*data)[EntnodeData::kName]);
		}
	}
	if(mf.NumFiles() != 0)
	{
		CvsCmdEditorsFiles(&mf);
	}
	return 0;
}

int UCvsFiles::OnCmdRelease(void)
{
	int nItem = -1;

	// first add the folders
	while((nItem = UEventSendMessage(GetWidID(), EV_LIST_GETNEXTSEL, UMAKEINT(kUMainWidget, nItem), 0L)) != -1)
	{
		EntnodeData *data;
		UEventSendMessage(GetWidID(), EV_LIST_ROWGETDATA, UMAKEINT(kUMainWidget, nItem), &data);

		if(data->GetType() == ENT_SUBDIR)
		{
			CStr fullpath;
			fullpath = m_path;
			if(!fullpath.endsWith(kPathDelimiter))
				fullpath << kPathDelimiter;
			fullpath << (*data)[EntnodeData::kName];
			CvsCmdReleaseFolder(fullpath);
		}
	}
	return 0;
}

int UCvsFiles::OnCmdTagNew(void)
{
	int nItem = -1;

	MultiFiles mf;
	mf.newdir(m_path);

	// first add the folders
	while((nItem = UEventSendMessage(GetWidID(), EV_LIST_GETNEXTSEL, UMAKEINT(kUMainWidget, nItem), 0L)) != -1)
	{
		EntnodeData *data;
		UEventSendMessage(GetWidID(), EV_LIST_ROWGETDATA, UMAKEINT(kUMainWidget, nItem), &data);

		if(data->GetType() == ENT_SUBDIR)
		{
			CStr fullpath;
			fullpath = m_path;
			if(!fullpath.endsWith(kPathDelimiter))
				fullpath << kPathDelimiter;
			fullpath << (*data)[EntnodeData::kName];
			CvsCmdTagCreateFolder(fullpath);
		}
		else
		{
			mf.newfile((*data)[EntnodeData::kName]);
		}
	}
	if(mf.NumFiles() != 0)
	{
		CvsCmdTagCreateFiles(&mf);
	}
	return 0;
}

int UCvsFiles::OnCmdTagDelete(void)
{
	int nItem = -1;

	MultiFiles mf;
	mf.newdir(m_path);

	// first add the folders
	while((nItem = UEventSendMessage(GetWidID(), EV_LIST_GETNEXTSEL, UMAKEINT(kUMainWidget, nItem), 0L)) != -1)
	{
		EntnodeData *data;
		UEventSendMessage(GetWidID(), EV_LIST_ROWGETDATA, UMAKEINT(kUMainWidget, nItem), &data);

		if(data->GetType() == ENT_SUBDIR)
		{
			CStr fullpath;
			fullpath = m_path;
			if(!fullpath.endsWith(kPathDelimiter))
				fullpath << kPathDelimiter;
			fullpath << (*data)[EntnodeData::kName];
			CvsCmdTagDeleteFolder(fullpath);
		}
		else
		{
			mf.newfile((*data)[EntnodeData::kName]);
		}
	}
	if(mf.NumFiles() != 0)
	{
		CvsCmdTagDeleteFiles(&mf);
	}
	return 0;
}

int UCvsFiles::OnCmdTagBranch(void)
{
	int nItem = -1;

	MultiFiles mf;
	mf.newdir(m_path);

	// first add the folders
	while((nItem = UEventSendMessage(GetWidID(), EV_LIST_GETNEXTSEL, UMAKEINT(kUMainWidget, nItem), 0L)) != -1)
	{
		EntnodeData *data;
		UEventSendMessage(GetWidID(), EV_LIST_ROWGETDATA, UMAKEINT(kUMainWidget, nItem), &data);

		if(data->GetType() == ENT_SUBDIR)
		{
			CStr fullpath;
			fullpath = m_path;
			if(!fullpath.endsWith(kPathDelimiter))
				fullpath << kPathDelimiter;
			fullpath << (*data)[EntnodeData::kName];
			CvsCmdTagBranchFolder(fullpath);
		}
		else
		{
			mf.newfile((*data)[EntnodeData::kName]);
		}
	}
	if(mf.NumFiles() != 0)
	{
		CvsCmdTagBranchFiles(&mf);
	}
	return 0;
}

int UCvsFiles::OnCmdExplore(void)
{
	int nItem = -1;

	// first add the folders
	if((nItem = UEventSendMessage(GetWidID(), EV_LIST_GETNEXTSEL, UMAKEINT(kUMainWidget, nItem), 0L)) != -1)
	{
		EntnodeData *data;
		UEventSendMessage(GetWidID(), EV_LIST_ROWGETDATA, UMAKEINT(kUMainWidget, nItem), &data);

		CStr fullpath;
		fullpath = m_path;
		if(!fullpath.endsWith(kPathDelimiter))
			fullpath << kPathDelimiter;
		fullpath << (*data)[EntnodeData::kName];

#ifdef WIN32
		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());
		}
#endif
#ifdef qUnix
		CvsArgs args(false);
		args.add(gCvsPrefs.Browser());
		args.add(fullpath);
		UCvsApp::gApp->Execute(args.Argc(), args.Argv());
#endif
	}
	return 0;
}

int UCvsFiles::OnCmdIgnore(void)
{
	gFileViewIgnore = !(bool)gFileViewIgnore;
	ResetView(true, true);
	return 0;
}

int UCvsFiles::OnCmdEditsel(void)
{
	int nItem = -1;

	while((nItem = UEventSendMessage(GetWidID(), EV_LIST_GETNEXTSEL, UMAKEINT(kUMainWidget, nItem), 0L)) != -1)
	{
		EntnodeData *data;
		UEventSendMessage(GetWidID(), EV_LIST_ROWGETDATA, UMAKEINT(kUMainWidget, nItem), &data);
		EditSel(data);
	}
	return 0;
}

int UCvsFiles::OnCmdEditseldef(void)
{
	int nItem = -1;

	while((nItem = UEventSendMessage(GetWidID(), EV_LIST_GETNEXTSEL, UMAKEINT(kUMainWidget, nItem), 0L)) != -1)
	{
		EntnodeData *data;
		UEventSendMessage(GetWidID(), EV_LIST_ROWGETDATA, UMAKEINT(kUMainWidget, nItem), &data);
		EditSel(data, true);
	}
	return 0;
}

int UCvsFiles::OnMacroSel(int cmd)
{
	CTcl_Interp interp;
	CMacroEntry & entry = gMacrosSel.entries[cmd - cmdSELMACRO];
	CStr path = entry.path;
	CTcl_Interp::Unixfy(path);

	TclBrowserReset();

	int nItem = -1;
	while((nItem = UEventSendMessage(GetWidID(), EV_LIST_GETNEXTSEL, UMAKEINT(kUMainWidget, nItem), 0L)) != -1)
	{
		EntnodeData *data;
		UEventSendMessage(GetWidID(), EV_LIST_ROWGETDATA, UMAKEINT(kUMainWidget, nItem), &data);
		TclBrowserAppend(m_path, data);
	}

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

void UCvsFiles::EditSel(EntnodeData *data, bool useDefault)
{
	CStr fullpath;
	fullpath = m_path;
	if(!fullpath.endsWith(kPathDelimiter))
		fullpath << kPathDelimiter;
	fullpath << (*data)[EntnodeData::kName];
	if(data->GetType() == ENT_SUBDIR)
	{
		ResetView(fullpath, true);
	}
	else
	{
#ifdef WIN32
		char viewer[_MAX_PATH];
		HINSTANCE hInst = FindExecutable((*data)[EntnodeData::kName],
			m_path, viewer);
		bool done = false;
		if(!useDefault && (UINT)hInst >= 32)
		{
			HINSTANCE hInst = ShellExecute(*AfxGetMainWnd(), "open", (*data)[EntnodeData::kName],
				NULL, m_path, SW_SHOWDEFAULT);
			if((long)hInst < 32)
			{
				cvs_err("Unable to open '%s' (error %d), using default viewer instead\n", (char *)fullpath, GetLastError());
			}
			else
				done = true;
		} 

		if(!done)
		{
			const char * argv[3] = {0L, 0L, 0L};
			CStr program, file;
			if(strchr(gCvsPrefs.Viewer(), ' ') != 0L)
			{
				program << '\"';
				program << gCvsPrefs.Viewer();
				program << '\"';
			}
			else
				program = gCvsPrefs.Viewer();
			if(strchr(fullpath, ' ') != 0L)
			{
				file << '\"';
				file << fullpath;
				file << '\"';
			}
			else
				file = fullpath;
			argv[0] = program;
			argv[1] = file;
			int process = _spawnvp(_P_NOWAIT, gCvsPrefs.Viewer(), argv);
		}
#endif
#ifdef qUnix
		CStr fullpath;
		fullpath = m_path;
		if(!fullpath.endsWith(kPathDelimiter))
			fullpath << kPathDelimiter;
		fullpath << (*data)[EntnodeData::kName];

		CvsArgs args(false);
		args.add(gCvsPrefs.Viewer());
		args.add(fullpath);
		UCvsApp::gApp->Execute(args.Argc(), args.Argv());
#endif
	}
}

void UCvsFiles::OnDblClick(void)
{
	int nItem = -1;

	while((nItem = UEventSendMessage(GetWidID(), EV_LIST_GETNEXTSEL, UMAKEINT(kUMainWidget, nItem), 0L)) != -1)
	{
		EntnodeData *data;
		UEventSendMessage(GetWidID(), EV_LIST_ROWGETDATA, UMAKEINT(kUMainWidget, nItem), &data);
		EditSel(data);
	}
}

static int compareName(UWidget *wid, void *data1, void *data2)
{
	UCvsFiles *w = (UCvsFiles *)wid;
	EntnodeData *d1 = (EntnodeData *)data1;
	EntnodeData *d2 = (EntnodeData *)data2;
	int res = stricmp((*d1)[EntnodeData::kName], (*d2)[EntnodeData::kName]);
	return w->IsSortAscendant() ? res : -res;
}

static int compareStatus(UWidget *wid, void *data1, void *data2)
{
	UCvsFiles *w = (UCvsFiles *)wid;
	EntnodeData *d1 = (EntnodeData *)data1;
	EntnodeData *d2 = (EntnodeData *)data2;
	int res = stricmp((*d1)[EntnodeData::kStatus], (*d2)[EntnodeData::kStatus]);
	if(res == 0)
		res = stricmp((*d1)[EntnodeData::kName], (*d2)[EntnodeData::kName]);
	return w->IsSortAscendant() ? res : -res;
}

static int compareOption(UWidget *wid, void *data1, void *data2)
{
	UCvsFiles *w = (UCvsFiles *)wid;
	EntnodeData *d1 = (EntnodeData *)data1;
	EntnodeData *d2 = (EntnodeData *)data2;
	const char *s1 = (*d1)[EntnodeFile::kOption];
	const char *s2 = (*d2)[EntnodeFile::kOption];
	int res;
	if(s1 != 0L && s2 != 0L)
		res = strcmp(s1, s2);
	else
		res = (long)s1 < (long)s2 ? -1 : ((long)s1 > (long)s2 ? 1 : 0);
	if(res == 0)
		res = stricmp((*d1)[EntnodeData::kName], (*d2)[EntnodeData::kName]);
	return w->IsSortAscendant() ? res : -res;
}

static int revcmp(const char *rev1, const char *rev2)
{
	if(rev1 == 0L && rev2 == 0L)
		return 0;
	else if(rev1 == 0L || rev2 == 0L)
		return rev1 == 0L ? -1 : 1;

	CStr r1(rev1), r2(rev2);
	CStr q1, q2;
	char *tmp;
	int v1, v2;

	if((tmp = strchr(r1, '.')) != 0L)
	{
		tmp[0] = '\0';
		q1 = tmp + 1;
	}

	v1 = atoi(r1);

	if((tmp = strchr(r2, '.')) != 0L)
	{
		tmp[0] = '\0';
		q2 = tmp + 1;
	}
	v2 = atoi(r2);

	if(v1 == v2)
		return revcmp(q1.empty() ? (char *)0L : (const char *)q1, q2.empty() ? (char *)0L : (const char *)q2);

	return v1 < v2 ? -1 : 1;
}

static int compareRevs(UWidget *wid, void * data1, void * data2)
{
	UCvsFiles *w = (UCvsFiles *)wid;
	EntnodeData *d1 = (EntnodeData *)data1;
	EntnodeData *d2 = (EntnodeData *)data2;
	const char *s1 = (*d1)[EntnodeFile::kVN];
	const char *s2 = (*d2)[EntnodeFile::kVN];
	int res;
	if(s1 != 0L && s2 != 0L)
		res = revcmp(s1, s2);
	else
		res = (long)s1 < (long)s2 ? -1 : ((long)s1 > (long)s2 ? 1 : 0);
	if(res == 0)
		res = stricmp((*d1)[EntnodeData::kName], (*d2)[EntnodeData::kName]);
	return w->IsSortAscendant() ? res : -res;
}

static int compareTag(UWidget *wid, void * data1, void * data2)
{
	UCvsFiles *w = (UCvsFiles *)wid;
	EntnodeData *d1 = (EntnodeData *)data1;
	EntnodeData *d2 = (EntnodeData *)data2;
	const char *s1 = (*d1)[EntnodeFile::kTag];
	const char *s2 = (*d2)[EntnodeFile::kTag];
	int res;
	if(s1 != 0L && s2 != 0L)
		res = strcmp(s1, s2);
	else
		res = (long)s1 < (long)s2 ? -1 : ((long)s1 > (long)s2 ? 1 : 0);
	if(res == 0)
		res = stricmp((*d1)[EntnodeData::kName], (*d2)[EntnodeData::kName]);
	return w->IsSortAscendant() ? res : -res;
}

static int compareConflict(UWidget *wid, void * data1, void * data2)
{
	UCvsFiles *w = (UCvsFiles *)wid;
	EntnodeData *d1 = (EntnodeData *)data1;
	EntnodeData *d2 = (EntnodeData *)data2;
	const char *s1 = (*d1)[EntnodeFile::kConflict];
	const char *s2 = (*d2)[EntnodeFile::kConflict];
	int res;
	if(s1 != 0L && s2 != 0L)
		res = strcmp(s1, s2);
	else
		res = (long)s1 < (long)s2 ? -1 : ((long)s1 > (long)s2 ? 1 : 0);
	if(res == 0)
		res = stricmp((*d1)[EntnodeData::kName], (*d2)[EntnodeData::kName]);
	return w->IsSortAscendant() ? res : -res;
}

void UCvsFiles::Resort(void)
{
	UListSorter fsort = 0L;
	switch(m_sort)
	{
	case EntnodeData::kName:
		fsort = compareName;
		break;
	case EntnodeFile::kVN:
		fsort = compareRevs;
		break;
	case EntnodeData::kStatus:
		fsort = compareStatus;
		break;
	case EntnodeFile::kOption:
		fsort = compareOption;
		break;
	case EntnodeFile::kTag:
		fsort = compareTag;
		break;
	case EntnodeFile::kConflict:
		fsort = compareConflict;
		break;
	}
	if(fsort != 0L)
		UEventSendMessage(GetWidID(), EV_LIST_RESORT, UMAKEINT(kUMainWidget, m_sort), (void *)fsort);
}

void UCvsFiles::OnSelColumn(int column)
{
	if(column == m_sort)
		m_ascendant = !m_ascendant;
	else
	{
		m_ascendant = true;
		switch(column)
		{
		case EntnodeData::kName:
		case EntnodeData::kStatus:
		case EntnodeFile::kOption:
		case EntnodeFile::kTag:
		case EntnodeFile::kConflict:
		case EntnodeFile::kVN:
			m_sort = column;
			break;
		}
	}

	Resort();
}
