/******************************************************************************
 SyGFileTreeTable.cc

	Displays tabbed indeces of JXCardFile

	BASE CLASS = JXTable

 ******************************************************************************/

#include "SyGFileTreeTable.h"
#include "SyGFileTreeUpdateTask.h"
#include "SyGHeaderWidget.h"
#include "JTreeList.h"
#include "JNamedTreeList.h"
#include "SyGFileTreeList.h"
#include "SyGDNDCursor.h"
#include "SyGFileTree.h"
#include "SyGApplication.h"
#include "SyGSet.h"
#include "SyGSelectNameTask.h"
#include "SyGPrefsMgr.h"
#include "SyGRemoteErrUpdateTask.h"
#include "SyGRunCmdDialog.h"
#include "SyGViewManPageDialog.h"
#include "SyGChooseSaveFile.h"

#include "SyGDirUtil.h"
#include "SyGGlobals.h"

#include "selectedfolderIcon.xpm"
#include "selectedopenfolderIcon.xpm"
#include "selectedfileIcon.xpm"
#include "selectedmacicon.xpm"
#include "user.xpm"
#include "group.xpm"
#include "size.xpm"
#include "mode.xpm"
#include "month.xpm"
#include "hidden.xpm"
#include "filter.xpm"

#include <JXSelectionManager.h>
#include <JXDNDManager.h>
#include <JXTextMenu.h>
#include <JXMenuBar.h>
#include <JXApplication.h>
#include <JXWindowPainter.h>
#include <JXImage.h>
#include <JXDisplay.h>
#include <JXWindow.h>
#include <JXWindowDirector.h>
#include <jXCSFIcons.h>
#include <JXDragPainter.h>
#include <JXKeyModifiers.h>
#include <JXInputField.h>
#include <JXToolBar.h>
#include <JXColormap.h>

#include <JColormap.h>
#include "JTableSelection.h"
#include "JTableSelectionIterator.h"
#include <JUNIXDirEntry.h>
#include <JStdError.h>
#include <JCreateProgressDisplay.h>
#include <JPainter.h>
#include <JUserNotification.h>
#include <JChooseSaveFile.h>
#include <JFontManager.h>
#include <JString.h>
#include <JRect.h>
#include <JFontStyle.h>
#include <JSimpleProcess.h>

#include <jXEventUtil.h>
#include <jXGlobals.h>
#include <jXUtil.h>

#include <jProcessUtil.h>
#include <jStrStreamUtil.h>
#include <jStreamUtil.h>
#include <jDirUtil.h>
#include <jFileUtil.h>
#include <jTime.h>
#include <jASCIIConstants.h>
#include <jMath.h>

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <iostream.h>
#include <iomanip.h>
#include <strstream.h>
#include <fstream.h>
#include <jAssert.h>

static const JCharacter* kTreeMenuTitleStr = "View";
static const JCharacter* kTreeMenuStr = 
//	"Select All %k Meta-A %i \"SelectAll::SyG\""
	" Create Link %i \"CreateLink::SyG\""
	"| Find Original %i \"FindOriginal::SyG\""
	"%l|Show filter %b %i \"ShowFilter::SyG\""
	"%l|Show hidden files %b %i \"ShowHidden::SyG\""
	"%l| Show user name %b %i \"ShowUser::SyG\""
	"| Show group name %b %i \"ShowGroup::SyG\""
	"| Show size %b %i \"ShowSize::SyG\""
	"| Show permissions %b %i \"ShowMode::SyG\""
	"| Show date %b %i \"ShowDate::SyG\""
	"%l| Show all attributes %i \"ShowAllAttr::SyG\""
	"| Hide all attributes %i \"HideAllAttr::SyG\"";


enum
{
	kCreateLink = 1,
	kFindOriginalCmd,
	kShowFilterCmd,
	kShowHiddenCmd,
	kShowUserCmd,
	kShowGroupCmd,
	kShowSizeCmd,
	kShowModeCmd,
	kShowDateCmd,
	kShowAllAttr,
	kHideAllAttr
};

// Shortcuts menu

static const JCharacter* kShortcutMenuTitleStr = "Shortcuts";
static const JCharacter* kShortcutMenuStr = 
	"Add Shortcut %k Meta-plus %i \"AddShortcut::SyG\""
	"| Remove Shortcut %k Meta-minus  %i \"RemoveShortcut::SyG\""
	"| Remove All Shortcuts %l %i \"RemoveAllShortcuts::SyG\"";
	
enum
{
	kAddShortcutCmd = 1,
	kRemoveShortcutCmd,
	kRemoveAllShortcutsCmd
};
	
// Selection Atoms

static const JCharacter* kMoveFilesXAtomName = "FM::MoveFiles";
static const JCharacter* kCopyFilesXAtomName = "FM::CopyFiles";
static const JCharacter* kFilesXAtomName = "JXURLs";

// Fonts

static const JCharacter* kDefaultName = JGetDefaultFontName();
const JSize kDefaultSize = kJDefaultFontSize;

// Font menu

static const JCharacter* kFontMenuTitleStr = "Font";
static const JCharacter* kFontRegex        = "^[0-9]x[0-9]*$";
static const JCharacter* kDefaultFontName  = "6x13";

// Font size menu (Courier is used if no other fonts are available.)

static const JCharacter* kSizeMenuTitleStr = "Size";

// Menus


const JCharacter* SyGFileTreeTable::kGXFMColInserted = 
	"SyGFileTreeTable::GXFMColInserted";
const JCharacter* SyGFileTreeTable::kGXFMColRemoved = 
	"SyGFileTreeTable::GXFMColRemoved";


const JCharacter* col1Name = "";
const JCharacter* col2Name = "Name";

const JCoordinate kDragBeginBuffer 	= 5;

// These are the number of columns up to and including the file name
const JIndex kLeadingCols			= 2;
const JCharacter kPermTestChar		= 'w';

static const JCharacter* kDNDActionCopyDescrip  	= "copy the items";
static const JCharacter* kDNDActionMoveDescrip  	= "move the items";

const JFileVersion kCurrentFileVersion	= 1;

/******************************************************************************
 Constructor

 ******************************************************************************/

SyGFileTreeTable*
SyGFileTreeTable::Create
	(	
	JXMenuBar* 			menuBar,
	SyGFileTree* 		fileTree,
	SyGFileTreeList* 	treeList,
	SyGSet* 			set,
	JXScrollbarSet* 	scrollbarSet,
	JXContainer*		enclosure,
	const HSizingOption	hSizing,
	const VSizingOption	vSizing,
	const JCoordinate	x,
	const JCoordinate	y,
	const JCoordinate	w,
	const JCoordinate	h
	)
{
	SyGFileTreeTable* widget =
		new SyGFileTreeTable(menuBar, fileTree, treeList, set, 
			scrollbarSet, enclosure, hSizing,vSizing, x,y, w,h);
	assert(widget != NULL);
	widget->JXTreeListWidgetX();
	return widget;
}

SyGFileTreeTable::SyGFileTreeTable
	(	
	JXMenuBar* 			menuBar,
	SyGFileTree* 		fileTree,
	SyGFileTreeList* 	treeList,
	SyGSet* 			set,
	JXScrollbarSet* 	scrollbarSet,
	JXContainer*		enclosure,
	const HSizingOption	hSizing,
	const VSizingOption	vSizing,
	const JCoordinate	x,
	const JCoordinate	y,
	const JCoordinate	w,
	const JCoordinate	h
	)
	:
	JXNamedTreeListWidget(treeList, scrollbarSet, enclosure, 
		hSizing,vSizing, x,y, w,h)
{
	itsRunDialog			= NULL;
	itsDNDBuffer			= NULL;
	itsDraggingRect			= kFalse;

	itsEditMenu = 
		GetEditMenuHandler()->AppendEditMenu(menuBar, JXMenu::kMacintoshStyle,
											 kFalse, kFalse, kFalse, kFalse);

	ListenTo(itsEditMenu);

//	itsTreeMenu = menuBar->InsertTextMenu(2, kTreeMenuTitleStr);
	itsTreeMenu = menuBar->AppendTextMenu(kTreeMenuTitleStr);
	itsTreeMenu->SetMenuItems(kTreeMenuStr);
//	itsTreeMenu->SetUpdateAction(JXMenu::kDisableNone);
	ListenTo(itsTreeMenu);

	itsTreeMenu->SetItemImage(kShowFilterCmd,	filter);
	itsTreeMenu->SetItemImage(kShowHiddenCmd, 	hidden);
	itsTreeMenu->SetItemImage(kShowUserCmd, 	user);
	itsTreeMenu->SetItemImage(kShowGroupCmd, 	group);
	itsTreeMenu->SetItemImage(kShowSizeCmd, 	size);
	itsTreeMenu->SetItemImage(kShowModeCmd, 	mode);
	itsTreeMenu->SetItemImage(kShowDateCmd, 	month);
	

//	itsShortcutMenu = menuBar->InsertTextMenu(3, kShortcutMenuTitleStr);
	itsShortcutMenu = menuBar->AppendTextMenu(kShortcutMenuTitleStr);
	assert (itsShortcutMenu != NULL);
	itsShortcutMenu->SetMenuItems(kShortcutMenuStr);
	itsShortcutMenu->SetUpdateAction(JXMenu::kDisableNone);
	ListenTo(itsShortcutMenu);
	
	itsFileTreeList = treeList;
	itsFileTree 	= fileTree;
	
	itsExecutableIcon = new JXImage(GetDisplay(), GetColormap(), JXGetExecIcon());
	assert( itsExecutableIcon != NULL );
	itsExecutableIcon->ConvertToRemoteStorage();
	
	itsSelectedBranchIcon = new JXImage(GetDisplay(), GetColormap(), 
					JXPM(selectedfolderIcon_xpm));
	assert( itsSelectedBranchIcon != NULL);
	itsSelectedBranchIcon->ConvertToRemoteStorage();

	itsBranchIcon = new JXImage(GetDisplay(), GetColormap(), 
					JXPM(JXGetFolderIcon()));
	assert( itsBranchIcon != NULL);
	itsBranchIcon->ConvertToRemoteStorage();
	
	itsOpenSelectedBranchIcon = new JXImage(GetDisplay(), GetColormap(), 
					JXPM(selectedopenfolderIcon_xpm));
	assert( itsOpenSelectedBranchIcon != NULL);
	itsOpenSelectedBranchIcon->ConvertToRemoteStorage();
	
	itsSelectedFileIcon = new JXImage(GetDisplay(), GetColormap(), 
					JXPM(selectedfileIcon_xpm));
	assert( itsSelectedFileIcon != NULL);
	itsSelectedFileIcon->ConvertToRemoteStorage();

	itsFileIcon = new JXImage(GetDisplay(), GetColormap(), 
					JXPM(JXGetPlainFileIcon()));
	assert( itsFileIcon != NULL);
	itsFileIcon->ConvertToRemoteStorage();
	
	itsSelectedExecIcon = new JXImage(GetDisplay(), GetColormap(), 
					JXPM(selectedmacicon_xpm));
	assert( itsSelectedExecIcon != NULL);
	itsSelectedExecIcon->ConvertToRemoteStorage();
		
	currentDndHereIndex = 0;

	JXSelectionManager* selMgr 	= GetSelectionManager();
	const Atom dndName 			= (GetDNDManager())->GetDNDSelectionName();
	AddSelectionTarget(dndName, selMgr->GetURLXAtom());
	
	itsFileUpdateTask = new SyGFileTreeUpdateTask(this);
	assert(itsFileUpdateTask != NULL);

	if (!GetWindow()->IsIconified())
		{
		JXGetApplication()->InstallIdleTask(itsFileUpdateTask);
		}	
	
	itsLongDisplay = kTrue;

	itsVisibleColsMask = 0;

	for (int i=0; i<5; i++)
		{
		itsVisibleCols[i] = kFalse;
		}

	itsDownPt = JPoint(0,0);
		
	ListenTo(SyGGetApplication());
	
	itsDownInCell = kFalse;
	
	if (!(GetDisplay())->GetCursor(kSyGDNDCursorName, &itsDNDCursor))
		{
		itsDNDCursor =
			(GetDisplay())->CreateCustomCursor(kSyGDNDCursorName, kSyGDNDCursor);
		}

	if (!(GetDisplay())->GetCursor(kSyGDNDCopyCursorName, &itsDNDCopyCursor))
		{
		itsDNDCopyCursor =
			(GetDisplay())->CreateCustomCursor(kSyGDNDCopyCursorName, kSyGDNDCopyCursor);
		}

	if (!(GetDisplay())->GetCursor(kSyGDNDAskCursorName, &itsDNDAskCursor))
		{
		itsDNDAskCursor =
			(GetDisplay())->CreateCustomCursor(kSyGDNDAskCursorName, kSyGDNDAskCursor);
		}
	itsSet = set;

	ShouldHilightTextOnly(kTrue);
	WantInput(kTrue);

	JXKeyModifiers mods;
	mods.SetState(kJXMod1KeyIndex, kTrue);
	GetWindow()->InstallShortcut(this, kJUpArrow, mods);
	GetWindow()->InstallShortcut(this, kJDownArrow, mods);

	mods.SetState(kJXShiftKeyIndex, kTrue);
	GetWindow()->InstallShortcut(this, kJUpArrow, mods);
	GetWindow()->InstallShortcut(this, kJDownArrow, mods);

	itsCurrentColType = kGFMNone;
}

/******************************************************************************
 Destructor

 ******************************************************************************/

SyGFileTreeTable::~SyGFileTreeTable()
{
	delete itsFileUpdateTask;
	delete itsExecutableIcon;
	delete itsSelectedBranchIcon;
	delete itsBranchIcon;
	delete itsOpenSelectedBranchIcon;
	delete itsSelectedFileIcon;
	delete itsFileIcon;
	delete itsSelectedExecIcon;
	delete itsFileTree;
	delete itsDNDBuffer;
}


/******************************************************************************
 TableDrawCell (virtual protected)

 ******************************************************************************/

void
SyGFileTreeTable::TableDrawCell
	(
	JPainter& p,
	const JPoint&	cell,
	const JRect&	rect
	)
{
	JRect sRect = rect;
	const JCoordinate theBuffer = GetFMBufferWidth(itsVisibleCols, 2, cell.x);
	if (theBuffer > 0)
		{
		sRect.left += theBuffer;
		}
		
	else
		{
		sRect.right += theBuffer;
		}
	
	if (cell.x < 3)
		{
		JXNamedTreeListWidget::TableDrawCell(p,cell,rect);
		return;
		}

	JString str;
	GFMColType type = GetFMColType(itsVisibleCols, kLeadingCols, cell.x);

	GetCellString(cell, &str);
	if (type == kGFMMode)
		{
		JSize w = GetFontManager()->GetCharWidth( JGetDefaultFontName(),
						kJDefaultFontSize, JFontStyle(), kPermTestChar);
		assert(str.GetLength() >= 9);
		for (JSize i = 1; i <= 9; i++)
			{
			JRect pRect		= rect;
			pRect.left 		= rect.left + (w * (i - 1));
			pRect.right		= pRect.left + w;
			JString pStr	= str.GetSubstring(i,i);
			p.String(pRect, pStr, JPainter::kHAlignCenter, JPainter::kVAlignCenter);
			}
		return;
		}
	
	p.SetFont(JGetDefaultFontName(), kJDefaultFontSize,
		JFontStyle(kFalse, kFalse, 0, kFalse, JGetCurrColormap()->GetBlackColor()));
		
	if (type == kGFMSize)
		{
		p.String(sRect, str, JPainter::kHAlignRight, JPainter::kVAlignCenter);
		}
		
	else 
		{
		p.String(sRect, str, JPainter::kHAlignLeft, JPainter::kVAlignCenter);
		}
}

/******************************************************************************
 GetIcon

******************************************************************************/

JBoolean
SyGFileTreeTable::GetImage
	(
	const JIndex 	index,
	const JXImage** image
	)
	const
{
	const JTreeList* treeList = GetTreeList();
	SyGFileTreeNode* entry = dynamic_cast(SyGFileTreeNode*, treeList->GetNode(index));
	assert (entry != NULL);
	const JTableSelection& s 	= GetTableSelection();
	JBoolean selected			= s.IsSelected(index, 2);
	
	if (entry->IsOpenable())
		{
		if ((selected) || (currentDndHereIndex == index))
			{
			*image = itsSelectedBranchIcon;
			return kTrue;
			}
		*image = itsBranchIcon;
		return kTrue;
		}
	
	else if (entry->IsExecutable() &&
	   (entry->GetType() == JUNIXDirEntry::kFile ||
		entry->GetType() == JUNIXDirEntry::kFileLink) )
		{
		if (selected)
			{
			*image = itsSelectedExecIcon;
			return kTrue;
			}
		else
			{
			*image = itsExecutableIcon;
			return kTrue;
			}
		}
		
	if (selected)
		{
		*image =  itsSelectedFileIcon;
		return kTrue;
		}
	*image = itsFileIcon;
	return kTrue;
}

/******************************************************************************
 HandleShortcut

 ******************************************************************************/

void  
SyGFileTreeTable::HandleShortcut
	(
	const int key,
	const JXKeyModifiers&   modifiers
	)
{
	if (GetWindow()->SwitchFocusToWidget(this))
		{
		if (key == kJUpArrow)
			{
			JString cwd = GetCurrentPath();
			if (cwd != "/")
				{
				if (cwd.GetLastCharacter() == '/')
					{
					cwd.RemoveSubstring(cwd.GetLength(), cwd.GetLength());
					}
				JString path;
				JString name;
				JSplitPathAndName(cwd, &path, &name);
				if (modifiers.shift())
					{
					SyGGetApplication()->NewChildWindow(path);
					}
				else
					{
					JTreeNode* jnode = itsFileTree->GetRoot();
					SyGFileTreeNode* base = dynamic_cast(SyGFileTreeNode*, jnode);
					assert(base != NULL);
					JError err = base->GoTo(path);
					assert(err.OK());
					itsTextBuffer.Clear();
					}
				}
			}

		else if (key == kJDownArrow)
			{
			JTableSelection& selection = GetTableSelection();
			JTableSelectionIterator* iter = 
				new JTableSelectionIterator(&selection);
			assert(iter != NULL);

			JPoint cell;
			if (iter->Next(&cell))
				{
				SyGFileTreeNode* node = 
					dynamic_cast(SyGFileTreeNode*, itsFileTreeList->GetNode(cell.y));
				assert (node != NULL);
				
				if (node->IsOpenable())
					{
					if (modifiers.shift())
						{
						SyGGetApplication()->NewChildWindow(node->GetFullPathAndName());
						}
					else
						{
						JTreeNode* jnode = itsFileTree->GetRoot();
						SyGFileTreeNode* base = dynamic_cast(SyGFileTreeNode*, jnode);
						assert(base != NULL);
						JError err = base->GoTo(node->GetFullPathAndName());
						assert(err.OK());
						itsTextBuffer.Clear();
						}
					}
				}
			delete iter;
			}
		}
	JXNamedTreeListWidget::HandleShortcut(key, modifiers);
}

/******************************************************************************
 HandleKeyPress

 ******************************************************************************/

void  
SyGFileTreeTable::HandleKeyPress
	(
	const int key,
	const JXKeyModifiers&   modifiers
	)
{
	if (key == kJDeleteKey || key == kJForwardDeleteKey)
		{
		if (SyGGetPrefsMgr()->BSIsTrashing())
			{
			MoveSelectedToTrash();
			}
		else
			{
			DeleteSelected();
			}
		ClearIncrementalSearchBuffer();
		}

//	else if (key == ' ')
//		{
//		itsTextBuffer.Clear();
//		GetTableSelection().ClearSelection();
//		Redraw();
//		}

	else if (key == kJReturnKey)
		{
		JTableSelection& s = GetTableSelection();
		if (IsEditing())
			{
			EndEditing();
			}
		else if (s.GetSelectedCellCount() == 1)
			{
			JPoint cell;
			JBoolean ok = s.GetFirstSelectedCell(&cell);
			assert(ok);
			BeginEditing(cell);
			}
		ClearIncrementalSearchBuffer();
		}
		
	else if (key == kJUpArrow)
		{
		ClearIncrementalSearchBuffer();

		JTableSelection& s = GetTableSelection();
		JTableSelectionIterator iter(&s);

		JPoint cell;
		if (iter.Next(&cell))
			{
			if (cell.y > 1)
				{
				cell.y--;
				s.ClearSelection();
				s.SelectCell(cell.y, cell.x);
				TableScrollToCell(cell);
				}
			}
		else if (GetRowCount() > 0)
			{
			cell.x = kLeadingCols;
			cell.y = GetRowCount();
			s.SelectCell(cell);
			TableScrollToCell(cell);
			}
		}

	else if (key == kJDownArrow)
		{
		ClearIncrementalSearchBuffer();

		JTableSelection& s = GetTableSelection();
		JTableSelectionIterator iter(&s);

		JPoint cell;
		if (iter.Next(&cell))
			{
			if (cell.y < (JCoordinate)GetRowCount())
				{
				cell.y++;
				s.ClearSelection();
				s.SelectCell(cell.y, cell.x);
				TableScrollToCell(cell);
				}
			}
		else if (GetRowCount() > 0)
			{
			cell.x = kLeadingCols;
			cell.y = 1;
			s.SelectCell(cell);
			TableScrollToCell(cell);
			}
		}
		
	else
		{
		if (key == kJEscapeKey && !IsEditing())
			{
			ClearIncrementalSearchBuffer();
			}
		JXNamedTreeListWidget::HandleKeyPress(key, modifiers);
		}

}

/******************************************************************************
 Draw

 ******************************************************************************/

void
SyGFileTreeTable::Draw
	(
	JXWindowPainter& p, 
	const JRect& rect
	)
{
	if (itsDraggingRect)
		{
		itsUpdateWhileDragging = kTrue;
		}
	JXNamedTreeListWidget::Draw(p,rect);
}

/******************************************************************************
 HandleMouseDown

 ******************************************************************************/

void
SyGFileTreeTable::HandleMouseDown
	(
	const JPoint&			pt,
	const JXMouseButton 	button,
	const JSize 			clickCount,
	const JXButtonStates&	buttonStates,
	const JXKeyModifiers& 	modifiers
	)
{
	itsDownPt 				= pt;
	itsTextBuffer.Clear();
	itsDraggingRect 		= kFalse;
	itsUpdateWhileDragging 	= kFalse;
	itsWaitingToEdit		= kFalse;
	itsDownInCell 			= kFalse;
	itsClearIfNotDND		= kFalse;
	JBoolean selectAfter	= kFalse;
	JTableSelection& s 		= GetTableSelection();

	ClearIncrementalSearchBuffer();
	
	JXNamedTreeListWidget::HandleMouseDown(pt, button, clickCount, buttonStates, modifiers);
	if (button > kJXRightButton)
		{
		return;
		}

	JPoint cell;
	JBoolean inCell = GetCell(pt, &cell);

	JXNamedTreeListWidget::NodePart part;
	GetNode(pt, &cell, &part);

	JBoolean inNode = kFalse;
	if ((part == JXNamedTreeListWidget::kInImage) || 
		(part == JXNamedTreeListWidget::kInText))
		{
		inNode = kTrue;
		}

	if ((part == JXNamedTreeListWidget::kInImage) &&
		(clickCount == 2))
		{
		JTreeNode* jnode 		= itsFileTree->GetRoot();
		SyGFileTreeNode* base 	= dynamic_cast(SyGFileTreeNode*, jnode);
		assert(base != NULL);
		JString path 			= base->GetFullPathAndName();
		DoubleClickIcon(cell.y, modifiers);
		if (path != base->GetFullPathAndName())
			{
			return;
			}
		if (modifiers.control())
			{
			selectAfter = kTrue;
			}
		}

	JBoolean inName = kFalse;
	if (part == JXNamedTreeListWidget::kInText)
		{
		inName = kTrue;
		}

	if (((cell.x <= 2) || !inCell) && (!inCell || !inNode) && !IsDraggingToggle())
		{
		JPainter* p = CreateDragInsidePainter();
		p->Rect(JRect(pt, pt));
		itsStartPt = itsPrevPt = pt;
		itsDraggingRect = kTrue;
		}

	if (!inCell)
		{
		itsDownInCell = kFalse;
		return;
		}

	GFMColType type = GetFMColType(itsVisibleCols, kLeadingCols, cell.x);
	if (cell.x == 2)
		{
		if (inName && (!modifiers.shift()) &&
			(!modifiers.meta()) && (!modifiers.control()) &&
			(button == kJXLeftButton))
			{
			if (!s.IsSelected(cell.y, 2))
				{
				s.ClearSelection();
				}
			s.SelectCell(cell.y, 2);
			itsWaitingToEdit 	= kTrue;
			}
		if (inCell && inNode)
			{
			itsDownInCell = kTrue;
			}
		if (inNode && (!itsWaitingToEdit))
			{
			if (modifiers.shift() || modifiers.control() || (button == kJXRightButton))
				{
				s.InvertCell(cell.y, 2);
				}
			else
				{
				if (s.IsSelected(cell.y, 2))
					{
					itsClearIfNotDND = kTrue;
					}
				else
					{
					s.ClearSelection();
					}
				s.SelectCell(cell.y, 2);
				}
			}
		else if (!inNode && !modifiers.shift() && !s.IsSelected(cell))
			{
			s.ClearSelection();
			}
		
		}
	else if (type == kGFMMode)
		{
		if (!s.IsSelected(cell.y, 2))
			{
			s.ClearSelection();
			}
		JSize w = GetFontManager()->GetCharWidth( JGetDefaultFontName(),
					kJDefaultFontSize, JFontStyle(), kPermTestChar);

		JRect cellRect 	= GetCellRect(cell);
		JIndex subIndex	= (pt.x - cellRect.left)/w + 1;

		mode_t posMask = 1 << (9 - subIndex);
		mode_t permsMask = 511;
		
		const JTreeList* treeList = GetTreeList();
		SyGFileTreeNode* node = 
			dynamic_cast(SyGFileTreeNode*, treeList->GetNode(cell.y));
		assert (node != NULL);

 		JUNIXDirEntry* dirEntry = node->GetDirEntry();
		assert (dirEntry != NULL);	
		
		mode_t theMode= dirEntry->GetMode();
		theMode = theMode & permsMask;
		
		JBoolean usePos;
		 
		if ((theMode & posMask) == posMask)
			{
			usePos = kFalse;
			}
			
		else
			{
			usePos = kTrue;
			}

		JString permsStr;
		if (subIndex < 4)
			{
			permsStr += "u";
			}
		else if (subIndex < 7)
			{
			permsStr += "g";
			}
		else
			{
			permsStr += "o";
			}
			
		if (usePos)
			{
			permsStr += "+";
			}
		else
			{
			permsStr += "-";
			}

		if ((subIndex == 1) || (subIndex == 4) || (subIndex == 7))
			{
			permsStr += "r";
			}
		else if ((subIndex == 2) || (subIndex == 5) || (subIndex == 8))
			{
			permsStr += "w";
			}
		else
			{
			permsStr += "x";
			}

			
		JBoolean found = kFalse;
			
		JTableSelectionIterator* iter = 
		new JTableSelectionIterator(&(GetTableSelection()));
		JPoint testCell;
		
		while(iter->Next(&testCell))
			{
			if (testCell.y == cell.y)
				{
				found = kTrue;
				}
				
			node = 
				dynamic_cast(SyGFileTreeNode*, treeList->GetNode(testCell.y));
			assert (node != NULL);

			JString sysCmd = "chmod ";
			if (modifiers.meta())
				{
				sysCmd = "chmod -R ";
				}
			sysCmd += permsStr;
			sysCmd += " \"";
			sysCmd += node->GetFullPathAndName();
			sysCmd += "\"";
			SystemCall(sysCmd);
			node->UpdateDir(kTrue);
			TableRefresh();
			}
			
		delete iter;
		
		if (!found)
			{
			node = 
				dynamic_cast(SyGFileTreeNode*, treeList->GetNode(cell.y));
			assert (node != NULL);
			
			JString sysCmd = "chmod ";
			if (modifiers.meta())
				{
				sysCmd = "chmod -R ";
				}
			sysCmd += permsStr;
			sysCmd += " \"";
			sysCmd += node->GetFullPathAndName();
			sysCmd += "\"";
			SystemCall(sysCmd);
			node->UpdateDir(kTrue);
			TableRefresh();
			}
		UpdateDisplay();
		}

	if (selectAfter)
		{
		s.SelectCell(cell.y, 2);
		}
}

/******************************************************************************
 HandleMouseDrag (virtual protected)

 ******************************************************************************/

void
SyGFileTreeTable::HandleMouseDrag
	(
	const JPoint&			pt,
	const JXButtonStates&	buttonStates,
	const JXKeyModifiers&	modifiers
	)
{
	const JBoolean scrolled = ScrollForDrag(pt);
	JPainter* p 			= GetDragPainter();
	
	
	if (p != NULL)
		{
		if (pt != itsPrevPt)
			{
			if (!scrolled)
				{
				p->Rect(JRect(itsStartPt, itsPrevPt));
				}
			p->Rect(JRect(itsStartPt, pt));
			itsPrevPt = pt;
			}
		}
	else if (itsDownInCell)
		{
		JPoint p = itsDownPt - pt;
		if (sqrt(p.x*p.x + p.y*p.y) > kDragBeginBuffer)
			{
			itsWaitingToEdit = kFalse;
			itsClearIfNotDND = kFalse;
			BeginDND(pt, buttonStates, modifiers);
			}
		}
	else 
		{
		JXNamedTreeListWidget::HandleMouseDrag(pt,buttonStates,modifiers);
		}
	
}

/******************************************************************************
 HandleMouseUp

 ******************************************************************************/

void
SyGFileTreeTable::HandleMouseUp
	(
	const JPoint&		pt,
	const JXMouseButton button,
	const JXButtonStates& buttonStates,
	const JXKeyModifiers& modifiers
	)
{
	JPainter* p = GetDragPainter();
	if (p != NULL)
		{
		JTableSelection& selection = GetTableSelection();
		p->Rect(JRect(itsStartPt, itsPrevPt));
		if (!modifiers.shift())
			{
			selection.ClearSelection();
			}
		JPoint stCell;
		JPoint prCell;
		JIndex start;
		JIndex end;
		JBoolean stOK = kFalse;
		JBoolean prOK = kFalse;
		if (GetCell(itsStartPt, &stCell))
			{
			stOK = kTrue;
			}
		if (GetCell(itsPrevPt, &prCell))
			{
			prOK = kTrue;
			}
		if (itsStartPt.y < itsPrevPt.y)
			{
			if (stOK)
				{
				start = stCell.y;
				}
			if (prOK)
				{
				end = prCell.y;
				}
			else
				{
				end = GetRowCount();
				}
			}
		else
			{
			if (prOK)
				{
				start = prCell.y;
				}
			if (stOK)
				{
				end = stCell.y;
				}
			else
				{
				end = GetRowCount();
				}
			}
		if (stOK || prOK)
			{
			for (JSize i = start; i <= end; i++)
				{
				JPoint cell(GetNodeColIndex(), i);
				JRect rect = GetCellRect(cell);
				JSize indent = GetNodeIndent(i);
				JPoint sPt(itsStartPt.x, rect.ycenter());
				JPoint pPt(itsPrevPt.x, rect.ycenter());
				if (PointInNode(i, sPt) || PointInNode(i, pPt))
					{
					if (modifiers.shift())
						{
						if (selection.IsSelected(i,2))
							{
							selection.SelectCell(i, 2, kFalse);
							}
						else
							{
							selection.SelectCell(i, 2);
							}
						}
					else
						{
						selection.SelectCell(i, 2);
						}
					}
				JCoordinate stOffset = TableToTreeCell(i, itsStartPt.x);
				JCoordinate prOffset = TableToTreeCell(i, itsPrevPt.x);
				if (((stOffset < 0) && (prOffset > 0)) ||
					((stOffset > 0) && (prOffset < 0)))
					{
					if (modifiers.shift())
						{
						if (selection.IsSelected(i,2))
							{
							selection.SelectCell(i, 2, kFalse);
							}
						else
							{
							selection.SelectCell(i, 2);
							}
						}
					else
						{
						selection.SelectCell(i, 2);
						}
					}
				}
			}
		DeleteDragPainter();
		Refresh();
		}
	else if (itsClearIfNotDND)
		{
		JPoint cell;
		if (GetCell(pt, &cell))
			{
			JTableSelection& s = GetTableSelection();
			s.ClearSelection();
			s.SelectCell(cell.y, 2);
			}
		}
	else if (itsWaitingToEdit)
		{
		JXNamedTreeListWidget::HandleMouseUp(pt, button, buttonStates, modifiers);
		JPoint cell;
		if (GetCell(pt, &cell))
			{
			JTreeNode* jnode 		= GetTreeList()->GetNode(cell.y);
			JNamedTreeNode* node 	= dynamic_cast(JNamedTreeNode*, jnode);
			assert(node != NULL);
			GetTableSelection().ClearSelection();
			GetTableSelection().SelectCell(cell);
			BeginEditing(cell);
			TableScrollToCell(JPoint(1, cell.y));
			}
		}
	else
		{
		JXNamedTreeListWidget::HandleMouseUp(pt, button, buttonStates, modifiers);
		}
	itsDraggingRect 	= kFalse;
	itsWaitingToEdit	= kFalse;
	itsClearIfNotDND	= kFalse;
}

/******************************************************************************
 HandleDNDHere 

 ******************************************************************************/

void
SyGFileTreeTable::HandleDNDHere
	(
	const JPoint& 	pt,
	const JXWidget* source
	)
{
	ScrollForDrag(pt);

	JIndex oldIndex = currentDndHereIndex;
	JPoint cell;
	if (!GetCell(pt, &cell))
		{
		currentDndHereIndex = 0;
		if (oldIndex != 0)
			{
			TableRefreshCell(oldIndex, 2);
			}
		return;
		}

	currentDndHereIndex = GetNearestDirIndex(cell.y);

	if (oldIndex != 0)
		{
		TableRefreshCell(oldIndex, 2);
		}

	if (currentDndHereIndex != 0)
		{
		TableRefreshCell(currentDndHereIndex, 2);
		}
}

/******************************************************************************
 HandleDNDLeave 

 ******************************************************************************/

void
SyGFileTreeTable::HandleDNDLeave()
{
	if (currentDndHereIndex != 0)
		{
		TableRefreshCell(currentDndHereIndex, 1);
		}
		
	currentDndHereIndex = 0;
}

/******************************************************************************
 HandleDNDDrop 

 ******************************************************************************/

void
SyGFileTreeTable::HandleDNDDrop
	(
	const JPoint& 		pt,
	const JArray<Atom>& typeList,
	const Atom 			action,
	const Time			time,
	const JXWidget* 	source
	)
{	
	Atom realAction 		= action;
	JXDNDManager* dndMgr  	= GetDNDManager();
	Window dragWindow		= dndMgr->GetDraggerWindow();
	const Atom dndName 		= dndMgr->GetDNDSelectionName();
	JXSelectionManager* selManager = GetSelectionManager();

	const Atom xdsXAtom 	= dndMgr->GetDNDDirectSave0XAtom();
	JBoolean isXDS			= kFalse;
	const JSize typeCount 	= typeList.GetElementCount();
	for (JSize i = 1; i <= typeCount; i++)
		{
		if (typeList.GetElement(i) == xdsXAtom)
			{
			isXDS			= kTrue;
			break;
			}
		}
	if (isXDS)
		{
		Atom actualType;
		int actualFormat;
		unsigned long itemCount, remainingBytes;
		unsigned char* rawData = NULL;
		XGetWindowProperty(*(GetDisplay()), dragWindow,
						   dndMgr->GetDNDDirectSave0XAtom(),
						   0, LONG_MAX, True, selManager->GetMimePlainTextXAtom(),
						   &actualType, &actualFormat,
						   &itemCount, &remainingBytes, &rawData);

		if (actualType == selManager->GetMimePlainTextXAtom() &&
			actualFormat == 8 && itemCount > 0)
			{
			JTreeNode* jnode;
			if (currentDndHereIndex == 0)
				{
				jnode = itsFileTree->GetRoot();
				}
			else
				{
				JTreeList* treeList = GetTreeList();
				jnode = treeList->GetNode(currentDndHereIndex);
				}
			SyGFileTreeNode* node = 
				dynamic_cast(SyGFileTreeNode*, jnode);
			assert (node != NULL);
			JString dest 		= node->GetFullPathAndName();
			JAppendDirSeparator(&dest);
			JString path 		= dest + (char*) rawData;
			JString url 		= JXFileNameToURL(path);
			XChangeProperty(*(GetDisplay()), dragWindow,
							dndMgr->GetDNDDirectSave0XAtom(),
							(GetSelectionManager())->GetMimePlainTextXAtom(), 8,
							PropModeReplace,
							(unsigned char*) (url.GetCString()), url.GetLength());
			unsigned char* data = NULL;
			JSize dataLength;
			Atom returnType;
			JXSelectionManager::DeleteMethod delMethod;
			if (selManager->GetSelectionData(dndName, 
											 time, GetWindow(), dndMgr->GetDNDDirectSave0XAtom(), 
											 &returnType, &data, &dataLength, &delMethod))
				{
				if (*data == 0x53)
					{
					UpdateDisplay();
					}
				else if (*data == 0x46)
					{
					XChangeProperty(*(GetDisplay()), source->GetWindow()->GetXWindow(),
							dndMgr->GetDNDDirectSave0XAtom(),
							(GetSelectionManager())->GetMimePlainTextXAtom(), 8,
							PropModeReplace,
							(unsigned char*) "", 0);
					SyGRemoteErrUpdateTask* task =
						new SyGRemoteErrUpdateTask();
					assert(task != NULL);
					JXGetApplication()->InstallUrgentTask(task);
					}
				selManager->DeleteSelectionData(&data, delMethod);
				}
			
			}
		}
	else
		{
		if (action == dndMgr->GetDNDActionAskXAtom())
			{
			JArray<Atom> actionList;
			JPtrArray<JString> descriptionList;
			descriptionList.SetCleanUpAction(JPtrArrayT::kDeleteAllAsObjects);
			if (!dndMgr->GetAskActions(&actionList, &descriptionList))
				{
				realAction = dndMgr->GetDNDActionMoveXAtom();;
				}
			else if (!dndMgr->ChooseDropAction(actionList, descriptionList,
											   &realAction))
				{
				return;
				}
			}

		unsigned char* data = NULL;
		JSize dataLength;
		Atom returnType;
		JXSelectionManager::DeleteMethod delMethod;
		if (selManager->GetSelectionData(dndName, 
										 time, GetWindow(), selManager->GetURLXAtom(), 
										 &returnType, &data, &dataLength, &delMethod))
			{
			if (returnType == selManager->GetURLXAtom())
				{
				JPtrArray<JString> fileNameList, urlList;
				JXUnpackFileNames((char*) data, dataLength, &fileNameList, &urlList);

				JTreeNode* jnode;
				if (currentDndHereIndex == 0)
					{
					jnode = itsFileTree->GetRoot();
					}
				else
					{
					JTreeList* treeList = GetTreeList();
					jnode = treeList->GetNode(currentDndHereIndex);
					}
					
				SyGFileTreeNode* node = 
					dynamic_cast(SyGFileTreeNode*, jnode);
				assert (node != NULL);
				const JString& dest = node->GetFullPathAndName();

				JSize count = fileNameList.GetElementCount();
				
				JIndex fileCount = 1;
				JBoolean keepGoing = kTrue;
				JProgressDisplay* pg   = NULL;
				JBoolean force = kFalse;
				JBoolean firstCheck = kTrue;
				
				while ((fileCount <= count) && keepGoing)
					{
					JString temp = *(fileNameList.NthElement(fileCount));
					if (JNameUsed(temp))
						{
						if (realAction == dndMgr->GetDNDActionMoveXAtom())
							{
							if (pg == NULL)
								{
								pg = JGetCreatePG()->New();
								pg->FixedLengthProcessBeginning(count,
									"Moving Files...", kTrue, kFalse);
								}
								
							gMove(temp, dest, force, &force, &firstCheck);
							}
							
						else
							{
							if (pg == NULL)
								{
								pg = JGetCreatePG()->New();
								pg->FixedLengthProcessBeginning(count,
									"Copying Files...", kTrue, kFalse);
								}
							
							if (!gCopy(temp, dest, force, &force, &firstCheck))
								{
								keepGoing = kFalse;
								}
								
							}
						}

					if (keepGoing && pg != NULL)
						{
						keepGoing = pg->IncrementProgress();
						}
						
					fileCount++;			
					}
					
				if (pg != NULL)
					{
					pg->ProcessFinished();
					delete pg;
					}
				fileNameList.DeleteAll();
				urlList.DeleteAll();
				}		

			selManager->DeleteSelectionData(&data, delMethod);
			}
		}
	if (currentDndHereIndex != 0)
		{
		TableRefreshCell(currentDndHereIndex, 1);
		}
		
	currentDndHereIndex = 0;
	UpdateDisplay();
	ClearIncrementalSearchBuffer();
}

/******************************************************************************
 ConvertSelection 

 ******************************************************************************/

JBoolean 
SyGFileTreeTable::ConvertSelection
	(
	const Atom 		name, 
	const Atom 		requestType, 
	Atom* 			returnType,
	unsigned char** data,
	JSize* 			dataLength, 
	JSize* 			bitsPerBlock
	)
{
	*bitsPerBlock = 8;

	JXSelectionManager* selMgr = GetSelectionManager();
	JXDNDManager* dndMgr       = GetDNDManager();
	
	if (name == dndMgr->GetDNDSelectionName() &&
			 requestType == selMgr->GetURLXAtom() &&
			 itsDNDBuffer != NULL)
		{
		CopyToDNDBuffer();

		*returnType = selMgr->GetURLXAtom();
		*dataLength = itsDNDBuffer->GetLength();
		*data = new unsigned char[ *dataLength ];
		if (*data != NULL)
			{
			memcpy(*data, *itsDNDBuffer, *dataLength);
			return kTrue;
			}
		}

	return kFalse;
}

/******************************************************************************
 CopyToDNDBuffer (private)

 ******************************************************************************/

void
SyGFileTreeTable::CopyToDNDBuffer()
{
	if (itsDNDBuffer == NULL || !itsDNDBuffer->IsEmpty())
		{
		return;
		}

	JTableSelection& s = GetTableSelection();
	assert( s.HasSelection() );

	JPtrArray<JString> fileList;
	JTreeList* treeList = GetTreeList();
	
	JTableSelectionIterator iter(&s);
	JPoint cell;
	while (iter.Next(&cell) && (cell.x == 2))
		{
		SyGFileTreeNode* node = 
			dynamic_cast(SyGFileTreeNode*, treeList->GetNode(cell.y));
		assert (node != NULL);
		JString* s = new JString(node->GetFullPathAndName());
		assert(s != NULL);
		fileList.Append(s);
		}

	*itsDNDBuffer = JXPackFileNames(fileList);
	fileList.DeleteAll();
}

/******************************************************************************
 LostSelectionOwnership 

 ******************************************************************************/

void 
SyGFileTreeTable::LostSelectionOwnership
	(
	const Atom selectionName
	)
{
	if (selectionName == (GetDNDManager())->GetDNDSelectionName())
		{
		delete itsDNDBuffer;
		itsDNDBuffer = NULL;
		}
	JXNamedTreeListWidget::LostSelectionOwnership(selectionName);
}

/******************************************************************************
 GetDNDAction (virtual protected)

	This is called repeatedly during the drag so the drop action can be
	changed based on the current target, buttons, and modifier keys.

 ******************************************************************************/

Atom
SyGFileTreeTable::GetDNDAction
	(
	const JXContainer*		target,
	const JXButtonStates&	buttonStates,
	const JXKeyModifiers&	modifiers
	)
{
	JBoolean moveDef	= SyGGetPrefsMgr()->IsMovingByDefault();
	if (modifiers.control())
		{
		return (GetDNDManager())->GetDNDActionAskXAtom();
		}
	else if ((modifiers.meta() && moveDef) ||
			 (!modifiers.meta() && !moveDef))
		{
		return (GetDNDManager())->GetDNDActionCopyXAtom();
		}
	else
		{
		return (GetDNDManager())->GetDNDActionMoveXAtom();
		}
}

/******************************************************************************
 GetDNDAskActions (virtual protected)

	This is called when the value returned by GetDropAction() changes to
	XdndActionAsk.  If GetDropAction() repeatedly returns XdndActionAsk,
	this function is not called again because it is assumed that the
	actions are the same within a single DND session.

	This function must place at least 2 elements in askActionList and
	askDescriptionList.

	The first element should be the default action.

 ******************************************************************************/

void
SyGFileTreeTable::GetDNDAskActions
	(
	const JXButtonStates&	buttonStates,
	const JXKeyModifiers&	modifiers,
	JArray<Atom>*			askActionList,
	JPtrArray<JString>*		askDescriptionList
	)
{
	JXDNDManager* dndMgr = GetDNDManager();
	askActionList->AppendElement(dndMgr->GetDNDActionCopyXAtom());
	askActionList->AppendElement(dndMgr->GetDNDActionMoveXAtom());

	JString* s = new JString(kDNDActionCopyDescrip);
	assert( s != NULL );
	askDescriptionList->Append(s);

	s = new JString(kDNDActionMoveDescrip);
	assert( s != NULL );
	askDescriptionList->Append(s);
}

/******************************************************************************
 HandleDNDResponse (virtual protected)

	This is called when the target indicates whether or not it will accept
	the drop.  If !dropAccepted, the action is undefined.  If the drop target
	is not within the same application, target is NULL.

 ******************************************************************************/

void
SyGFileTreeTable::HandleDNDResponse
	(
	const JXContainer*	target,
	const JBoolean		dropAccepted,
	const Atom			action
	)
{
	JXDNDManager* dndMgr = GetDNDManager();
	if (!dropAccepted)
		{
		DisplayCursor(itsDNDCursor);
		}
	else if (action == dndMgr->GetDNDActionCopyXAtom())
		{
		DisplayCursor(itsDNDCopyCursor);
		}
	else if (action == dndMgr->GetDNDActionMoveXAtom())
		{
		DisplayCursor(itsDNDCursor);
		}
	else if (action == dndMgr->GetDNDActionLinkXAtom())
		{
		DisplayCursor(itsDNDCursor);
		}
	else if (action == dndMgr->GetDNDActionAskXAtom())
		{
		DisplayCursor(itsDNDAskCursor);
		}
	else
		{
		DisplayCursor(itsDNDCursor);
		}
}

/******************************************************************************
 UpdateDisplay 

 ******************************************************************************/

void 
SyGFileTreeTable::UpdateDisplay()
{
	itsFileTreeList->UpdateList();
	if (!itsDraggingRect)
		{
		TableRefresh();
		}
}

/******************************************************************************
 AvailableWidth

 ******************************************************************************/

JCoordinate  
SyGFileTreeTable::GetAvailableWidth() 
	const
{
//	return (JXNamedTreeListWidget::GetAvailableWidth() - 
//		GetFMWidthUsed(itsVisibleCols));
}

/******************************************************************************
 Receive (protected)

 ******************************************************************************/

void
SyGFileTreeTable::Receive
	(
	JBroadcaster*	sender,
	const Message&	message
	)
{
	if (sender == itsEditMenu && message.Is(JXMenu::kNeedsUpdate))
		{
		if (HasFocus())
			{
			UpdateEditMenu();
			}
		}
	else if (sender == itsEditMenu && message.Is(JXMenu::kItemSelected))
		{
		if (HasFocus())
			{
			const JXMenu::ItemSelected* selection =
				dynamic_cast(const JXMenu::ItemSelected*, &message);
			assert( selection != NULL );
			HandleEditMenu(selection->GetIndex());
			}
		}	
	else if (sender == itsTreeMenu && message.Is(JXMenu::kItemSelected))
		{
		 const JXMenu::ItemSelected* selection =
			dynamic_cast(const JXMenu::ItemSelected*, &message);
		assert( selection != NULL );
		HandleTreeMenu(selection->GetIndex());
		}

	else if (sender == itsShortcutMenu && message.Is(JXMenu::kItemSelected))
		{
		 const JXMenu::ItemSelected* selection =
			dynamic_cast(const JXMenu::ItemSelected*, &message);
		assert( selection != NULL );
		HandleShortcutMenu(selection->GetIndex());
		}

	else if (sender == itsShortcutMenu && message.Is(JXMenu::kNeedsUpdate))
		{
		UpdateShortcutMenu();
		}
		
	else if (sender == itsTreeMenu && message.Is(JXMenu::kNeedsUpdate))
		{
		UpdateTreeMenu();
		}
	else if (sender == itsRunDialog && message.Is(JXDialogDirector::kDeactivated))
		{
		const JXDialogDirector::Deactivated* info =
			dynamic_cast(JXDialogDirector::Deactivated*, &message);
		assert(info != NULL);
		if (info->Successful())
			{
			GetRunCommand();
			}
		itsRunDialog = NULL;
		}				
	else 
		{
		JXNamedTreeListWidget::Receive(sender, message);
		}
	
}

/******************************************************************************
 ExtractInputData (virtual protected)

 ******************************************************************************/

JBoolean
SyGFileTreeTable::ExtractInputData
	(
	const JPoint& cell
	)
{
	JIndex index 	= cell.y;
	JXInputField* input;
	JBoolean ok		= GetXInputField(&input);
	assert(ok);
	JString newName	= input->GetText();
	if (itsFileTreeList->GetNodeName(index) == newName)
		{
		return kTrue;
		}
	JTreeNode* jnode = itsFileTreeList->GetNode(index);
	SyGFileTreeNode* node = dynamic_cast(SyGFileTreeNode*, jnode);
	assert (node != NULL);
	JString oldName = node->GetFullPathAndName();
	JString path;
	JString name;
	if (oldName.GetLastCharacter() == '/')
		{
		oldName.RemoveSubstring(oldName.GetLength(), oldName.GetLength());
		}
	JSplitPathAndName(oldName, &path, &name);
	JAppendDirSeparator(&path);
	JString realName = path + newName;
	if (JNameUsed(realName))
		{
		JGetUserNotification()->ReportError("That name is in use.");
		return kFalse;
		}
	if (realName.GetLastCharacter() == '/')
		{
		realName.RemoveSubstring(realName.GetLength(), realName.GetLength());
		}
	JError err = JRenameFile(oldName, realName);
	if (err.OK())
		{
		node->SetNameAndUpdate(newName);
		JTreeNode* jparent = node->GetParent();
		SyGFileTreeNode* parent = 
			dynamic_cast(SyGFileTreeNode*, jparent);
		parent->Remove(node);
		parent->InsertSorted(node);
		JIndex findex;
		if (itsFileTreeList->FindNode(node, &findex))
			{
			JTableSelection& s = GetTableSelection();
			s.ClearSelection();
			JPoint cell(2, findex);
			s.SelectCell(cell);
			TableScrollToCell(cell);
			}
		TableRefresh();
		return kTrue;
		}
	else
		{
		err.ReportError();
		}
	return kFalse;
}

/******************************************************************************
 NewNameOK

 ******************************************************************************/

JBoolean  
SyGFileTreeTable::NewNameOK
	(
	const JIndex index,
	const JString& newName
	)
{
	if (itsFileTreeList->GetNodeName(index) == newName)
		{
		return kTrue;
		}
	JTreeNode* jnode = itsFileTreeList->GetNode(index);
	SyGFileTreeNode* node = dynamic_cast(SyGFileTreeNode*, jnode);
	assert (node != NULL);
	JString oldName = node->GetFullPathAndName();
	JString path;
	JString name;
	if (oldName.GetLastCharacter() == '/')
		{
		oldName.RemoveSubstring(oldName.GetLength(), oldName.GetLength());
		}
	JSplitPathAndName(oldName, &path, &name);
	JAppendDirSeparator(&path);
	JString realName = path + newName;
	if (JNameUsed(realName))
		{
		JGetUserNotification()->ReportError("That name is in use.");
		return kFalse;
		}
	if (realName.GetLastCharacter() == '/')
		{
		realName.RemoveSubstring(realName.GetLength(), realName.GetLength());
		}
	JError err = JRenameFile(oldName, realName);
	if (err.OK())
		{
		node->SetNameAndUpdate(newName);
		JTreeNode* jparent = node->GetParent();
		SyGFileTreeNode* parent = 
			dynamic_cast(SyGFileTreeNode*, jparent);
		parent->Remove(node);
		parent->InsertSorted(node);
		JIndex findex;
		if (itsFileTreeList->FindNode(node, &findex))
			{
			JTableSelection& s = GetTableSelection();
			s.ClearSelection();
			JPoint cell(2, findex);
			s.SelectCell(cell);
			TableScrollToCell(cell);
			}
		TableRefresh();
		return kTrue;
		}
	else
		{
		err.ReportError();
		}
	return kFalse;
}

/******************************************************************************
 GetNearestDirIndex

 ******************************************************************************/

JIndex
SyGFileTreeTable::GetNearestDirIndex
	(
	const JIndex index
	)
{
	const JTreeList* treeList = GetTreeList();
	
	
	if (treeList->GetNode(index)->IsOpenable())
		{
		return index;
		}
		
	else
		{
		SyGFileTreeNode* entry = dynamic_cast(SyGFileTreeNode*, treeList->GetNode(index));
		assert (entry != NULL);
		
		JSize thisDepth = entry->GetDepth();
		
		for (JIndex i = index; i >= 1; i--)
			{
			entry = dynamic_cast(SyGFileTreeNode*, treeList->GetNode(i));
			assert (entry != NULL);
			
			if (entry->GetDepth() < thisDepth)
				{
				return i;
				break;
				}
			}
		}
		
	return 0;
}

/******************************************************************************
 HandleTreeMenu

 ******************************************************************************/

void  
SyGFileTreeTable::HandleTreeMenu
	(
	const JIndex index
	)
{
	ClearIncrementalSearchBuffer();
	if (index == kCreateLink)
		{
		MakeLinks();
		}
	else if (index == kFindOriginalCmd)
		{
		JTableSelectionIterator iter(&(GetTableSelection()));
		JPoint cell;
		JString dest;
		JString linkName;
		
		if (!iter.Next(&cell))
			{
			GetEditedCell(&cell);
			}
		JTreeNode* jnode = itsFileTreeList->GetNode(cell.y);
		SyGFileTreeNode* node = dynamic_cast(SyGFileTreeNode*, jnode);
		assert(node != NULL);
		JUNIXDirEntry* entry = node->GetDirEntry();
		const JString& str = entry->GetLinkName();
		JString fullName(str);
		if (fullName.GetFirstCharacter() != '/')
			{
			fullName = entry->GetPath() + fullName;
			}
		JString path;
		JString name;
		if (fullName.GetLastCharacter() == '/')
			{
			fullName.RemoveSubstring(fullName.GetLength(), fullName.GetLength());
			}
		JSplitPathAndName(fullName, &path, &name);
		SyGGetApplication()->NewChildWindow(path, name);
		}
	else if (index == kShowFilterCmd)
		{
		itsSet->ToggleFilter();
		}
	else if (index == kShowHiddenCmd)
		{
		JBoolean show = kTrue;
		if (itsFileTree->HiddenEntriesVisible())
			{
			show = kFalse;
			}
		itsFileTree->ShowHiddenEntries(show);
		JTreeNode* node = itsFileTree->GetRoot();
		SyGFileTreeNode* base = dynamic_cast(SyGFileTreeNode*, node);
		assert(base != NULL);
		base->ShowHidden(show);

		}
	else if (index == kShowUserCmd)
		{
		JBoolean prefs[7];
		GetPreferences(prefs);
		if (prefs[2])
			{
			prefs[2] = kFalse;
			if (itsCurrentColType == kGFMUID)
				{
				SetCurrentCol(kLeadingCols);
				}
			}
		else
			{
			prefs[2] = kTrue;
			}
		SetPreferences(prefs);
		}
	else if (index == kShowGroupCmd)
		{
		JBoolean prefs[7];
		GetPreferences(prefs);
		if (prefs[3])
			{
			prefs[3] = kFalse;
			if (itsCurrentColType == kGFMGID)
				{
				SetCurrentCol(kLeadingCols);
				}
			}
		else
			{
			prefs[3] = kTrue;
			}
		SetPreferences(prefs);
		}
	else if (index == kShowSizeCmd)
		{
		JBoolean prefs[7];
		GetPreferences(prefs);
		if (prefs[4])
			{
			prefs[4] = kFalse;
			if (itsCurrentColType == kGFMSize)
				{
				SetCurrentCol(kLeadingCols);
				}
			}
		else
			{
			prefs[4] = kTrue;
			}
		SetPreferences(prefs);
		}
	else if (index == kShowModeCmd)
		{
		JBoolean prefs[7];
		GetPreferences(prefs);
		if (prefs[5])
			{
			prefs[5] = kFalse;
			}
		else
			{
			prefs[5] = kTrue;
			}
		SetPreferences(prefs);
		}
	else if (index == kShowDateCmd)
		{
		JBoolean prefs[7];
		GetPreferences(prefs);
		if (prefs[6])
			{
			prefs[6] = kFalse;
			if (itsCurrentColType == kGFMDate)
				{
				SetCurrentCol(kLeadingCols);
				}			
			}
		else
			{
			prefs[6] = kTrue;
			}
		SetPreferences(prefs);
		}
	else if (index == kShowAllAttr)
		{
		JBoolean prefs[7];
		GetPreferences(prefs);
		for (JIndex i = 2; i <= 6; i++)
			{
			prefs[i] = kTrue;
			}
		SetPreferences(prefs);
		}
	else if (index == kHideAllAttr)
		{
		JBoolean prefs[7];
		GetPreferences(prefs);
		for (JIndex i = 2; i <= 6; i++)
			{
			prefs[i] = kFalse;
			}
		SetPreferences(prefs);
		}
}

/******************************************************************************
 Remove

 ******************************************************************************/

JBoolean  
SyGFileTreeTable::Remove
	(
	const JString& src
	)
{
	JString homeDir;
	JBoolean success = kFalse;
	
	if (JDirectoryExists(src))
		{
		JError err = JRemoveDirectory(src);
		if (err.OK())
			{
			return kTrue;
			}
		}
	
	if (JGetHomeDirectory(&homeDir))
		{
		JString trashName = homeDir + ".trashcan";
		
		if (JFileExists(trashName))
			{
			JGetUserNotification()->ReportError("There is a file name \".trashcan\"");
			return kFalse;
			}
		if (!JDirectoryExists(trashName))
			{
			JError err = JCreateDirectory(trashName);
			if (!err.OK())
				{
				JGetUserNotification()->ReportError("Error creating trashcan folder!");
				return kFalse;
				}
			}
		
		JAppendDirSeparator(&trashName);
		JString name(src);
		if (name.GetLastCharacter() == '/')
			{
			name.RemoveSubstring(name.GetLength(), name.GetLength());
			}

		JString path;
		JString oldName;
		
		JSplitPathAndName(name, &path, &oldName);
		
		JString newName = trashName + oldName;
		
		if (JNameUsed(newName))
			{
			JString tempName = newName;			
			JIndex number = 1;
			while (JNameUsed(tempName) && (number <= 25))
				{
				tempName = newName + JString(number);
				number++;
				}
			newName = tempName;
			}
		
		if (!JNameUsed(newName))
			{
			JString sysCmd = "mv \"" + name + "\" \"" + newName + "\"";
			success = SystemCall(sysCmd, kFalse);
			if (!success)
				{
				sysCmd = "cp -Rd \"" + name + "\" \"" + newName + "\"";
				success = SystemCall(sysCmd);
				if (success)
					{
					JRemoveFile(name);
					}
				}
			if (!success)
				{
				JGetUserNotification()->ReportError("Unable to remove.");
				}
			}
		else
			{
			JGetUserNotification()->ReportError("Unable to remove, you may need to empty your trashcan.");
			}
		}
	else
		{
		JGetUserNotification()->ReportError("You need to have a home directory.");
		}
	return success;
}

/******************************************************************************
 Delete

 ******************************************************************************/

void  
SyGFileTreeTable::Delete
	(
	const JString& src
	)
{
	JString sysCmd = "rm -rf \"" + src + "\"";
	SystemCall(sysCmd);
}

/******************************************************************************
 MoveSelectedToTrash (public)

 ******************************************************************************/

void  
SyGFileTreeTable::MoveSelectedToTrash()
{
	JBoolean warn = SyGGetPrefsMgr()->IsWarningBeforeTrash();
	if (!warn || JGetUserNotification()->AskUserYes("Are you sure you want to move these files to the trashcan?"))
		{
		JTableSelectionIterator iter(&(GetTableSelection()));
		JPoint cell;
		JBoolean found 	= kFalse;
		while(iter.Next(&cell))
			{
			found		= kTrue;
			MoveCellToTrash(cell);
			}
		if (!found && GetEditedCell(&cell))
			{
			MoveCellToTrash(cell);
			}	
		sync();
		itsFileTreeList->ForceListUpdate();
		TableRefresh();
		}

}

/******************************************************************************
 MoveCellToTrash (private)

 ******************************************************************************/

void
SyGFileTreeTable::MoveCellToTrash
	(
	const JPoint cell
	)
{
	SyGFileTreeNode* node = 
		dynamic_cast(SyGFileTreeNode*, GetTreeList()->GetNode(cell.y));
	assert (node != NULL);
	JString file = node->GetFullPathAndName();
	if (Remove(file))
		{
		delete node;
		}			
}


/******************************************************************************
 InsertFMTreeCol

 ******************************************************************************/

void  
SyGFileTreeTable::InsertFMTreeCol
	(
	const GFMColType type
	)
{
	JIndex 		index;
	JCoordinate width;
	
	if (InsertFMCol(this, itsVisibleCols, 2, type, 
			&index, &width))
		{
		InsertCol(index, width);
		}
}

/******************************************************************************
 RemoveFMTreeCol

 ******************************************************************************/

void  
SyGFileTreeTable::RemoveFMTreeCol
	(
	const GFMColType type
	)
{
	JIndex index;
	
	if (RemoveFMCol(this, itsVisibleCols, 2, type, &index))
		{
		RemoveCol(index);
		}
}

/******************************************************************************
 DoubleClickIcon

 ******************************************************************************/

void 
SyGFileTreeTable::DoubleClickIcon
	(
	const JIndex index,
	const JXKeyModifiers& modifiers
	)
{
	PerformAction(index, JConvertToBoolean(!modifiers.meta()), 
				  modifiers.meta(), modifiers.control());
	return;
}

/******************************************************************************
 PerformAction

 ******************************************************************************/

void 
SyGFileTreeTable::PerformAction
	(
	const JIndex index, 
	const JBoolean altDir, 
	const JBoolean altFile,
	const JBoolean cntlFile
	)
{
	SyGFileTreeNode* node = 
		dynamic_cast(SyGFileTreeNode*, itsFileTreeList->GetNode(index));
	assert (node != NULL);
	
	if (node->IsOpenable())
		{
		if (altDir)
			{
			SyGGetApplication()->NewChildWindow(node->GetFullPathAndName());
			}
		else
			{
			JTreeNode* jnode = itsFileTree->GetRoot();
			SyGFileTreeNode* base = dynamic_cast(SyGFileTreeNode*, jnode);
			assert(base != NULL);
			JError err = base->GoTo(node->GetFullPathAndName());
			assert(err.OK());
			itsTextBuffer.Clear();
			}
		}
		
	else if (node->IsExecutable())
		{
		SyGHandleExecutable(node->GetFullPathAndName());
		}
	
	else if (node->IsReadable())
		{
		if (cntlFile)
			{
			itsRunOnSelected 	= kTrue;
			itsRunFile 			= node->GetFullPathAndName();
			StartRunCommand();
			}
		else
			{
			SyGHandleFileAction(node->GetFullPathAndName(), altFile);
			}
		}
}

/******************************************************************************
 InitializeColumns

 ******************************************************************************/

void 
SyGFileTreeTable::InitializeColumns()
{
	JSize imageWidth = 20;
	if (GetRowCount() >= 1)
		{
		imageWidth = GetImageWidth(1);
		}
	JBoolean prefs[7];
	SyGGetPrefsMgr()->GetTreePreferences(prefs);
	SetPreferences(prefs);
}

/******************************************************************************
 InitializeColumns

 ******************************************************************************/

void 
SyGFileTreeTable::InitializeColumns
	(
	istream& is
	)
{
	JFileVersion version;
	is >> version;
	int type;
	is >> type;
	JBoolean prefs[7];
	is >> prefs[2];
	is >> prefs[3];
	is >> prefs[4];
	is >> prefs[5];
	is >> prefs[6];
	SetPreferences(prefs);
	if (type != kGFMNone)
		{
		SetCurrentCol((GFMColType)type);
		}
}

/******************************************************************************
 GetViewPreferences

 ******************************************************************************/

void 
SyGFileTreeTable::GetViewPreferences()
{
	JBoolean prefs[7];
	SyGGetPrefsMgr()->GetTreePreferences(prefs);
	
	if (prefs[0])
		{
		itsFileTree->ShowHiddenEntries(kTrue);
		}
		
	else
		{
		itsFileTree->ShowHiddenEntries(kFalse);
		}
		
	if (prefs[1])
		{
		itsFileTree->ShowFiles(kTrue);
		}
		
	else
		{
		itsFileTree->ShowFiles(kFalse);
		}
		
	if (prefs[2])
		{
		InsertFMTreeCol(kGFMUID);
		}
		
	else
		{
		RemoveFMTreeCol(kGFMUID);
		}
	
	if (prefs[3])
		{
		InsertFMTreeCol(kGFMGID);
		}
	
	else
		{
		RemoveFMTreeCol(kGFMGID);
		}
	
	if (prefs[4])
		{
		InsertFMTreeCol(kGFMSize);
		}
	
	else
		{
		RemoveFMTreeCol(kGFMSize);
		}
	
	if (prefs[5])
		{
		InsertFMTreeCol(kGFMMode);
		}
	
	else
		{
		RemoveFMTreeCol(kGFMMode);
		}
	
	if (prefs[6])
		{
		InsertFMTreeCol(kGFMDate);
		}
		
	else
		{
		RemoveFMTreeCol(kGFMDate);
		}
	
	AdjustToTree();
}

/******************************************************************************
 GetPreferences

 ******************************************************************************/

void 
SyGFileTreeTable::GetPreferences
	(
	JBoolean prefs[]
	)
{
	prefs[0] = itsFileTree->HiddenEntriesVisible();
	prefs[1] = itsFileTree->FilesVisible();
	
	for (int i = 2; i < 7; i++)
		{
		prefs[i] = itsVisibleCols[i-2];
		}
		
}

/******************************************************************************
 SetPreferences

 ******************************************************************************/

void 
SyGFileTreeTable::SetPreferences
	(
	JBoolean prefs[]
	)
{
	if (prefs[2])
		{
		InsertFMTreeCol(kGFMUID);
		}
		
	else
		{
		RemoveFMTreeCol(kGFMUID);
		}
	
	if (prefs[3])
		{
		InsertFMTreeCol(kGFMGID);
		}
	
	else
		{
		RemoveFMTreeCol(kGFMGID);
		}
	
	if (prefs[4])
		{
		InsertFMTreeCol(kGFMSize);
		}
	
	else
		{
		RemoveFMTreeCol(kGFMSize);
		}
	
	if (prefs[5])
		{
		InsertFMTreeCol(kGFMMode);
		}
	
	else
		{
		RemoveFMTreeCol(kGFMMode);
		}
	
	if (prefs[6])
		{
		InsertFMTreeCol(kGFMDate);
		}
		
	else
		{
		RemoveFMTreeCol(kGFMDate);
		}
	
	AdjustToTree();
}

/******************************************************************************
 WillAcceptDrop

 ******************************************************************************/

JBoolean  
SyGFileTreeTable::WillAcceptDrop
	(
	const JArray<Atom>& typeList, 
	Atom*				action,
	const Time			time,
	const JXWidget*		source
	)
{
	if (source == this)
		{
		return kTrue;
		}

	const Atom urlXAtom = (GetSelectionManager())->GetURLXAtom();
	const Atom xdsXAtom = (GetDNDManager())->GetDNDDirectSave0XAtom();
	JTreeNode* jnode 	= itsFileTree->GetRoot();
	SyGFileTreeNode* node = 
		dynamic_cast(SyGFileTreeNode*, jnode);
	assert (node != NULL);
	const JString& dest = node->GetFullPathAndName();
	JBoolean writeOK	= JDirectoryWritable(dest);
		
	const JSize typeCount 		= typeList.GetElementCount();
	for (JSize i = 1; i <= typeCount; i++)
		{
		if ((typeList.GetElement(i) == urlXAtom) ||
			((typeList.GetElement(i) == xdsXAtom) && writeOK))
			{
			return kTrue;
			}
		}
	
	return kFalse;
}

/******************************************************************************
 DNDInit

 ******************************************************************************/

void  
SyGFileTreeTable::DNDInit
	(
	const JPoint& pt, 
	const JXButtonStates& buttonStates,
	const JXKeyModifiers& modifiers
	)
{
	if (itsDNDBuffer == NULL)
		{
		itsDNDBuffer = new JString;
		assert( itsDNDBuffer != NULL );
		}
	else
		{
		itsDNDBuffer->Clear();
		}
}

/******************************************************************************
 DNDFinish (virtual protected)

 ******************************************************************************/

void
SyGFileTreeTable::DNDFinish
	(
	const JXContainer* target
	)
{
	CopyToDNDBuffer();
}


/******************************************************************************
 UpdateTreeMenu

 ******************************************************************************/

void  
SyGFileTreeTable::UpdateTreeMenu()
{
	JTableSelection& s 	= GetTableSelection();
	JBoolean selected	= s.HasSelection();
	if (!selected)
		{
		selected		= IsEditing();
		}
	JSize count 		= s.GetSelectedCellCount();
	if ((count == 0) && IsEditing())
		{
		count			= 1;
		}
	if (selected)
		{
		itsTreeMenu->EnableItem(kCreateLink);
		JPoint cell;
		if (count == 1)
			{
			if (!GetEditedCell(&cell))
				{
				s.GetFirstSelectedCell(&cell);
				}
			JTreeNode* jnode = itsFileTreeList->GetNode(cell.y);
			SyGFileTreeNode* node = dynamic_cast(SyGFileTreeNode*, jnode);
			assert(node != NULL);
			JUNIXDirEntry* entry = node->GetDirEntry();
			JUNIXDirEntry::Type type = entry->GetType();
			if ((type == JUNIXDirEntry::kFileLink) || (type == JUNIXDirEntry::kDirLink))
				{
				itsTreeMenu->EnableItem(kFindOriginalCmd);
				}
			}
		}
	if (itsSet->FilterVisible())
		{
		itsTreeMenu->CheckItem(kShowFilterCmd);
		}
	itsTreeMenu->EnableItem(kShowFilterCmd);
	itsTreeMenu->EnableItem(kShowHiddenCmd);
	itsTreeMenu->EnableItem(kShowUserCmd);
	itsTreeMenu->EnableItem(kShowGroupCmd);
	itsTreeMenu->EnableItem(kShowSizeCmd);
	itsTreeMenu->EnableItem(kShowModeCmd);
	itsTreeMenu->EnableItem(kShowDateCmd);
	itsTreeMenu->EnableItem(kShowAllAttr);
	itsTreeMenu->EnableItem(kHideAllAttr);

	if (itsFileTree->HiddenEntriesVisible())
		{
		itsTreeMenu->CheckItem(kShowHiddenCmd);
		}
	JBoolean prefs[7];
	GetPreferences(prefs);
	if (prefs[2])
		{
		itsTreeMenu->CheckItem(kShowUserCmd);
		}
	if (prefs[3])
		{
		itsTreeMenu->CheckItem(kShowGroupCmd);
		}
	if (prefs[4])
		{
		itsTreeMenu->CheckItem(kShowSizeCmd);
		}
	if (prefs[5])
		{
		itsTreeMenu->CheckItem(kShowModeCmd);
		}
	if (prefs[6])
		{
		itsTreeMenu->CheckItem(kShowDateCmd);
		}		
}

/******************************************************************************
 GetCurrentPath

 ******************************************************************************/

const JString& 
SyGFileTreeTable::GetCurrentPath()
{
	JTreeNode* node = itsFileTree->GetRoot();
	JNamedTreeNode* nnode = dynamic_cast(JNamedTreeNode*, node);
	assert(nnode != NULL);
	return nnode->GetName();
}

/******************************************************************************
 SavePreferences

 ******************************************************************************/

void 
SyGFileTreeTable::SavePreferences
	(
	ostream& os
	)
{
	os << ' ' << kCurrentFileVersion << ' ';
	os << (int)itsCurrentColType << ' ';
	JBoolean prefs[7];
	GetPreferences(prefs);
	os << prefs[2];
	os << prefs[3];
	os << prefs[4];
	os << prefs[5];
	os << prefs[6] << " ";
}

/******************************************************************************
 SaveAsDefault

 ******************************************************************************/

void 
SyGFileTreeTable::SaveAsDefault()
{
	JBoolean prefs[7];
	GetPreferences(prefs);
	SyGGetPrefsMgr()->SetTreePreferences(prefs);
	JXWindow* window = GetWindow();
	SyGGetPrefsMgr()->SaveDefaultWindowSize(window->GetFrameWidth(), window->GetFrameHeight());
	itsSet->SaveFilterPref();
}

/******************************************************************************
 HandleShortcutMenu

 ******************************************************************************/

void  
SyGFileTreeTable::HandleShortcutMenu
	(
	const JIndex index
	)
{
	ClearIncrementalSearchBuffer();
	if (index == kAddShortcutCmd)
		{
		JString shortcut(GetCurrentPath());
		JString homeDir;
		if (JGetHomeDirectory(&homeDir))
			{
			JAppendDirSeparator(&homeDir);
			if (shortcut.BeginsWith(homeDir))
				{
				shortcut.ReplaceSubstring(1, homeDir.GetLength(), "~/");
				}
			}
		SyGGetApplication()->AddShortcut(shortcut);
		}
		
	else if (index == kRemoveShortcutCmd)
		{
		JString shortcut(GetCurrentPath());
		JString homeDir;
		if (JGetHomeDirectory(&homeDir))
			{
			JAppendDirSeparator(&homeDir);
			if (shortcut.BeginsWith(homeDir))
				{
				shortcut.ReplaceSubstring(1, homeDir.GetLength(), "~/");
				}
			}
		SyGGetApplication()->RemoveShortcut(shortcut);
		}
		
	else if (index == kRemoveAllShortcutsCmd)
		{
		SyGGetApplication()->RemoveAllShortcuts();
		}
		
	else
		{
		SyGGetApplication()->OpenShortcut(index - 3);
		}
}

/******************************************************************************
 UpdateShortcutMenu

 ******************************************************************************/

void  
SyGFileTreeTable::UpdateShortcutMenu()
{
	JSize count = itsShortcutMenu->GetItemCount();

	for (JSize j = 4; j <= count; j++)
		{
		itsShortcutMenu->DeleteItem(4);
		}
		
	JPtrArray<JString>& shortcuts = SyGGetApplication()->GetShortcuts();
	
	JIndex index = shortcuts.GetElementCount();
	
	for (JIndex i = 1; i<= index; i++)
		{
		itsShortcutMenu->AppendItem(*(shortcuts.NthElement(i)));
		}

}

/******************************************************************************
 SelectAll

 ******************************************************************************/

void  
SyGFileTreeTable::SelectAll()
{
	GetTableSelection().SelectCol(2);
	ClearIncrementalSearchBuffer();
}

/******************************************************************************
 MakeLinks

 ******************************************************************************/

void  
SyGFileTreeTable::MakeLinks()
{
	JTableSelection& s 	= GetTableSelection();
	JTableSelectionIterator iter(&s);
	JPoint cell;
	JArray<JIndex> nodes;
	JBoolean found	= kFalse;
	JBoolean ok		= kTrue;
	
	while(iter.Next(&cell) && ok)
		{
		found 		= kTrue;
		ok			= MakeLinkToCell(cell, &nodes);
		}
	if (!found && GetEditedCell(&cell))
		{
		MakeLinkToCell(cell, &nodes);
		}
	s.ClearSelection();
	JSize count = nodes.GetElementCount();
	for (JSize i = 1; i <= count; i++)
		{
		s.SelectCell(nodes.GetElement(i), 2);
		}
}

/******************************************************************************
 MakeLinkToCell (private)

 ******************************************************************************/

JBoolean
SyGFileTreeTable::MakeLinkToCell
	(
	const JPoint 	cell,
	JArray<JIndex>*	nodes
	)
{
	JTreeNode* jnode = itsFileTreeList->GetNode(cell.y);
	JString dest;
	SyGFileTreeNode* node 	= dynamic_cast(SyGFileTreeNode*, jnode);
	assert(node != NULL);
	JTreeNode* jparent 		= node->GetParent();
	JNamedTreeNode* parent 	= dynamic_cast(JNamedTreeNode*, jparent);
	assert(parent != NULL);
	JUNIXDirEntry* dirEntry = node->GetDirEntry();
	if (JDirectoryWritable(dirEntry->GetPath()))
		{
		const JString& src = dirEntry->GetFullName();
		dest = src;
		if (dest.GetCharacter(dest.GetLength()) == '/')
			{
			dest.RemoveSubstring(dest.GetLength(), dest.GetLength());
			}
			
		dest = dest + ".link";
		if (symlink(src, dest) != 0)
			{
			JGetUserNotification()->ReportError("Symbolic link failed!");
			return kFalse;
			}
		JUNIXDirEntry* entry = new JUNIXDirEntry(dest);
		assert(entry != NULL);
		SyGFileTreeNode* fnode = 
			new SyGFileTreeNode(itsFileTree, entry, NULL, entry->GetName(), node->IsOpenable());
		assert(fnode != NULL);
		parent->InsertSorted(fnode);
		JIndex findex;
		if (GetTreeList()->FindNode(fnode, &findex))
			{
			nodes->AppendElement(findex);
			}
		
		}
	else
		{
		const JString& src 		= dirEntry->GetFullName();
		const JString& srcName 	= dirEntry->GetName();
		JString homeDir;
		if (JGetHomeDirectory(&homeDir))
			{
			JAppendDirSeparator(&homeDir);
			dest 				= homeDir + srcName;
			JString linkName 	= srcName;
			if (dest.GetCharacter(dest.GetLength()) == '/')
				{
				dest.RemoveSubstring(dest.GetLength(), dest.GetLength());
				}
			if (linkName.GetCharacter(linkName.GetLength()) == '/')
				{
				linkName.RemoveSubstring(linkName.GetLength(), linkName.GetLength());
				}	
			dest = dest + ".link";
			linkName += ".link";
			if (symlink(src, dest) != 0)
				{
				JGetUserNotification()->ReportError("Symbolic link failed!");
				return kFalse;
				}
			SyGGetApplication()->NewChildWindow(homeDir, linkName);
			return kTrue;
			}
		else
			{
			JGetUserNotification()->ReportError("You don't have write access!");
			return kFalse;
			}
		}
	return kTrue;
}


/******************************************************************************
 SelectName

 ******************************************************************************/

void  
SyGFileTreeTable::SelectName
	(
	const JCharacter* name
	)
{
	if ( JString(name) != "")
		{
		SyGSelectNameTask* task = new SyGSelectNameTask(this, name);
		assert(task != NULL);
		JXGetApplication()->InstallIdleTask(task);
		}
}

/******************************************************************************
 DoSelectName

 ******************************************************************************/

void  
SyGFileTreeTable::DoSelectName
	(
	const JCharacter* name
	)
{
	JTreeNode* jnode = itsFileTree->GetRoot();
	GSimpleFileTreeNode* base = dynamic_cast(GSimpleFileTreeNode*, jnode);
	assert(base != NULL);
	base->UpdateDir(kTrue);
	GSimpleFileTreeNode* node;
	if (base->FindChild(name, &node))
		{
		JIndex index;
		if (itsFileTreeList->FindNode(node, &index))
			{
			JPoint cell(2, index);
			GetTableSelection().SelectCell(cell);
			TableScrollToCell(cell);
			}
		}
}

/******************************************************************************
 SelectClosest

 ******************************************************************************/

void  
SyGFileTreeTable::SelectClosest()
{
	JIndex count = 0;
	JIndex closest = 0;

	for (JSize i = 1; i <= itsFileTreeList->GetElementCount(); i++)
		{
		JTreeNode* jnode = itsFileTreeList->GetNode(i);
		SyGFileTreeNode* node = dynamic_cast(SyGFileTreeNode*, jnode);
		assert(node != NULL);
		JString test = node->GetName();
		test.ToLower();
		JIndex index = 1;
		while (	test.IndexValid(index) &&
				itsTextBuffer.IndexValid(index) &&
				(test.GetCharacter(index) == itsTextBuffer.GetCharacter(index)))
			{
			index++;
			}
		if (index > count + 1)
			{
			count = index - 1;
			closest = i;
			}
		}
	if (count > 0)
		{
		JTableSelection& selection = GetTableSelection();
		selection.ClearSelection();
		JPoint cell(2, closest);
		selection.SelectCell(cell);
		cell.x = 1;
		TableScrollToCell(cell, kFalse);
		}
}


/******************************************************************************
 SaveDirState

 ******************************************************************************/

void  
SyGFileTreeTable::SaveDirState
	(
	ostream& os
	)
{
	os << ' ' << kCurrentFileVersion << ' ';
	JPtrArray<JString> names;
	JSize count = itsFileTreeList->GetElementCount();
	for (JSize i = 1; i <= count; i++)
		{
		if (itsFileTreeList->IsOpen(i))
			{
			JTreeNode* jnode = itsFileTreeList->GetNode(i);
			SyGFileTreeNode* node = dynamic_cast(SyGFileTreeNode*, jnode);
			assert(node != NULL);
			JString* str = new JString(node->GetFullPathAndName());
			assert(str != NULL);
			names.Append(str);
			}
		}
	JSize ncount = names.GetElementCount();
	os << ncount << " ";
	for (JSize i = 1; i <= ncount; i++)
		{
		os << *(names.NthElement(i)) << ' ';
		}
	
}

/******************************************************************************
 InitDirs

 ******************************************************************************/

void  
SyGFileTreeTable::InitDirs
	(
	istream& is
	)
{
	JFileVersion version;
	is >> version;
	JSize namecount;
	is >> namecount;
	for (JSize i = 1; i <= namecount; i++)
		{
		JString file;
		is >> file;
		JBoolean found = kFalse;
		JSize count = itsFileTreeList->GetElementCount();
		JIndex index = 1;
		while (!found && (index <= count))
			{
			JTreeNode* jnode = itsFileTreeList->GetNode(index);
			SyGFileTreeNode* node = dynamic_cast(SyGFileTreeNode*, jnode);
			assert(node != NULL);
			if (node->GetFullPathAndName() == file)
				{
				found = kTrue;
				if (node->IsOpenable())
					{
					itsFileTreeList->Open(index);
					}
				}
			index ++;
			}
		}
}

/******************************************************************************
 CreateNewDirectory (public)

 ******************************************************************************/

void
SyGFileTreeTable::CreateNewDirectory()
{
	ClearIncrementalSearchBuffer();
	JTableSelectionIterator iter(&(GetTableSelection()));

	JTreeList* treeList = GetTreeList();
	JPoint cell;
	JString dirName;

	SyGFileTreeNode* node;
	
	if (iter.Next(&cell))
		{
		if (treeList->GetNode(cell.y)->IsOpenable())
			{
			node = dynamic_cast(SyGFileTreeNode*, treeList->GetNode(cell.y));
			assert (node != NULL);
			treeList->Open(node);
			dirName = node->GetFullPathAndName();
			}

		else
			{
			JIndex index = GetNearestDirIndex(cell.y);
			JTreeNode* jnode;
			if (index == 0)
				{
				jnode = itsFileTree->GetRoot();
				}
			else
				{
				const JTreeNode* connode = treeList->GetNode(index);
				jnode = const_cast<JTreeNode*>(connode);
				}
			node = dynamic_cast(SyGFileTreeNode*, jnode);
			assert (node != NULL);
			dirName = node->GetFullPathAndName();
			}
		}
	
	else
		{
		node = dynamic_cast(SyGFileTreeNode*, itsFileTree->GetRoot());
		assert (node != NULL);
		dirName = node->GetFullPathAndName();
		}

	JAppendDirSeparator(&dirName);
	
	JString tempName = dirName + "Untitled";
	
	JIndex number = 1;

	while (JDirectoryExists(tempName))
		{
		tempName = dirName + "Untitled" + JString(number);
		number++;
		}
		
	dirName = tempName;
		
	JError err = JCreateDirectory(dirName);
	if (err.OK())
		{
		JUNIXDirEntry* entry = new JUNIXDirEntry(dirName);
		assert(entry != NULL);
		SyGFileTreeNode* fnode = 
			new SyGFileTreeNode(itsFileTree, entry, NULL, entry->GetName());
		assert(fnode != NULL);
		node->InsertSorted(fnode);
		JIndex findex;
		if (GetTreeList()->FindNode(fnode, &findex))
			{
			JTableSelection& s = GetTableSelection();
			s.ClearSelection();
			s.SelectCell(findex, 2);
			TableScrollToCell(JPoint(1, findex));
			}
		return;
		}
		
	else
		{
		JGetUserNotification()->ReportError("Directory not created.");
		}
}

/******************************************************************************
 DeleteSelected (public)

 ******************************************************************************/

void
SyGFileTreeTable::DeleteSelected()
{
	if (JGetUserNotification()->AskUserYes("Are you sure you want to permanently delete these files?"))
		{
		JTableSelectionIterator iter(&(GetTableSelection()));
		JPoint cell;
		JBoolean ok 	= kTrue;
		JBoolean found	= kFalse;
		while(iter.Next(&cell))
			{
			found 				= kTrue;
			JBoolean deleted	= DeleteCell(cell);
			if (!deleted)
				{
				ok				= kFalse;
				}
			}
		if (!found && GetEditedCell(&cell))
			{
			JBoolean deleted	= DeleteCell(cell);
			if (!deleted)
				{
				ok				= kFalse;
				}
			}
		if (!ok)
			{
			JGetUserNotification()->ReportError("Some files could not be removed.");
			}
		}
}

/******************************************************************************
 DeleteCell (private)

 ******************************************************************************/

JBoolean
SyGFileTreeTable::DeleteCell
	(
	const JPoint cell
	)
{
	JBoolean ok = kTrue;
	SyGFileTreeNode* node = 
		dynamic_cast(SyGFileTreeNode*, GetTreeList()->GetNode(cell.y));
	assert (node != NULL);
	JString file = node->GetFullPathAndName();
	if (node->GetType() == JUNIXDirEntry::kDir)
		{
		JString cmd = "rm -rf \"" + file + "\"";
		JString errOutput;
		JError err = JRunProgram(cmd, &errOutput);
		if (!err.OK())
			{
			ok = kFalse;
			}
		}
	else
		{
		JError err = JRemoveFile(file);
		if (!err.OK())
			{
			ok = kFalse;
			}
		}
	if (ok)
		{
		delete node;
		}
	return ok;
}

/******************************************************************************
 DuplicateSelected (public)

 ******************************************************************************/

void
SyGFileTreeTable::DuplicateSelected()
{
	JTableSelection& s = GetTableSelection();
	JTableSelectionIterator iter(&s);

	JPoint cell;
	JArray<JIndex> nodes;
	JBoolean found	= kFalse;
	JBoolean ok		= kTrue;
	while(iter.Next(&cell) && ok)
		{
		found 		= kTrue;
		ok 			= DuplicateCell(cell, &nodes);
		}
	if (!found && GetEditedCell(&cell))
		{
		DuplicateCell(cell, &nodes);
		}
	s.ClearSelection();
	JSize count = nodes.GetElementCount();
	for (JSize i = 1; i <= count; i++)
		{
		s.SelectCell(nodes.GetElement(i), 2);
		}
}

/******************************************************************************
 DuplicateCell (private)

 ******************************************************************************/

JBoolean
SyGFileTreeTable::DuplicateCell
	(
	const JPoint 	cell, 
	JArray<JIndex>*	nodes
	)
{
	JTreeNode* jnode = itsFileTreeList->GetNode(cell.y);
	SyGFileTreeNode* node = dynamic_cast(SyGFileTreeNode*, jnode);
	assert(node != NULL);
	JTreeNode* jparent = node->GetParent();
	JNamedTreeNode* parent = dynamic_cast(JNamedTreeNode*, jparent);
	assert(parent != NULL);
	JString file = node->GetFullPathAndName();
	
	JString tempName = file + ".copy";
	
	JIndex number = 1;
	while (JNameUsed(tempName) && (number <= 25))
		{
		tempName = file + ".copy" + JString(number);
		number++;
		}
	number = 1;
	if (JNameUsed(tempName))
		{
		JIndex findex;
		if (file.LocateSubstring(".", &findex))
			{
			file.RemoveSubstring(findex, file.GetLength());
			}
		}
	while (JNameUsed(tempName) && (number <= 25))
		{
		tempName = file + "." + JString(number);
		}
	if (!JNameUsed(tempName))
		{
		JString sysCmd = "cp -Rd \"" + file + "\" \"" + tempName + "\"";
		if (SystemCall(sysCmd))
			{
			JUNIXDirEntry* entry = new JUNIXDirEntry(tempName);
			assert(entry != NULL);
			SyGFileTreeNode* fnode = 
				new SyGFileTreeNode(itsFileTree, entry, NULL, entry->GetName(), node->IsOpenable());
			assert(fnode != NULL);
			parent->InsertSorted(fnode);
			JIndex findex;
			if (GetTreeList()->FindNode(fnode, &findex))
				{
				nodes->AppendElement(findex);
				}
			}
		else
			{
			return kFalse;
			}
		}
	return kTrue;
}


/******************************************************************************
 PerformOpen (public)

 ******************************************************************************/

void
SyGFileTreeTable::PerformOpen()
{
	ClearIncrementalSearchBuffer();
	JTableSelectionIterator iter(&(GetTableSelection()));
	JPoint cell;
	JBoolean found 	= kFalse;		
	while(iter.Next(&cell))
		{
		found 		= kTrue;
		PerformAction(cell.y, kTrue, kFalse);
		}
	if (!found && GetEditedCell(&cell))
		{
		found		= kTrue;
		PerformAction(cell.y, kTrue, kFalse);
		}
	if (!found)
		{
		JString path;
		if (SyGGetChooseSaveFile()->ChooseRPath("Select path", NULL, GetCurrentPath(), &path))
			{
			if (SyGGetChooseSaveFile()->IsOpeningInNewWindow())
				{
				SyGGetApplication()->NewChildWindow(path);
				}
			else
				{
				JTreeNode* jnode = itsFileTree->GetRoot();
				SyGFileTreeNode* base = dynamic_cast(SyGFileTreeNode*, jnode);
				assert(base != NULL);
				JError err = base->GoTo(path);
				GetWindow()->GetDirector()->Activate();
				}
			}
		}
}

/******************************************************************************
 PointInNode

 ******************************************************************************/

JBoolean 
SyGFileTreeTable::PointInNode
	(
	const JIndex index, 
	const JPoint pt
	)
{
	JPoint cell;
	JXNamedTreeListWidget::NodePart part;
	GetNode(pt, &cell, &part);

	if (cell.y != (JCoordinate)index)
		{
		return kFalse;
		}

	if ((part == JXNamedTreeListWidget::kInImage) || 
		(part == JXNamedTreeListWidget::kInText))
		{
		return kTrue;
		}

	return kFalse;
}

/******************************************************************************
 TableToTreeCell (private)

 ******************************************************************************/

JCoordinate
SyGFileTreeTable::TableToTreeCell
	(
	const JIndex 		index,
	const JCoordinate	position
	)
	const
{
	JPoint cell(GetNodeColIndex(), index);
	JRect rect = GetCellRect(cell);
	JSize indent = GetNodeIndent(index);
	JCoordinate treePos = position - indent - rect.left;
	return treePos;
}

/******************************************************************************
 WindowIconified (public)

 ******************************************************************************/

void
SyGFileTreeTable::WindowIconified()
{
	JXGetApplication()->RemoveIdleTask(itsFileUpdateTask);
	ClearIncrementalSearchBuffer();
}

/******************************************************************************
 WindowDeiconified (public)

 ******************************************************************************/

void
SyGFileTreeTable::WindowDeiconified()
{
	JXGetApplication()->InstallIdleTask(itsFileUpdateTask);
}

/******************************************************************************
 ViewManPage (public)

 ******************************************************************************/

void
SyGFileTreeTable::ViewManPage()
{
	JTableSelection& s = GetTableSelection();
	JPoint cell;
	if (s.GetSingleSelectedCell(&cell) || GetEditedCell(&cell))
		{
		JTreeNode* jnode = itsFileTreeList->GetNode(cell.y);
		SyGFileTreeNode* node = dynamic_cast(SyGFileTreeNode*, jnode);
		assert(node != NULL);
		JString name = node->GetName();
		SyGGetApplication()->ViewManPage(name);
		}
	else
		{
		SyGGetManPageDialog()->Activate();
		}
}

/******************************************************************************
 UpdateEditMenu (private)

 ******************************************************************************/

void
SyGFileTreeTable::UpdateEditMenu()
{
	JXTEBase* te = GetEditMenuHandler();
	JIndex index;
	if (te->EditMenuCmdToIndex(JTextEditor::kSelectAllCmd, &index))
		{
		itsEditMenu->EnableItem(index);
		}
}

/******************************************************************************
 HandleEditMenu

 ******************************************************************************/

void
SyGFileTreeTable::HandleEditMenu
	(
	const JIndex index
	)
{
	ClearIncrementalSearchBuffer();
	JXTEBase* te = GetEditMenuHandler();
	JTextEditor::CmdIndex cmd;
	if (te->EditMenuIndexToCmd(index, &cmd))
		{
		if (cmd == JTextEditor::kSelectAllCmd)
			{
			SelectAll();
			}
		}
}

/******************************************************************************
 GetColTitle (public)

 ******************************************************************************/

JString
SyGFileTreeTable::GetColTitle
	(
	const JIndex index
	)
	const
{
	JString str;
	if (index == kLeadingCols)
		{
		str = "Name";
		}
	else
		{
		GetFMColTitle(itsVisibleCols, kLeadingCols, index, &str);
		}
	return str;
}

/******************************************************************************
 GetBufferWidth (public)

 ******************************************************************************/

JCoordinate
SyGFileTreeTable::GetBufferWidth
	(
	const JIndex index
	)
	const
{
	if (index == kLeadingCols)
		{
		if (GetRowCount() == 0)
			{
			return 0;
			}
		return GetImageWidth(1);
		}
	return GetFMBufferWidth(itsVisibleCols, kLeadingCols, index);
}

/******************************************************************************
 IsCurrentCol (public)

 ******************************************************************************/

JBoolean
SyGFileTreeTable::IsCurrentCol
	(
	const JIndex index
	)
	const
{
	GFMColType type = GetFMColType(itsVisibleCols, kLeadingCols, index);
	if (type == itsCurrentColType)
		{
		return kTrue;
		}
	return kFalse;
}

/******************************************************************************
 SetCurrentCol (public)

 ******************************************************************************/

void
SyGFileTreeTable::SetCurrentCol
	(
	const JIndex index
	)
{
	GFMColType type = GetFMColType(itsVisibleCols, kLeadingCols, index);
	SetCurrentCol(type);
}

/******************************************************************************
 SetCurrentCol (public)

 ******************************************************************************/

void
SyGFileTreeTable::SetCurrentCol
	(
	const GFMColType type
	)
{
	ClearIncrementalSearchBuffer();
	if ((type != itsCurrentColType) && (type != kGFMMode))
		{
		itsCurrentColType = type;
		JTreeNode* jnode  = itsFileTree->GetRoot();
		SyGFileTreeNode* root = dynamic_cast(SyGFileTreeNode*, jnode);
		assert(root != NULL);
		itsFileTree->ChangeSortByType(type);

		TableRefresh();
		}
}

/******************************************************************************
 RunCommand (public)

 ******************************************************************************/

void
SyGFileTreeTable::RunCommand()
{
	itsRunOnSelected = kFalse;
	StartRunCommand();
}

/******************************************************************************
 StartRunCommand (private)

 ******************************************************************************/

void
SyGFileTreeTable::StartRunCommand()
{
	assert(itsRunDialog == NULL);
	itsRunDialog = new SyGRunCmdDialog(JXGetApplication());
	assert(itsRunDialog != NULL);
	itsRunDialog->BeginDialog();
	ListenTo(itsRunDialog);
}

/******************************************************************************
 GetRunCommand (private)

 ******************************************************************************/

void
SyGFileTreeTable::GetRunCommand()
{
	JXGetApplication()->DisplayBusyCursor();
	
	assert(itsRunDialog != NULL);
	JString cmd 		= itsRunDialog->GetCommand();
	JTableSelection& s 	= GetTableSelection();
	if (itsRunOnSelected || (s.GetSelectedCellCount() != 0) || IsEditing())
		{
		JTableSelectionIterator iter(&s);

		JPoint cell;
		JBoolean found 	= kFalse;
		while(iter.Next(&cell))
			{
			found		= kTrue;
			JTreeNode* jnode = itsFileTreeList->GetNode(cell.y);
			SyGFileTreeNode* node =
				dynamic_cast(SyGFileTreeNode*, jnode);
			assert(node != NULL);
			SyGHandleCustomFileAction(node->GetFullPathAndName(), cmd);
			}
		if (!found && GetEditedCell(&cell))
			{
			JTreeNode* jnode = itsFileTreeList->GetNode(cell.y);
			SyGFileTreeNode* node =
				dynamic_cast(SyGFileTreeNode*, jnode);
			assert(node != NULL);
			SyGHandleCustomFileAction(node->GetFullPathAndName(), cmd);			
			}
		}
	else
		{
		JError err = JChangeDirectory(GetCurrentPath());
		if (err.OK())
			{
			JSimpleProcess::Create(cmd, kTrue);
			}
		}
}

/******************************************************************************
 LoadToolBarDefaults (public)

 ******************************************************************************/

void
SyGFileTreeTable::LoadToolBarDefaults
	(
	JXToolBar* toolBar
	)
{
	toolBar->NewGroup();
	toolBar->AppendButton(itsTreeMenu, kShowFilterCmd);
	toolBar->NewGroup();
	toolBar->AppendButton(itsTreeMenu, kShowHiddenCmd);
	toolBar->NewGroup();
	toolBar->AppendButton(itsTreeMenu, kShowUserCmd);
	toolBar->AppendButton(itsTreeMenu, kShowGroupCmd);
	toolBar->AppendButton(itsTreeMenu, kShowSizeCmd);
	toolBar->AppendButton(itsTreeMenu, kShowModeCmd);
	toolBar->AppendButton(itsTreeMenu, kShowDateCmd);
}

/******************************************************************************
 AdjustToTree (virtual protected)

 ******************************************************************************/

void
SyGFileTreeTable::AdjustToTree()
{
	const JIndex colIndex = GetNodeColIndex();
	const JSize count     = GetRowCount();
	for (JIndex i=1; i<=count; i++)
		{
		JTreeNode* jnode = itsFileTreeList->GetNode(i);
		SyGFileTreeNode* node =
			dynamic_cast(SyGFileTreeNode*, jnode);
		assert(node != NULL);
		const JUNIXDirEntry* entry  = node->GetDirEntry();		
		JUNIXDirEntry::Type type 	= entry->GetType();
		if ((type == JUNIXDirEntry::kDirLink) || 
			(type == JUNIXDirEntry::kFileLink))
			{
			SetCellStyle(JPoint(colIndex, i),
						 JFontStyle(kFalse, kTrue, 0, kFalse));
			}
		else if ((type == JUNIXDirEntry::kBrokenLink) ||
				 (type == JUNIXDirEntry::kUnknownLink))
			{
			JColorIndex color = GetColormap()->GetGray60Color();
			SetCellStyle(JPoint(colIndex, i),
						 JFontStyle(kFalse, kTrue, 0, kFalse, color));
			}
		}

	JXNamedTreeListWidget::AdjustToTree();
}

/******************************************************************************
 GetMinCellWidth (virtual protected)

 ******************************************************************************/

JSize
SyGFileTreeTable::GetMinCellWidth
	(
	const JPoint& cell
	)
	const
{
	if (cell.x > (JCoordinate)GetNodeColIndex())
		{
		JString str;
		GetCellString(cell, &str);
		const JCoordinate theBuffer = 
			GetFMBufferWidth(itsVisibleCols, kLeadingCols, cell.x);
		JSize size;
		JString fontname = GetFont(&size);
		JSize w;
		GFMColType type = GetFMColType(itsVisibleCols, kLeadingCols, cell.x);

		if (type == kGFMMode)
			{
			w = GetFontManager()->GetCharWidth( JGetDefaultFontName(),
						kJDefaultFontSize, JFontStyle(), kPermTestChar) * 9;
			w += JLAbs(theBuffer);
			}
		else
			{
			w = GetFontManager()->GetStringWidth(fontname, 
						size, JFontStyle(), str) + 2 * JLAbs(theBuffer);
			}
		JSize wT = GetFontManager()->GetStringWidth(fontname, 
			size, JFontStyle(), GetColTitle(cell.x)) + 2 * JLAbs(theBuffer);
		if (wT > w)
			{
			return wT;
			}
		return w;
		}
	return JXNamedTreeListWidget::GetMinCellWidth(cell);
}

/******************************************************************************
 GetCellString (private)

 ******************************************************************************/

void
SyGFileTreeTable::GetCellString
	(
	const JPoint 	cell, 
	JString* 		str
	)
	const
{
	const JTreeList* treeList = GetTreeList();
	SyGFileTreeNode* entry = dynamic_cast(SyGFileTreeNode*, treeList->GetNode(cell.y));
	assert (entry != NULL);

	JUNIXDirEntry* dirEntry = entry->GetDirEntry();
	assert (dirEntry != NULL);
	
	GFMColType type = GetFMColType(itsVisibleCols, kLeadingCols, cell.x);

	if (type == kGFMUID)
		{
		*str = dirEntry->GetUserName();
		}
		
	else if (type == kGFMGID)
		{
		*str = dirEntry->GetGroupName();
		}

	else if (type == kGFMSize)
		{
		if (entry->IsOpenable())
			{
			*str = "-";
			}
		else
			{
			JSize size = dirEntry->GetSize();
			
			if (size > 5000)
				{
				JString sStr((float)size/1000.0, 1);
				*str = sStr + " K";
				}
				
			else if (size > 5000000)
				{
				JString sStr((float)size/1000000.0, 1);
				*str = sStr + " M";
				}
			else
				{
				*str = size;
				}
			}
		}

	else if (type == kGFMMode)
		{
		*str 	= JGetPermissionsString(dirEntry->GetMode());
		}

	else if (type == kGFMDate)
		{
		*str = JConvertToTimeStamp(dirEntry->GetModTime());
		}
}

/******************************************************************************
 HasSelection (public)

 ******************************************************************************/

JBoolean
SyGFileTreeTable::HasSelection()
	const
{
	JBoolean selected 	= GetTableSelection().HasSelection();
	if (!selected)
		{
		selected 		= IsEditing();
		}
	return selected;
}

/******************************************************************************
 HasSelection (public)

 ******************************************************************************/

JBoolean
SyGFileTreeTable::HasSelectionAndFocus()
	const
{
	JBoolean selected 	= GetTableSelection().HasSelection();
	if (selected && HasFocus())
		{
		return kTrue;
		}
	if (!selected)
		{
		selected 		= IsEditing();
		}
	return selected;
}
