/******************************************************************************
 JXUNIXDirTable.cc

	Displays a directory listing stored in a JUNIXDirInfo object.
	To filter the displayed files, create a derived class of JUNIXDirInfo.

	BASE CLASS = JXTable

	Copyright © 1996 by Glenn W. Bach.
	Copyright © 1998 by John Lindal.  All rights reserved.

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

#include <JXUNIXDirTable.h>
#include <JXUNIXDirUpdateTask.h>
#include <JUNIXDirInfo.h>
#include <JUNIXDirEntry.h>
#include <jXCSFIcons.h>

#include <JXWindow.h>
#include <JXWindowPainter.h>
#include <JXImage.h>
#include <JXColormap.h>
#include <JXSelectionManager.h>
#include <JXDNDManager.h>
#include <jXGlobals.h>
#include <jXUtil.h>

#include <JTableSelection.h>
#include <JString.h>
#include <JFontManager.h>
#include <jDirUtil.h>
#include <jFileUtil.h>
#include <jASCIIConstants.h>
#include <jAssert.h>

const JIndex kIconColumn = 1;
const JIndex kTextColumn = 2;
const JSize kColumnCount = 2;

const JCoordinate kIconColWidth   = 20;
const JCoordinate kTextColPadding = 5;
const JCoordinate kHMarginWidth   = 3;
const JCoordinate kVMarginWidth   = 1;

const JCharacter* JXUNIXDirTable::kFileDblClicked = "FileDblClicked::JXUNIXDirTable";

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

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

JXUNIXDirTable::JXUNIXDirTable
	(
	JUNIXDirInfo*		data,
	JXScrollbarSet*		scrollbarSet,
	JXContainer*		enclosure,
	const HSizingOption	hSizing,
	const VSizingOption	vSizing,
	const JCoordinate	x,
	const JCoordinate	y,
	const JCoordinate	w,
	const JCoordinate	h
	)
	:
	JXTable(10, 10, scrollbarSet, enclosure, hSizing,vSizing, x,y, w,h)
{
	assert( data != NULL );
	itsDirInfo = data;

	itsActiveCells = new JRunArray<JBoolean>;
	assert( itsActiveCells != NULL );

	itsDirUpdateTask = new JXUNIXDirUpdateTask(this);
	assert( itsDirUpdateTask != NULL );
	(JXGetApplication())->InstallIdleTask(itsDirUpdateTask);

	itsDragType                  = kInvalidDrag;
	itsAllowSelectFilesFlag      = kTrue;
	itsAllowSelectMultipleFlag   = kFalse;
	itsAllowDblClickInactiveFlag = kFalse;
	itsSelectWhenChangePathFlag  = kTrue;
	itsMaxStringWidth            = 0;
	itsReselectFlag              = kFalse;

	itsKeyBuffer = new JString;
	assert( itsKeyBuffer != NULL );

	itsReselectNameList = new JPtrArray<JString>;
	assert( itsReselectNameList != NULL );

	itsFileIcon = new JXImage(GetDisplay(), GetColormap(), JXGetPlainFileIcon());
	assert( itsFileIcon != NULL );
	itsFileIcon->ConvertToRemoteStorage();

	itsFolderIcon = new JXImage(GetDisplay(), GetColormap(), JXGetFolderIcon());
	assert( itsFolderIcon != NULL );
	itsFolderIcon->ConvertToRemoteStorage();

	itsExecIcon = new JXImage(GetDisplay(), GetColormap(), JXGetExecIcon());
	assert( itsExecIcon != NULL );
	itsExecIcon->ConvertToRemoteStorage();

	itsUnknownIcon = new JXImage(GetDisplay(), GetColormap(), JXGetUnknownFileTypeIcon());
	assert( itsUnknownIcon != NULL );
	itsUnknownIcon->ConvertToRemoteStorage();

	const JIndex blackColor = (GetColormap())->GetBlackColor();
	SetRowBorderInfo(0, blackColor);
	SetColBorderInfo(0, blackColor);
	AppendCol();
	AppendCol();
	SetColWidth(kIconColumn, kIconColWidth);
	SetDefaultRowHeight((GetFontManager())->
							GetLineHeight(JGetDefaultFontName(), kJXDefaultFontSize, JFontStyle()) +
						2*kVMarginWidth);
	AdjustTableContents();
	ListenTo(itsDirInfo);

	SetBackColor(GetFocusColor());
}

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

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

JXUNIXDirTable::~JXUNIXDirTable()
{
	delete itsActiveCells;
	delete itsDirUpdateTask;
	delete itsKeyBuffer;

	itsReselectNameList->DeleteAll();
	delete itsReselectNameList;

	delete itsFileIcon;
	delete itsFolderIcon;
	delete itsExecIcon;
	delete itsUnknownIcon;
}

/******************************************************************************
 GetPath

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

const JString&
JXUNIXDirTable::GetPath()
	const
{
	return itsDirInfo->GetCWD();
}

/******************************************************************************
 HasSelection

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

JBoolean
JXUNIXDirTable::HasSelection()
	const
{
	return (GetTableSelection()).HasSelection();
}

/******************************************************************************
 HasSingleSelection

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

JBoolean
JXUNIXDirTable::HasSingleSelection()
	const
{
	const JTableSelection& s = GetTableSelection();
	JPoint cell1, cell2;
	return JI2B(
		s.GetFirstSelectedCell(&cell1) &&
		s.GetLastSelectedCell(&cell2) &&
		cell1.y == cell2.y);
}

/******************************************************************************
 GetFirstSelection

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

JBoolean
JXUNIXDirTable::GetFirstSelection
	(
	const JUNIXDirEntry** entry
	)
	const
{
	JPoint cell;
	if ((GetTableSelection()).GetFirstSelectedCell(&cell))
		{
		*entry = &(itsDirInfo->GetEntry(cell.y));
		return kTrue;
		}
	else
		{
		return kFalse;
		}
}

/******************************************************************************
 GetSelection

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

JBoolean
JXUNIXDirTable::GetSelection
	(
	JPtrArray<JUNIXDirEntry>* entryList
	)
	const
{
	entryList->RemoveAll();

	JTableSelectionIterator iter(&(GetTableSelection()));

	JPoint cell;
	while (iter.Next(&cell) && cell.x == 1)
		{
		entryList->Append(const_cast<JUNIXDirEntry*>(&(itsDirInfo->GetEntry(cell.y))));
		}

	return JNegate( entryList->IsEmpty() );
}

/******************************************************************************
 SelectSingleEntry

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

JBoolean
JXUNIXDirTable::SelectSingleEntry
	(
	const JIndex	index,
	const JBoolean	scroll
	)
{
	if (ItemIsActive(index))
		{
		JTableSelection& s = GetTableSelection();
		s.ClearSelection();
		s.SelectRow(index);

		s.SetBoat(JPoint(kColumnCount, index));
		s.SetAnchor(JPoint(1, index));
		if (scroll)
			{
			TableScrollToCell(JPoint(1, index));
			}
		itsKeyBuffer->Clear();
		return kTrue;
		}
	else
		{
		return kFalse;
		}
}

/******************************************************************************
 SelectFirstEntry

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

void
JXUNIXDirTable::SelectFirstEntry
	(
	const JBoolean scroll
	)
{
	JIndex index;
	if (GetNextSelectable(0, kFalse, &index))
		{
		SelectSingleEntry(index, scroll);
		}
}

/******************************************************************************
 SelectLastEntry

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

void
JXUNIXDirTable::SelectLastEntry
	(
	const JBoolean scroll
	)
{
	JIndex index;
	if (GetPrevSelectable(GetRowCount()+1, kFalse, &index))
		{
		SelectSingleEntry(index, scroll);
		}
}

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

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

void
JXUNIXDirTable::SelectAll()
{
	(GetTableSelection()).SelectAll();
	CleanSelection();
}

/******************************************************************************
 CleanSelection (private)

	Only active files are allowed in a multiple selection.

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

void
JXUNIXDirTable::CleanSelection()
{
	JTableSelection& s = GetTableSelection();
	JTableSelectionIterator iter(&s);

	JPoint cell;
	while (iter.Next(&cell) && cell.x == 1)
		{
		if (!ItemIsSelectable(cell.y, kTrue))
			{
			s.SelectRow(cell.y, kFalse);
			}
		}

	itsKeyBuffer->Clear();
}

/******************************************************************************
 ShowHidden

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

void
JXUNIXDirTable::ShowHidden
	(
	const JBoolean showHidden
	)
{
	itsDirInfo->ShowHidden(showHidden);
}

/******************************************************************************
 AllowSelectFiles

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

void
JXUNIXDirTable::AllowSelectFiles
	(
	const JBoolean allowSelectFiles,
	const JBoolean allowMultiple
	)
{
	itsAllowSelectMultipleFlag = allowMultiple;

	if (allowSelectFiles != itsAllowSelectFilesFlag)
		{
		itsAllowSelectFilesFlag = allowSelectFiles;
		AdjustTableContents();
		}

	if (!itsAllowSelectMultipleFlag && !HasSingleSelection())
		{
		(GetTableSelection()).ClearSelection();
		}
}

/******************************************************************************
 GoToSelectedDirectory

	If there is a single item selected, and it is a directory, we switch to
	it and return kTrue.

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

JBoolean
JXUNIXDirTable::GoToSelectedDirectory()
{
	const JUNIXDirEntry* entry;
	if (HasSingleSelection() && GetFirstSelection(&entry))
		{
		JError err = JNoError();

		if (entry->GetName() == "..")
			{
			err = itsDirInfo->GoUp();
			}
		else if (entry->IsDirectory())
			{
			// need local copy because GoDown() deletes entry

			const JString dir = entry->GetName();
			err = itsDirInfo->GoDown(dir);
			}
		else
			{
			return kFalse;
			}

		if (err.OK())
			{
			return kTrue;
			}
		else
			{
			err.ReportError();
			return kFalse;
			}
		}

	return kFalse;
}

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

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

void
JXUNIXDirTable::TableDrawCell
	(
	JPainter&		p,
	const JPoint&	cell,
	const JRect&	rect
	)
{
	const JUNIXDirEntry& entry = itsDirInfo->GetEntry(cell.y);
	HilightIfSelected(p, cell, rect);

	// draw icon

	if (cell.x == (JCoordinate) kIconColumn)
		{
		JXImage* icon = NULL;
		if (entry.IsDirectory())
			{
			icon = itsFolderIcon;
			}
		else if (entry.IsFile())
			{
			if (entry.IsExecutable())
				{
				icon = itsExecIcon;
				}
			else
				{
				icon = itsFileIcon;
				}
			}
		else if (entry.IsUnknown())
			{
			icon = itsUnknownIcon;
			}

		if (icon != NULL)
			{
			p.Image(*icon, icon->GetBounds(), rect);
			}
		}

	// draw name

	else if (cell.x == (JCoordinate) kTextColumn)
		{
		const JBoolean italic = entry.IsLink();

		JColorIndex color = (GetColormap())->GetBlackColor();
		if (!itsActiveCells->GetElement(cell.y))
			{
			color = (GetColormap())->GetGray40Color();
			}

		p.SetFont(JGetDefaultFontName(), kJXDefaultFontSize,
				  JFontStyle(kFalse, italic, 0, kFalse, color));

		JRect r = rect;
		r.left += kHMarginWidth;
		p.String(r, entry.GetName(), JPainter::kHAlignLeft, JPainter::kVAlignCenter);
		}
}

/******************************************************************************
 HandleMouseDown (virtual protected)

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

void
JXUNIXDirTable::HandleMouseDown
	(
	const JPoint&			pt,
	const JXMouseButton		button,
	const JSize				clickCount,
	const JXButtonStates&	buttonStates,
	const JXKeyModifiers&	modifiers
	)
{
	itsKeyBuffer->Clear();
	JTableSelection& s = GetTableSelection();

	itsDragType = kInvalidDrag;
	if (button > kJXRightButton)
		{
		ScrollForWheel(button, modifiers);
		return;
		}

	JPoint cell;
	if (!GetCell(pt, &cell))
		{
		s.ClearSelection();
		return;
		}

	const JPoint newBoat   = JPoint(kColumnCount, cell.y);
	const JPoint newAnchor = JPoint(1, cell.y);

	const JBoolean extendSelection = JI2B( modifiers.shift()   && itsAllowSelectMultipleFlag );
	const JBoolean selectDiscont   = JI2B( modifiers.control() && itsAllowSelectMultipleFlag );
	if (itsAllowSelectMultipleFlag &&
		((button == kJXLeftButton && extendSelection) || button == kJXRightButton))
		{
		if (s.OKToExtendSelection())
			{
			s.ExtendSelection(newBoat);
			CleanSelection();
			itsDragType = kSelectRangeDrag;
			}
		}
	else if (itsAllowSelectMultipleFlag &&
			 button == kJXLeftButton && selectDiscont && s.IsSelected(cell))
		{
		itsDragType = kDeselectCellDrag;
		s.SelectRow(cell.y, kFalse);
		s.ClearBoat();
		s.ClearAnchor();
		}
	else if (itsAllowSelectMultipleFlag &&
			 button == kJXLeftButton && selectDiscont)
		{
		itsDragType = kSelectCellDrag;
		s.SelectRow(cell.y, kTrue);
		CleanSelection();
		s.SetBoat(newBoat);
		s.SetAnchor(newAnchor);
		}
	else if (button == kJXLeftButton)
		{
		if (clickCount == 1 && SelectSingleEntry(cell.y, kFalse))
			{
			itsDragType =
				itsAllowSelectMultipleFlag ? kSelectRangeDrag : kSelectSingleDrag;
			}
		else if (clickCount == 2)
			{
			s.SetBoat(newBoat);
			s.SetAnchor(newAnchor);
			HandleDoubleClick(cell.y);		// can delete us
			return;
			}
		}

	// anything here also needs to be done before HandleDoubleClick()

	(GetWindow())->Update();
}

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

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

void
JXUNIXDirTable::HandleMouseDrag
	(
	const JPoint&			pt,
	const JXButtonStates&	buttonStates,
	const JXKeyModifiers&	modifiers
	)
{
	if (itsDragType == kInvalidDrag)
		{
		return;
		}

	ScrollForDrag(pt);
	JTableSelection& s = GetTableSelection();

	JPoint cell;
	const JBoolean ok = GetCell(JPinInRect(pt, GetBounds()), &cell);
	assert( ok );
	if (cell.y == (s.GetBoat()).y)
		{
		return;
		}

	const JPoint newBoat   = JPoint(kColumnCount, cell.y);
	const JPoint newAnchor = JPoint(1, cell.y);

	if (itsDragType == kSelectSingleDrag)
		{
		SelectSingleEntry(cell.y, kFalse);
		}
	else if (itsDragType == kSelectCellDrag)
		{
		s.SelectRow(cell.y, kTrue);
		CleanSelection();
		s.SetBoat(newBoat);
		s.SetAnchor(newAnchor);
		}
	else if (itsDragType == kDeselectCellDrag)
		{
		s.SelectRow(cell.y, kFalse);
		}
	else if (itsDragType == kSelectRangeDrag)
		{
		s.ExtendSelection(newBoat);
		CleanSelection();
		}

	(GetWindow())->Update();
}

/******************************************************************************
 HandleDoubleClick (private)

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

void
JXUNIXDirTable::HandleDoubleClick
	(
	const JIndex index
	)
{
	const JUNIXDirEntry& entry = itsDirInfo->GetEntry(index);
	if (ItemIsActive(index))
		{
		if (entry.IsDirectory())
			{
			GoToSelectedDirectory();
			}
		else
			{
			Broadcast(FileDblClicked(entry, kTrue));
			}
		}
	else if (itsAllowDblClickInactiveFlag && entry.IsFile())
		{
		Broadcast(FileDblClicked(entry, kFalse));
		}
}

/******************************************************************************
 HandleKeyPress (virtual protected)

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

void
JXUNIXDirTable::HandleKeyPress
	(
	const int				key,
	const JXKeyModifiers&	modifiers
	)
{
	JPoint topSelCell;
	JTableSelection& s             = GetTableSelection();
	const JBoolean hadSelection    = s.GetFirstSelectedCell(&topSelCell);
	const JBoolean extendSelection = JI2B( modifiers.shift() && itsAllowSelectMultipleFlag );

	if (key == ' ')
		{
		itsKeyBuffer->Clear();
		s.ClearSelection();
		}

	else if (key == kJReturnKey)
		{
		if (hadSelection)
			{
			HandleDoubleClick(topSelCell.y);	// can delete us
			return;
			}
		}

	else if (key == kJUpArrow)
		{
		JIndex index;
		if (modifiers.meta())
			{
			const JError err = itsDirInfo->GoUp();
			if (!err.OK())
				{
				err.ReportError();
				}
			}
		else if (hadSelection && extendSelection)
			{
			if (s.OKToExtendSelection())
				{
				if (GetPrevSelectable((s.GetBoat()).y, kTrue, &index))
					{
					s.ExtendSelection(JPoint(kColumnCount, index));
					CleanSelection();
					}
				TableScrollToCell(JPoint(1, (s.GetBoat()).y));
				}
			}
		else if (hadSelection && GetPrevSelectable(topSelCell.y, kFalse, &index))
			{
			SelectSingleEntry(index);
			}
		else if (hadSelection)
			{
			SelectSingleEntry(topSelCell.y);
			}
		else
			{
			SelectLastEntry();
			}
		}

	else if (key == kJDownArrow)
		{
		JIndex index;
		if (modifiers.meta())
			{
			GoToSelectedDirectory();
			}
		else if (hadSelection && extendSelection)
			{
			if (s.OKToExtendSelection())
				{
				if (GetNextSelectable((s.GetBoat()).y, kTrue, &index))
					{
					s.ExtendSelection(JPoint(kColumnCount, index));
					CleanSelection();
					}
				TableScrollToCell(JPoint(1, (s.GetBoat()).y));
				}
			}
		else if (hadSelection && GetNextSelectable(topSelCell.y, kFalse, &index))
			{
			SelectSingleEntry(index);
			}
		else if (hadSelection)
			{
			SelectSingleEntry(topSelCell.y);
			}
		else
			{
			SelectFirstEntry();
			}
		}

	else if (key == kJLeftArrow)
		{
		const JError err = itsDirInfo->GoUp();
		if (!err.OK())
			{
			err.ReportError();
			}
		}

	else if (key == kJRightArrow)
		{
		GoToSelectedDirectory();
		}

	else if (itsAllowSelectMultipleFlag &&
			 (key == 'a' || key == 'A') && modifiers.meta() && !modifiers.shift())
		{
		SelectAll();
		}

	else if (JXIsPrint(key) && !modifiers.control() && !modifiers.meta())
		{
		itsKeyBuffer->AppendCharacter(key);

		JIndex index;
		if (ClosestMatch(*itsKeyBuffer, &index))
			{
			JString saveBuffer = *itsKeyBuffer;
			SelectSingleEntry(index);
			*itsKeyBuffer = saveBuffer;
			}
		}

	else
		{
		JXTable::HandleKeyPress(key, modifiers);
		}

	// anything here also needs to be done before HandleDoubleClick()

	(GetWindow())->Update();
}

/******************************************************************************
 InstallShortcuts

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

void
JXUNIXDirTable::InstallShortcuts()
{
	JXKeyModifiers modifiers;
	modifiers.SetState(kJXMetaKeyIndex, kTrue);

	JXWindow* window = GetWindow();
	window->InstallShortcut(this, kJUpArrow, modifiers);
	window->InstallShortcut(this, kJDownArrow, modifiers);
}

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

	Alt-up and Alt-down are handled as if we had focus.

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

void
JXUNIXDirTable::HandleShortcut
	(
	const int				key,
	const JXKeyModifiers&	modifiers
	)
{
	if ((key == kJUpArrow || key == kJDownArrow) && modifiers.meta())
		{
		if (Focus())
			{
			HandleKeyPress(key, modifiers);
			}
		}
	else
		{
		JXTable::HandleShortcut(key, modifiers);
		}
}

/******************************************************************************
 WillAcceptDrop (virtual protected)

	Accept text/uri-list from anybody but ourselves.

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

JBoolean
JXUNIXDirTable::WillAcceptDrop
	(
	const JArray<Atom>&	typeList,
	Atom*				action,
	const Time			time,
	const JXWidget*		source
	)
{
	// dropping on ourselves makes no sense

	if (this == const_cast<JXWidget*>(source))
		{
		return kFalse;
		}

	// we accept drops of type text/uri-list

	const Atom urlXAtom = (GetSelectionManager())->GetURLXAtom();

	const JSize typeCount = typeList.GetElementCount();
	for (JIndex i=1; i<=typeCount; i++)
		{
		if (typeList.GetElement(i) == urlXAtom)
			{
			*action = (GetDNDManager())->GetDNDActionPrivateXAtom();
			return kTrue;
			}
		}

	return kFalse;
}

/******************************************************************************
 HandleDNDDrop (virtual protected)

	This is called when the data is dropped.  The data is accessed via
	the selection manager, just like Paste.

	Since we only accept text/uri-list, we don't bother to check typeList.

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

void
JXUNIXDirTable::HandleDNDDrop
	(
	const JPoint&		pt,
	const JArray<Atom>&	typeList,
	const Atom			action,
	const Time			time,
	const JXWidget*		source
	)
{
	JXSelectionManager* selMgr = GetSelectionManager();

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

			const JSize fileCount = fileNameList.GetElementCount();
			if (fileCount > 0)
				{
				const JString* entryName = fileNameList.FirstElement();
				JString path, name;
				if (JDirectoryExists(*entryName))
					{
					path = *entryName;
					}
				else if (JFileExists(*entryName))
					{
					JSplitPathAndName(*entryName, &path, &name);
					}

				const JError err = itsDirInfo->GoTo(path);
				if (!err.OK())
					{
					err.ReportError();
					}
				else if (!name.IsEmpty())
					{
					JTableSelection& s = GetTableSelection();
					s.ClearSelection();
					for (JIndex i=1; i<=fileCount; i++)
						{
						entryName = fileNameList.NthElement(i);
						if (JFileExists(*entryName))
							{
							JSplitPathAndName(*entryName, &path, &name);
							JIndex index;
							if (itsDirInfo->FindEntry(name, &index) &&
								ItemIsActive(index))
								{
								if (!s.HasSelection())
									{
									const JBoolean ok = SelectSingleEntry(index);
									assert( ok );
									}
								else
									{
									s.SelectRow(index);
									s.ClearBoat();
									s.ClearAnchor();
									}
								if (!itsAllowSelectMultipleFlag)
									{
									break;
									}
								}
							}
						}
					CleanSelection();
					}
				}

			fileNameList.DeleteAll();
			urlList.DeleteAll();
			}

		selMgr->DeleteSelectionData(&data, delMethod);
		}
}

/******************************************************************************
 AdjustTableContents (private)

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

void
JXUNIXDirTable::AdjustTableContents()
{
	itsKeyBuffer->Clear();

	// adjust the number of rows and the width of the text column

	const JFontManager* fontMgr = GetFontManager();
	const JFontStyle style;

	RemoveAllRows();
	itsMaxStringWidth = 0;

	const JSize count = itsDirInfo->GetElementCount();
	for (JIndex i=1; i<=count; i++)
		{
		AppendRow();

		const JUNIXDirEntry& entry = itsDirInfo->GetEntry(i);
		const JSize w = fontMgr->GetStringWidth(
			JGetDefaultFontName(), kJXDefaultFontSize, style, entry.GetName());
		if (w > itsMaxStringWidth)
			{
			itsMaxStringWidth = w;
			}
		}

	AdjustColWidths();

	// deactivate unselectable items

	itsActiveCells->RemoveAll();
	if (count > 0)
		{
		itsActiveCells->AppendElements(kTrue, count);

		for (JIndex i=1; i<=count; i++)
			{
			const JUNIXDirEntry& entry  = itsDirInfo->GetEntry(i);
			if (entry.IsBrokenLink() || entry.IsUnknown() ||
				(entry.IsDirectory() && (!entry.IsReadable() || !entry.IsExecutable())) ||
				(entry.IsFile() && (!entry.IsReadable() || !itsAllowSelectFilesFlag)))
				{
				itsActiveCells->SetElement(i, kFalse);
				}
			}
		}

	// select appropriate items

	JBoolean deselect  = kFalse;
	JTableSelection& s = GetTableSelection();
	if (itsReselectFlag && !itsReselectNameList->IsEmpty())
		{
		// select the items that still exist

		const JSize count = itsReselectNameList->GetElementCount();
		for (JIndex i=1; i<=count; i++)
			{
			JIndex j;
			if (itsDirInfo->FindEntry(*(itsReselectNameList->NthElement(i)), &j) &&
				ItemIsActive(j))	// check for active in case of single, broken link
				{
				s.SelectRow(j);
				}
			}
		if (count > 1)
			{
			CleanSelection();
			}

		if (!HasSelection())
			{
			// select the closest item

			JIndex i;
			if (ClosestMatch(*(itsReselectNameList->FirstElement()), &i))
				{
				s.SelectRow(i);

				// If it is not an exact match, deselect it after scrolling to it.

				if ((itsDirInfo->GetEntry(i)).GetName() !=
					*(itsReselectNameList->FirstElement()))
					{
					deselect = kTrue;
					}
				}
			}
		}

	if (HasSelection())
		{
		// keep display from jumping

		JPoint cell;
		const JBoolean hasSelection = s.GetFirstSelectedCell(&cell);
		assert( hasSelection );
		const JRect selRect   = GetCellRect(cell);
		const JPoint scrollPt = selRect.topLeft() + itsReselectScrollOffset;
		ScrollTo(scrollPt);
		}
	else if (!itsReselectFlag)
		{
		SelectFirstEntry();
		}

	if (deselect)
		{
		s.ClearSelection();
		}

	itsReselectFlag = kFalse;
	itsReselectNameList->DeleteAll();
}

/******************************************************************************
 GetNextSelectable (protected)

	startIndex can be zero.

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

JBoolean
JXUNIXDirTable::GetNextSelectable
	(
	const JIndex	startIndex,
	const JBoolean	forMulti,
	JIndex*			nextIndex
	)
	const
{
	const JSize rowCount = GetRowCount();
	if (startIndex >= rowCount)
		{
		return kFalse;
		}

	JIndex i = startIndex + 1;
	while (i <= rowCount && !ItemIsSelectable(i, forMulti))
		{
		i++;
		}

	*nextIndex = i;
	return JI2B( i <= rowCount );
}

/******************************************************************************
 GetPrevSelectable (protected)

	startIndex can be count+1.

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

JBoolean
JXUNIXDirTable::GetPrevSelectable
	(
	const JIndex	startIndex,
	const JBoolean	forMulti,
	JIndex*			nextIndex
	)
	const
{
	if (startIndex <= 1)
		{
		return kFalse;
		}

	JIndex i = startIndex - 1;
	while (i >= 1 && !ItemIsSelectable(i, forMulti))
		{
		i--;
		}

	*nextIndex = i;
	return JI2B( i >= 1 );
}

/******************************************************************************
 ItemIsFile (protected)

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

JBoolean
JXUNIXDirTable::ItemIsFile
	(
	const JIndex index
	)
	const
{
	return (itsDirInfo->GetEntry(index)).IsFile();
}

/******************************************************************************
 ClosestMatch

	Returns kFalse if nothing can be selected.

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

JBoolean
JXUNIXDirTable::ClosestMatch
	(
	const JCharacter*	prefixStr,
	JIndex*				index
	)
	const
{
	JBoolean found = itsDirInfo->ClosestMatch(prefixStr, index);

	if (found && !ItemIsActive(*index))
		{
		found = GetNextSelectable(*index, kFalse, index);
		if (!found)
			{
			found = GetPrevSelectable(GetRowCount()+1, kFalse, index);
			}
		}

	return found;
}

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

	Called by our idle task.

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

void
JXUNIXDirTable::UpdateDisplay()
{
	itsDirInfo->Update();
}

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

	This provides a single entry point to update the display, regardless
	of what triggered the update.

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

void
JXUNIXDirTable::Receive
	(
	JBroadcaster*	sender,
	const Message&	message
	)
{
	if (sender == itsDirInfo && message.Is(JUNIXDirInfo::kContentsWillBeUpdated))
		{
		RememberSelections();
		}
	else if (sender == itsDirInfo && message.Is(JUNIXDirInfo::kPathChanged))
		{
		// This comes after ContentsChanged, and cancels the effect of
		// ContentsWillBeUpdated.

		itsReselectFlag = kFalse;
		itsReselectNameList->DeleteAll();
		if (itsSelectWhenChangePathFlag)
			{
			SelectFirstEntry();
			}
		else
			{
			(GetTableSelection()).ClearSelection();
			}
		}

	else if (sender == itsDirInfo && message.Is(JUNIXDirInfo::kContentsChanged))
		{
		AdjustTableContents();
		}

	else
		{
		JXTable::Receive(sender, message);
		}
}

/******************************************************************************
 RememberSelections (private)

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

void
JXUNIXDirTable::RememberSelections()
{
	JPtrArray<JUNIXDirEntry> entryList;
	if (!itsReselectFlag && GetSelection(&entryList))
		{
		itsReselectFlag = kTrue;

		const JSize count = entryList.GetElementCount();
		for (JIndex i=1; i<=count; i++)
			{
			JString* s = new JString((entryList.NthElement(i))->GetName());
			assert( s != NULL );
			itsReselectNameList->Append(s);
			}

		JPoint cell;
		const JBoolean hasSelection = (GetTableSelection()).GetFirstSelectedCell(&cell);
		assert( hasSelection );
		const JRect selRect     = GetCellRect(cell);
		itsReselectScrollOffset = (GetAperture()).topLeft() - selRect.topLeft();
		}
}

/******************************************************************************
 ApertureResized (virtual protected)

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

void
JXUNIXDirTable::ApertureResized
	(
	const JCoordinate dw,
	const JCoordinate dh
	)
{
	JXTable::ApertureResized(dw,dh);
	AdjustColWidths();
}

/******************************************************************************
 AdjustColWidths (private)

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

void
JXUNIXDirTable::AdjustColWidths()
{
	const JSize apWidth = GetApertureWidth();
	if (itsMaxStringWidth + kTextColPadding > apWidth - kIconColWidth)
		{
		SetColWidth(kTextColumn, itsMaxStringWidth + kTextColPadding);
		}
	else
		{
		SetColWidth(kTextColumn, apWidth - kIconColWidth);
		}
}
