/******************************************************************************
 JXFileListTable.cc

	Displays a sorted list of files.  Derived classes can listen to the object
	returned by GetFullNameDataList() and keep an array of extra data in
	sync with our information.

	BASE CLASS = JXTable

	Copyright © 1998-99 by John Lindal.  All rights reserved.

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

#include <JXFileListTable.h>
#include <jXCSFIcons.h>

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

#include <JTableSelection.h>
#include <JFontManager.h>
#include <JRegex.h>
#include <JString.h>
#include <jDirUtil.h>
#include <jFileUtil.h>
#include <jASCIIConstants.h>
#include <jGlobals.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 JCoordinate kDebounceWidth = 3;

// JBroadcaster messages

const JCharacter* JXFileListTable::kProcessSelection = "ProcessSelection::JXFileListTable";

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

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

JXFileListTable::JXFileListTable
	(
	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)
{
	itsRegex              = NULL;
	itsAcceptFileDropFlag = kFalse;
	itsBSRemoveSelFlag    = kFalse;
	itsDragType           = kInvalidDrag;
	itsMaxStringWidth     = 0;
	itsClipBuffer         = NULL;
	itsDNDBuffer          = NULL;
	itsEditMenuProvider   = NULL;
	itsEditMenu           = NULL;

	itsFileList = new JPtrArray<JString>(100);
	assert( itsFileList != NULL );
	itsFileList->SetCompareFunction(JCompareStringsCaseSensitive);

	itsVisibleList = new JPtrArray<JString>(100);
	assert( itsVisibleList != NULL );
	itsVisibleList->SetSortOrder(JOrderedSetT::kSortAscending);
	itsVisibleList->SetCompareFunction(JCompareStringsCaseInsensitive);

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

	itsFileIcon = new JXImage(GetDisplay(), GetColormap(), JXGetPlainFileIcon());
	assert( itsFileIcon != NULL );
	itsFileIcon->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);

	SetBackColor(GetFocusColor());

	// targets for clipboard

	JXSelectionManager* selMgr = GetSelectionManager();

	AddSelectionTarget(kJXClipboardName, XA_STRING);
	AddSelectionTarget(kJXClipboardName, selMgr->GetTextXAtom());

	// targets for DND

	const Atom dndName = (GetDNDManager())->GetDNDSelectionName();

	AddSelectionTarget(dndName, selMgr->GetURLXAtom());
}

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

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

JXFileListTable::~JXFileListTable()
{
	itsFileList->DeleteAll();
	delete itsFileList;

	itsVisibleList->DeleteAll();
	delete itsVisibleList;

	delete itsRegex;
	delete itsFileIcon;
	delete itsKeyBuffer;
	delete itsClipBuffer;
	delete itsDNDBuffer;
}

/******************************************************************************
 AddFile

	Returns kTrue if the file was inserted.  Returns kFalse if the file
	didn't exist, isn't a file, or was already in the list.

	If fullNameIndex is not NULL, it is set to the index into GetFullNameList(),
	both when the file is inserted and when the file is already there.

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

JBoolean
JXFileListTable::AddFile
	(
	const JCharacter*	fullName,
	JIndex*				fullNameIndex	// can be NULL
	)
{
	if (fullNameIndex != NULL)
		{
		*fullNameIndex = 0;
		}

	if (!JFileExists(fullName))
		{
		return kFalse;
		}

	ClearSelection();

	JString* s = new JString(fullName);
	assert( s != NULL );

	JBoolean isDuplicate;
	const JIndex index = itsFileList->GetInsertionSortIndex(s, &isDuplicate);
	if (fullNameIndex != NULL)
		{
		*fullNameIndex = index;
		}

	if (!isDuplicate)
		{
		itsFileList->InsertAtIndex(index, s);
		FilterFile(index);
		AdjustColWidths();
		return kTrue;
		}
	else
		{
		delete s;
		return kFalse;
		}
}

/******************************************************************************
 RemoveFile

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

void
JXFileListTable::RemoveFile
	(
	const JCharacter* fullName
	)
{
	ClearSelection();

	JString s = fullName;
	JIndex index;
	if (itsFileList->SearchSorted(&s, JOrderedSetT::kAnyMatch, &index))
		{
		itsFileList->DeleteElement(index);
		RebuildTable(kTrue);
		}
}

/******************************************************************************
 RemoveFiles

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

void
JXFileListTable::RemoveFiles
	(
	const JPtrArray<JString>& fileList
	)
{
	ClearSelection();

	JBoolean changed = kFalse;

	const JSize count = fileList.GetElementCount();
	for (JIndex i=1; i<=count; i++)
		{
		JString* s = const_cast<JString*>(fileList.NthElement(i));
		JIndex index;
		if (itsFileList->SearchSorted(s, JOrderedSetT::kAnyMatch, &index))
			{
			itsFileList->DeleteElement(index);
			changed = kTrue;
			}
		}

	if (changed)
		{
		RebuildTable(kTrue);
		}
}

/******************************************************************************
 RemoveSelectedFiles

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

void
JXFileListTable::RemoveSelectedFiles()
{
	JTableSelection& s = GetTableSelection();
	if (s.HasSelection())
		{
		JTableSelectionIterator iter(&s);

		JPoint cell;
		while (iter.Next(&cell) && cell.x == 1)
			{
			itsFileList->DeleteElement(RowIndexToFileIndex(cell.y));
			}

		RebuildTable(kTrue);
		}
}

/******************************************************************************
 RemoveAllFiles

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

void
JXFileListTable::RemoveAllFiles()
{
	itsFileList->DeleteAll();
	RebuildTable();
}

/******************************************************************************
 GetFilterRegex

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

JBoolean
JXFileListTable::GetFilterRegex
	(
	JString* regexStr
	)
	const
{
	if (itsRegex != NULL)
		{
		*regexStr = itsRegex->GetPattern();
		return kTrue;
		}
	else
		{
		regexStr->Clear();
		return kFalse;
		}
}

/******************************************************************************
 SetFilterRegex

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

JError
JXFileListTable::SetFilterRegex
	(
	const JCharacter* regexStr
	)
{
	if (JStringEmpty(regexStr))
		{
		ClearFilterRegex();
		return JNoError();
		}
	else if (itsRegex == NULL)
		{
		itsRegex = new JRegex;
		assert( itsRegex != NULL );
		const JError err = itsRegex->SetPattern(regexStr);
		if (err.OK())
			{
			RebuildTable();
			}
		else
			{
			delete itsRegex;
			itsRegex = NULL;
			}
		return err;
		}
	else
		{
		JString origRegexStr = itsRegex->GetPattern();
		if (regexStr == origRegexStr)
			{
			return JNoError();
			}
		else
			{
			const JError err = itsRegex->SetPattern(regexStr);
			if (err.OK())
				{
				RebuildTable();
				}
			else
				{
				const JError err2 = itsRegex->SetPattern(origRegexStr);
				assert_ok( err2 );
				}
			return err;
			}
		}
}

/******************************************************************************
 ClearFilterRegex

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

void
JXFileListTable::ClearFilterRegex()
{
	if (itsRegex != NULL)
		{
		delete itsRegex;
		itsRegex = NULL;

		RebuildTable();
		}
}

/******************************************************************************
 RebuildTable (private)

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

void
JXFileListTable::RebuildTable
	(
	const JBoolean maintainScroll
	)
{
	const JPoint scrollPt = (GetAperture()).topLeft();

	itsVisibleList->DeleteAll();
	RemoveAllRows();
	itsKeyBuffer->Clear();
	itsMaxStringWidth = 0;

	const JSize count = itsFileList->GetElementCount();
	for (JIndex i=1; i<=count; i++)
		{
		FilterFile(i);
		}

	AdjustColWidths();

	if (maintainScroll)
		{
		ScrollTo(scrollPt);
		}
}

/******************************************************************************
 FilterFile (private)

	Client must call AdjustColWidths().

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

void
JXFileListTable::FilterFile
	(
	const JIndex fileIndex
	)
{
	const JString* fullName = itsFileList->NthElement(fileIndex);

	JString path, fileName;
	JSplitPathAndName(*fullName, &path, &fileName);

	if (itsRegex == NULL || itsRegex->Match(fileName))
		{
		JString* s = new JString(fileName);
		assert( s != NULL );

		JIndex rowIndex;
		itsVisibleList->InsertSorted(s, kTrue, &rowIndex);

		InsertRow(rowIndex);

		const JSize w = (GetFontManager())->GetStringWidth(
			JGetDefaultFontName(), kJXDefaultFontSize, JFontStyle(), fileName);
		if (w > itsMaxStringWidth)
			{
			itsMaxStringWidth = w;
			}
		}
}

/******************************************************************************
 GetFullName

	Get the full name for the file in the specified row.  This is public
	because it is needed by anybody who catches the ProcessSelection message.

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

JString
JXFileListTable::GetFullName
	(
	const JIndex rowIndex
	)
	const
{
	return *(itsFileList->NthElement(RowIndexToFileIndex(rowIndex)));
}

/******************************************************************************
 GetFullName

	Get the full name for the specified file.  Returns kTrue if successful.

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

JBoolean
JXFileListTable::GetFullName
	(
	const JString&	fileName,
	JString*		fullName
	)
	const
{
	JIndex index;
	if (FileNameToFileIndex(fileName, &index))
		{
		*fullName = *(itsFileList->NthElement(index));
		return kTrue;
		}
	else
		{
		fullName->Clear();
		return kFalse;
		}
}

/******************************************************************************
 RowIndexToFileIndex (private)

	Get the index into itsFileList for the file in the specified row.

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

JIndex
JXFileListTable::RowIndexToFileIndex
	(
	const JIndex rowIndex
	)
	const
{
	JIndex index;
	const JBoolean found = FileNameToFileIndex(GetFileName(rowIndex), &index);
	assert( found /* JXFileListTable::RowIndexToFileIndex() failed */ );
	return index;
}

/******************************************************************************
 FileNameToFileIndex (private)

	Get the index into itsFileList for the specified file.

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

JBoolean
JXFileListTable::FileNameToFileIndex
	(
	const JString&	name,
	JIndex*			index
	)
	const
{
	const JSize nameLen = name.GetLength();

	const JSize count = itsFileList->GetElementCount();
	for (JIndex i=1; i<=count; i++)
		{
		const JString* f = itsFileList->NthElement(i);
		const JSize len  = f->GetLength();
		if (f->EndsWith(name) &&
			(len == nameLen || f->GetCharacter(len - nameLen) == '/'))
			{
			*index = i;
			return kTrue;
			}
		}

	*index = 0;
	return kFalse;
}

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

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

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

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

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

JBoolean
JXFileListTable::GetSelection
	(
	JPtrArray<JString>* fileList
	)
	const
{
	fileList->DeleteAll();

	JTableSelectionIterator iter(&(GetTableSelection()));

	JPoint cell;
	while (iter.Next(&cell) && cell.x == 1)
		{
		JString* s = new JString(GetFullName(cell.y));
		assert( s != NULL );
		fileList->Append(s);
		}

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

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

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

void
JXFileListTable::SelectSingleEntry
	(
	const JIndex	index,
	const JBoolean	scroll
	)
{
	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();
}

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

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

void
JXFileListTable::SelectAll()
{
	(GetTableSelection()).SelectAll();
	itsKeyBuffer->Clear();
}

/******************************************************************************
 ClearSelection

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

void
JXFileListTable::ClearSelection()
{
	(GetTableSelection()).ClearSelection();
	itsKeyBuffer->Clear();
}

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

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

void
JXFileListTable::TableDrawCell
	(
	JPainter&		p,
	const JPoint&	cell,
	const JRect&	rect
	)
{
	HilightIfSelected(p, cell, rect);

	// draw icon

	if (cell.x == (JCoordinate) kIconColumn)
		{
		p.Image(*itsFileIcon, itsFileIcon->GetBounds(), rect);
		}

	// draw name

	else if (cell.x == (JCoordinate) kTextColumn)
		{
		JRect r = rect;
		r.left += kHMarginWidth;
		p.String(r, GetFileName(cell.y), JPainter::kHAlignLeft, JPainter::kVAlignCenter);
		}
}

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

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

void
JXFileListTable::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 = modifiers.shift();
	const JBoolean selectDiscont   = modifiers.control();
	if ((button == kJXLeftButton && extendSelection) || button == kJXRightButton)
		{
		if (s.OKToExtendSelection())
			{
			s.ExtendSelection(newBoat);
			itsDragType = kSelectRangeDrag;
			}
		}
	else if (button == kJXLeftButton && selectDiscont && s.IsSelected(cell))
		{
		itsDragType = kDeselectCellDrag;
		s.SelectRow(cell.y, kFalse);
		s.ClearBoat();
		s.ClearAnchor();
		}
	else if (button == kJXLeftButton && selectDiscont)
		{
		itsDragType = kSelectCellDrag;
		s.SelectRow(cell.y, kTrue);
		s.SetBoat(newBoat);
		s.SetAnchor(newAnchor);
		}
	else if (button == kJXLeftButton && clickCount == 1)
		{
		itsDragType    = kWaitForDND;
		itsMouseDownPt = pt;
		if (!s.IsSelected(cell))
			{
			SelectSingleEntry(cell.y, kFalse);
			}
		}
	else if (button == kJXLeftButton && clickCount == 2)
		{
		s.SetBoat(newBoat);
		s.SetAnchor(newAnchor);
		Broadcast(ProcessSelection());
		}

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

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

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

void
JXFileListTable::HandleMouseDrag
	(
	const JPoint&			pt,
	const JXButtonStates&	buttonStates,
	const JXKeyModifiers&	modifiers
	)
{
	if (itsDragType == kInvalidDrag)
		{
		return;
		}
	else if (itsDragType == kWaitForDND &&
			 (JLAbs(pt.x - itsMouseDownPt.x) > kDebounceWidth ||
			  JLAbs(pt.y - itsMouseDownPt.y) > kDebounceWidth))
		{
		BeginDND(pt, buttonStates, modifiers);
		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 == kSelectCellDrag)
		{
		s.SelectRow(cell.y, kTrue);
		s.SetBoat(newBoat);
		s.SetAnchor(newAnchor);
		}
	else if (itsDragType == kDeselectCellDrag)
		{
		s.SelectRow(cell.y, kFalse);
		}
	else if (itsDragType == kSelectRangeDrag)
		{
		s.ExtendSelection(newBoat);
		}

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

/******************************************************************************
 HandleMouseUp (virtual protected)

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

void
JXFileListTable::HandleMouseUp
	(
	const JPoint&			pt,
	const JXMouseButton		button,
	const JXButtonStates&	buttonStates,
	const JXKeyModifiers&	modifiers
	)
{
	if (itsDragType == kWaitForDND)		// DND never started
		{
		JPoint cell;
		const JBoolean ok = GetCell(itsMouseDownPt, &cell);
		assert( ok );
		SelectSingleEntry(cell.y, kFalse);
		}

	itsDragType = kInvalidDrag;
}

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

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

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

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

	else if (key == kJReturnKey)
		{
		itsKeyBuffer->Clear();
		if (hadSelection)
			{
			Broadcast(ProcessSelection());
			}
		}

	else if (key == kJDeleteKey && itsBSRemoveSelFlag)
		{
		RemoveSelectedFiles();
		}

	else if (key == kJUpArrow)
		{
		itsKeyBuffer->Clear();

		if (hadSelection && extendSelection)
			{
			if (s.OKToExtendSelection())
				{
				JIndex index = (s.GetBoat()).y;
				if (index > 1)
					{
					index--;
					s.ExtendSelection(JPoint(kColumnCount, index));
					}
				TableScrollToCell(JPoint(1, index));
				}
			}
		else if (hadSelection && topSelCell.y > 1)
			{
			(topSelCell.y)--;
			SelectSingleEntry(topSelCell.y);
			}
		else if (hadSelection)
			{
			TableScrollToCell(topSelCell);
			}
		else if (!hadSelection && GetRowCount() > 0)
			{
			SelectSingleEntry(GetRowCount());
			}
		}

	else if (key == kJDownArrow)
		{
		itsKeyBuffer->Clear();

		const JSize rowCount = GetRowCount();
		if (hadSelection && extendSelection)
			{
			if (s.OKToExtendSelection())
				{
				JIndex index = (s.GetBoat()).y;
				if (index < rowCount)
					{
					index++;
					s.ExtendSelection(JPoint(kColumnCount, index));
					}
				TableScrollToCell(JPoint(1, index));
				}
			}
		else if (hadSelection && topSelCell.y < (JCoordinate) rowCount)
			{
			(topSelCell.y)++;
			SelectSingleEntry(topSelCell.y);
			}
		else if (hadSelection)
			{
			TableScrollToCell(topSelCell);
			}
		else if (!hadSelection && rowCount > 0)
			{
			SelectSingleEntry(1);
			}
		}

	else if ((key == 'c' || key == 'C') && modifiers.meta() && !modifiers.shift())
		{
		CopySelectedFileNames();
		}
	else if ((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);
		}

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

/******************************************************************************
 ClosestMatch (private)

	Returns the index of the closest match for the given name prefix.

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

JBoolean
JXFileListTable::ClosestMatch
	(
	const JString&	prefixStr,
	JIndex*			index
	)
	const
{
	JString* target = const_cast<JString*>(&prefixStr);
	JBoolean found;
	*index = itsVisibleList->SearchSorted1(target, JOrderedSetT::kFirstMatch, &found);
	if (*index > itsVisibleList->GetElementCount())		// insert beyond end of list
		{
		*index = itsVisibleList->GetElementCount();
		}
	return JConvertToBoolean( *index > 0 );
}

/******************************************************************************
 SetEditMenuProvider

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

void
JXFileListTable::SetEditMenuProvider
	(
	JXTEBase* te
	)
{
	if (itsEditMenu != NULL)
		{
		StopListening(itsEditMenu);
		}

	if (te != NULL && te->GetEditMenu(&itsEditMenu))
		{
		itsEditMenuProvider = te;
		ListenTo(itsEditMenu);
		}
	else
		{
		itsEditMenuProvider = NULL;
		itsEditMenu         = NULL;
		}
}

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

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

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

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

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

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

void
JXFileListTable::UpdateEditMenu()
{
JBoolean ok;
JIndex index;

	if (HasSelection())
		{
		ok = itsEditMenuProvider->EditMenuCmdToIndex(JTextEditor::kCopyCmd, &index);
		assert( ok );
		itsEditMenu->EnableItem(index);
		}

	ok = itsEditMenuProvider->EditMenuCmdToIndex(JTextEditor::kSelectAllCmd, &index);
	assert( ok );
	itsEditMenu->EnableItem(index);
}

/******************************************************************************
 HandleEditMenu (private)

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

void
JXFileListTable::HandleEditMenu
	(
	const JIndex index
	)
{
	JTextEditor::CmdIndex cmd;
	if (itsEditMenuProvider->EditMenuIndexToCmd(index, &cmd))
		{
		if (cmd == JTextEditor::kCopyCmd)
			{
			CopySelectedFileNames();
			}
		else if (cmd == JTextEditor::kSelectAllCmd)
			{
			SelectAll();
			}
		}
}

/******************************************************************************
 CopySelectedFileNames (private)

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

void
JXFileListTable::CopySelectedFileNames()
{
	if (HasSelection() && BecomeSelectionOwner(kJXClipboardName))
		{
		if (itsClipBuffer == NULL)
			{
			itsClipBuffer = new JString;
			assert( itsClipBuffer != NULL );
			}
		itsClipBuffer->Clear();

		JTableSelectionIterator iter(&(GetTableSelection()));
		JPoint cell;
		JSize selectCount = 0;
		while (iter.Next(&cell) && cell.x == 1)
			{
			if (selectCount > 0)
				{
				itsClipBuffer->AppendCharacter('\n');
				}
			*itsClipBuffer += GetFileName(cell.y);
			selectCount++;
			}

		if (selectCount > 1)
			{
			itsClipBuffer->AppendCharacter('\n');
			}
		}
}

/******************************************************************************
 DNDInit (virtual protected)

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

void
JXFileListTable::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
JXFileListTable::DNDFinish
	(
	const JXContainer* target
	)
{
	CopyToDNDBuffer();
}

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

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

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

	assert( HasSelection() );

	JPtrArray<JString> fileList;

	JTableSelectionIterator iter(&(GetTableSelection()));
	JPoint cell;
	while (iter.Next(&cell) && cell.x == 1)
		{
		JString* s = itsFileList->NthElement(RowIndexToFileIndex(cell.y));
		fileList.Append(s);
		}

	*itsDNDBuffer = JXPackFileNames(fileList);
}

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

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

Atom
JXFileListTable::GetDNDAction
	(
	const JXContainer*		target,
	const JXButtonStates&	buttonStates,
	const JXKeyModifiers&	modifiers
	)
{
	return (GetDNDManager())->GetDNDActionPrivateXAtom();
}

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

	Accept text/uri-list from anybody but ourselves.

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

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

	if (!itsAcceptFileDropFlag || 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
JXFileListTable::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();
			for (JIndex i=1; i<=fileCount; i++)
				{
				AddFile(*(fileNameList.NthElement(i)));
				}

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

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

/******************************************************************************
 ConvertSelection (virtual protected)

	Convert selection to the specified type and return kTrue,
	or return kFalse if the conversion cannot be accomplished.

	*returnType must be actual data type.  For example, when "TEXT" is
	requested, one often returns XA_STRING.

	*data must be allocated with "new unsigned char[]" and will be deleted
	by the caller.  *dataLength must be set to the length of *data.

	*bitsPerBlock must be set to the number of bits per element of data.
	e.g.	If data is text, *bitsPerBlock=8.
			If data is an int, *bitsPerBlock=sizeof(int)*8

	Since X performs byte swapping when *bitsPerBlock > 8, mixed data is
	packed one byte at a time to insure that it can be correctly decoded.

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

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

	JXSelectionManager* selMgr = GetSelectionManager();
	JXDNDManager* dndMgr       = GetDNDManager();

	if (name == kJXClipboardName &&
		(type == XA_STRING || type == selMgr->GetTextXAtom()))
		{
		*returnType = XA_STRING;
		*dataLength = itsClipBuffer->GetLength();
		*data = new unsigned char[ *dataLength ];
		if (*data != NULL)
			{
			memcpy(*data, *itsClipBuffer, *dataLength);
			return kTrue;
			}
		}

	else if (name == dndMgr->GetDNDSelectionName() &&
			 type == 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;
}

/******************************************************************************
 LostSelectionOwnership (virtual protected)

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

void
JXFileListTable::LostSelectionOwnership
	(
	const Atom selectionName
	)
{
	if (selectionName == kJXClipboardName)
		{
		delete itsClipBuffer;
		itsClipBuffer = NULL;
		}
	else if (selectionName == (GetDNDManager())->GetDNDSelectionName())
		{
		delete itsDNDBuffer;
		itsDNDBuffer = NULL;
		}

	JXTable::LostSelectionOwnership(selectionName);
}

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

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

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

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

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

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