/******************************************************************************
 BindingsTable.cc


	Copyright © 1997 by Glenn W. Bach. All rights reserved.

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

#include "BindingsTable.h"
#include "BindingsList.h"
#include "BindingsDir.h"
#include "PatternBinding.h"

#include <JXInputField.h>
#include <JXWindow.h>
#include <JXWindowPainter.h>
#include <JXDisplay.h>
#include <JXFontManager.h>
#include <JXDNDManager.h>
#include <JXSelectionManager.h>
#include <JXImage.h>

#include <JTableSelection.h>
#include <JTableSelectionIterator.h>
#include <JPainter.h>
#include <JString.h>
#include <JUserNotification.h>
#include <JOrderedSet.h>
#include <JFontManager.h>
#include <JError.h>
#include <JArray.h>
#include <JColormap.h>

#include <jASCIIConstants.h>
#include <jStreamUtil.h>
#include <jProcessUtil.h>
#include <jGlobals.h>

#include <X11/keysym.h>
#include <stdio.h>
#include <strstream.h>
#include <fstream.h>
#include <jAssert.h>

extern JUserNotification* gUserNotification;

const JCoordinate	kDefRowHeight 		= 15;
const JCoordinate	kDefColWidth  		= 80;

enum
{
	kPatternIndex = 1,
	kActionIndex,
	kAlternateIndex
};

const JCoordinate kCellBuffer = 5;

const JCharacter* BindingsTable::kEditStarted = "BindingsTable::kEditStarted";
const JCharacter* BindingsTable::kEditStopped = "BindingsTable::kEditStopped";
const JCharacter* BindingsTable::kChanged = "BindingsTable::kChanged";

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

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

BindingsTable::BindingsTable
	(
	BindingsList*		list,
	JXScrollbarSet* 	scrollbarSet, 
	JXContainer* 		enclosure,
	const HSizingOption hSizing, 
	const VSizingOption vSizing,
	const JCoordinate 	x, 
	const JCoordinate 	y,
	const JCoordinate 	w, 
	const JCoordinate 	h
	)
	:
	JXEditTable(kDefRowHeight, kDefColWidth, scrollbarSet, enclosure, 
			hSizing, vSizing, x, y, w, h),
	itsList(list)
{
	itsStringInputField = NULL;
	ListenTo(itsList);
	
	AppendCol(kDefColWidth);
	AppendCol(kDefColWidth);
	AppendCol(kDefColWidth);
//	SetRowBorderInfo(0, JGetCurrColormap()->GetBlackColor());
//	SetColBorderInfo(0, JGetCurrColormap()->GetBlackColor());
	SetBackColor(JGetCurrColormap()->GetWhiteColor());
	
	JCoordinate ascent, descent;
	itsLineHeight = 
		(GetFontManager())->GetLineHeight(JGetDefaultFontName(), 
		kJDefaultFontSize, JFontStyle(), &ascent, &descent) + 5;
	SyncWithList();
}

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

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

BindingsTable::~BindingsTable()
{
}

/******************************************************************************
 Receive

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

void
BindingsTable::Receive
	(
	JBroadcaster* 	sender, 
	const Message& 	message
	)
{
	if (sender == itsList)
		{
		if (message.Is(JOrderedSetT::kElementsInserted))
			{
			JOrderedSetT::ElementsInserted* info = 
				dynamic_cast(JOrderedSetT::ElementsInserted*, &message);
			assert(info != NULL);
			InsertRow(info->GetFirstIndex(), itsLineHeight);
			PatternBinding* binding = itsList->NthElement(info->GetFirstIndex());
			JString str = binding->GetPattern();
			JSize width = (GetFontManager())->GetStringWidth(JGetDefaultFontName(), 
									kJDefaultFontSize, JFontStyle(), str) + 2 * kCellBuffer;
			binding->SetPatternWidth(width);

			str = binding->GetAction();
			width = (GetFontManager())->GetStringWidth(JGetDefaultFontName(), 
									kJDefaultFontSize, JFontStyle(), str) + 2 * kCellBuffer;
			binding->SetActionWidth(width);

			str = binding->GetAlternate();
			width = (GetFontManager())->GetStringWidth(JGetDefaultFontName(), 
									kJDefaultFontSize, JFontStyle(), str) + 2 * kCellBuffer;
			binding->SetAlternateWidth(width);
			if (binding->GetPattern().IsEmpty())
				{
				BeginEditing(JPoint(1, info->GetFirstIndex()));
				}
			}
		else if (message.Is(JOrderedSetT::kElementsRemoved))
			{
			JOrderedSetT::ElementsRemoved* info = 
				dynamic_cast(JOrderedSetT::ElementsRemoved*, &message);
			assert(info != NULL);
			RemoveRow(info->GetFirstIndex());
			UpdateColWidths();
			}
		else if (message.Is(JOrderedSetT::kAllElementsRemoved))
			{
			RemoveAllRows();
			}
		TableRefresh();
		}
	else
		{
		JXTable::Receive(sender, message);
		}
}

/******************************************************************************
 ApertureResized

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

void
BindingsTable::ApertureResized
	(
	const JCoordinate dw, 
	const JCoordinate dh
	)
{
	UpdateColWidths();
}

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

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

void
BindingsTable::HandleKeyPress
	(
	const int key,				
	const JXKeyModifiers& modifiers
	)
{
	if (key == kJForwardDeleteKey || key == kJDeleteKey)
		{
		DeletePattern();
		}
	if (key == kJEscapeKey)
		{
		if (IsEditing())
			{
			CancelEditing();
			Broadcast(EditStopped());
			}
		else
			{
			JXWindowDirector* wdir = GetWindow()->GetDirector();
			BindingsDir* dir = dynamic_cast(BindingsDir*, wdir);
			assert(dir != NULL);
			dir->TryToClose();
			return;
			}
		}
	else if (key == kJReturnKey)
		{
		JPoint cell;
		if (GetEditedCell(&cell) && (cell.x == 1))
			{
			JIndex findex;
			JSize count = itsList->GetElementCount();
			if (EndEditing())
				{
				if ((itsFoundIndex <= count) && ((JCoordinate)itsFoundIndex != cell.y))
					{
					TableScrollToCell(JPoint(1,itsFoundIndex));
					BeginEditing(JPoint(1,itsFoundIndex));
					}
				}
			}
		else
			{
			JXEditTable::HandleKeyPress(key, modifiers);
			}
		}
	else if (key == kJTabKey)
		{
		JPoint cell;
		if (GetEditedCell(&cell) && (cell.x == 1))
			{
			JIndex findex;
			JSize count = itsList->GetElementCount();
			if (EndEditing())
				{
				if (itsFoundIndex <= count)
					{
					TableScrollToCell(JPoint(1,itsFoundIndex));
					BeginEditing(JPoint(2,itsFoundIndex));
					}
				}
			}
		else
			{
			JXEditTable::HandleKeyPress(key, modifiers);
			}
		}
	else
		{
		JXEditTable::HandleKeyPress(key, modifiers);
		}
	JPoint cell;
	if (GetEditedCell(&cell))
		{			
		Broadcast(EditStarted());
		}
	else
		{
		Broadcast(EditStopped());
		}
}

/******************************************************************************
 TableDrawCell

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

void
BindingsTable::TableDrawCell
	(
	JPainter& p, 
	const JPoint& cell, 
	const JRect& rect
	)
{
	HilightIfSelected(p, cell, rect);
	PatternBinding* binding = itsList->NthElement(cell.y);
	JString str;
	if (cell.x == kPatternIndex)
		{
		str = binding->GetPattern();
		}
	else if (cell.x == kActionIndex)
		{
		str = binding->GetAction();
		}
	else if (cell.x == kAlternateIndex)
		{
		str = binding->GetAlternate();
		}
	JRect r(rect);
	r.left += kCellBuffer;
	p.String(r, str, JPainter::kHAlignLeft, JPainter::kVAlignCenter);
}

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

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

void
BindingsTable::HandleMouseDown
	(
	const JPoint& pt, 
	const JXMouseButton button,
	const JSize clickCount,
	const JXButtonStates& buttonStates,
	const JXKeyModifiers& modifiers
	)
{
	JPoint cell;
	if (GetCell(pt, &cell))
		{
		BeginEditing(cell, kTrue);
		Broadcast(EditStarted());
		}
}

/******************************************************************************
 SyncWithList

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

void
BindingsTable::SyncWithList()
{
	RemoveAllRows();
	JSize width;
	JString str;
	for (JSize index = 1; index <= itsList->GetElementCount(); index++)
		{
		PatternBinding* binding = itsList->NthElement(index);
		str = binding->GetPattern();
		width = (GetFontManager())->GetStringWidth(JGetDefaultFontName(), 
									kJDefaultFontSize, JFontStyle(), str) + 2 * kCellBuffer;
		binding->SetPatternWidth(width);

		str = binding->GetAction();
		width = (GetFontManager())->GetStringWidth(JGetDefaultFontName(), 
									kJDefaultFontSize, JFontStyle(), str) + 2 * kCellBuffer;
		binding->SetActionWidth(width);
		
		str = binding->GetAlternate();
		width = (GetFontManager())->GetStringWidth(JGetDefaultFontName(), 
									kJDefaultFontSize, JFontStyle(), str) + 2 * kCellBuffer;
		binding->SetAlternateWidth(width);
		AppendRow(itsLineHeight);
		}
	UpdateColWidths();
}

/******************************************************************************
 UpdateColWidths

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

void
BindingsTable::UpdateColWidths()
{
	JSize w;
	JSize w1 = 100;
	JSize w2 = 50;
	JSize w3 = 100;
	JString str;
	for (JSize index = 1; index <= itsList->GetElementCount(); index++)
		{
		PatternBinding* binding = itsList->NthElement(index);
		w = binding->GetPatternWidth();
		if (w > w1)
			{
			w1 = w;
			}
			
		w = binding->GetActionWidth();
		if (w > w2)
			{
			w2 = w;
			}

		w = binding->GetAlternateWidth();
		if (w > w3)
			{
			w3 = w;
			}
		}
	SetColWidth(kPatternIndex, w1);
	SetColWidth(kActionIndex, w2);
	if (GetApertureWidth() > (JCoordinate)(w1 + w2 + w3))
		{
		SetColWidth(kAlternateIndex, GetApertureWidth() - w1 - w2 - 2);
		}
	else
		{
		SetColWidth(kAlternateIndex, w3);
		}
}

/******************************************************************************
 CreateXInputField (virtual protected)

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

JXInputField*
BindingsTable::CreateXInputField
	(
	const JPoint&    		cell,
	const JCoordinate       x,
	const JCoordinate       y,
	const JCoordinate       w,
	const JCoordinate       h
	)
{
	assert( itsStringInputField == NULL );

	itsStringInputField = 
		new JXInputField(this,
			kFixedLeft, kFixedTop, x+2,y+1,w-4,h-2);
	assert( itsStringInputField != NULL );

	PatternBinding* binding = itsList->NthElement(cell.y);
	JString str;
	if (cell.x == kPatternIndex)
		{
		str = binding->GetPattern();
		}
	else if (cell.x == kActionIndex)
		{
		str = binding->GetAction();
		}
	else if (cell.x == kAlternateIndex)
		{
		str = binding->GetAlternate();
		}

	Broadcast(EditStarted());

	itsStringInputField->SetText(str);
	return itsStringInputField;
}

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

 Extract the information from the active input field, check it,
 and delete the input field if successful.

 Returns kTrue if the data is valid and the process succeeded.

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

JBoolean
BindingsTable::ExtractInputData
	(
	const JPoint& cell
	)
{
	assert( itsStringInputField != NULL );

	const JString& value = itsStringInputField->GetText();
	PatternBinding* binding = itsList->NthElement(cell.y);
	JSize width;
	if (cell.x == kPatternIndex)
		{
		if (itsList->ChangePattern(cell.y, value, &itsFoundIndex))
			{
			UpdateColWidths();
			Broadcast(Changed());
			}
		else
			{
			itsFoundIndex = cell.y;
			}
		}
	else if (cell.x == kActionIndex)
		{
		if (itsList->ChangeAction(cell.y, value))
			{
			JString str = binding->GetAction();
			JSize width = (GetFontManager())->GetStringWidth(JGetDefaultFontName(), 
									kJDefaultFontSize, JFontStyle(), str) + 2 * kCellBuffer;
			binding->SetActionWidth(width);
			itsFoundIndex = cell.y;
			UpdateColWidths();
			Broadcast(Changed());
			}
		}
	else if (cell.x == kAlternateIndex)
		{
		if (itsList->ChangeAlternate(cell.y, value))
			{
			JString str = binding->GetAlternate();
			JSize width = (GetFontManager())->GetStringWidth(JGetDefaultFontName(), 
									kJDefaultFontSize, JFontStyle(), str) + 2 * kCellBuffer;
			binding->SetAlternateWidth(width);
			itsFoundIndex = cell.y;
			UpdateColWidths();
			Broadcast(Changed());
			}
		}
	return kTrue;
}

/******************************************************************************
 DeletePattern

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

void
BindingsTable::DeletePattern()
{
	JPoint cell;
	if (GetEditedCell(&cell))
		{
		PatternBinding* pb = itsList->NthElement(cell.y);
		if (pb->IsSystemBinding())
			{
			JGetUserNotification()->ReportError("This is a system entry and cannot be removed.");
			}
		else
			{
			itsList->DeleteBinding(cell.y);
			Broadcast(EditStopped());
			Broadcast(Changed());
			}
		}
}

/******************************************************************************
 DeleteXInputField (virtual protected)

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

void
BindingsTable::DeleteXInputField()
{
	if (itsStringInputField != NULL)
		{
		delete itsStringInputField;
		itsStringInputField = NULL;
		}
	Broadcast(EditStopped());
}
