/******************************************************************************
 GMessageTable.cc


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

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

#include "GMessageTable.h"
#include "GMessageTableDir.h"
#include "GMessageViewDir.h"
#include "GMessageEditDir.h"
#include "GMessageHeaderList.h"
#include "GMessageHeader.h"
#include "GPrefsMgr.h"
#include "gMailUtils.h"

#include "envelope-front.xpm"
#include "check1.xpm"

#include "jx_edit_clear.xpm"
#include "cross_out.xpm"
#include "reply.xpm"
#include "reply_all.xpm"
#include "reply_sender.xpm"
#include "forward.xpm"
#include "redirect.xpm"


#include <JXTextMenu.h>
#include <JXTimerTask.h>
#include <JXMenuBar.h>
#include <JXColormap.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 <JOrderedSet.h>
#include <JFontManager.h>
#include <JColormap.h>
#include <JError.h>
#include <JArray.h>

#include <GMGlobals.h>

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

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

const JCoordinate	kDefRowHeight 		= 15;
const JCoordinate	kDefColWidth  		= 80;
const JCoordinate	kPriorityColWidth	= 18;
const JCoordinate	kIconColWidth  		= 30;
const JCoordinate	kAttachColWidth		= 10;
const JCoordinate	kFromColWidth  		= 160;
const JCoordinate	kSubjectColWidth	= 200;
const JCoordinate	kDateColWidth		= 60; // used to be 250;
const JCoordinate	kSizeColWidth		= 60;
const JCoordinate	kCellBuffer			= 10;

const JCoordinate	kPriorityIndex		= 1;
const JCoordinate	kIconIndex			= 2;
const JCoordinate	kAttachIndex		= 3;
const JCoordinate	kFromIndex			= 4;
const JCoordinate	kSubjectIndex		= 5;
const JCoordinate	kDateIndex			= 6;
const JCoordinate	kSizeIndex			= 7;

const JCoordinate kDragBeginBuffer 				= 5;
static const JCharacter* kDragMessagesXAtomName = "GMailMessages";
static const JCharacter* kDNDActionCopyDescrip  = "copy the message";
static const JCharacter* kDNDActionMoveDescrip  = "move the message";

static const JCharacter* kMessageMenuTitleStr = "Message";
static const JCharacter* kMessageMenuStr = 
	"Select all %k Meta-A  %i \"SelectAll::Arrow\""
	"| Delete %k Meta-D %i \"Delete::Arrow\""
	"| Undelete %k Meta-U %i \"Unelete::Arrow\""
	"%l|Reply %k Meta-R %i \"Reply::Arrow\""
	"| Reply to sender %i \"ReplyToSender::Arrow\""
	"|Reply to all %i \"ReplyToAll::Arrow\""
	"| Forward %k Meta-F %i \"Forward::Arrow\""
	"| Redirect %i \"Redirect::Arrow\"";
	
enum
{
	kSelectAllCmd = 1,
	kDeleteCmd,
	kUndeleteCmd,
	kReplyCmd,
	kReplySenderCmd,
	kReplyAllCmd,
	kForwardCmd,
	kRedirectCmd
};

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

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

GMessageTable::GMessageTable
	(
	GMessageTableDir* 	dir,
	JXMenuBar* 			menuBar, 
	JXScrollbarSet* 	scrollbarSet, 
	JXContainer* 		enclosure,
	const HSizingOption hSizing, 
	const VSizingOption vSizing,
	const JCoordinate 	x, 
	const JCoordinate 	y,
	const JCoordinate 	w, 
	const JCoordinate 	h
	)
	:
	JXTable(kDefRowHeight, kDefColWidth, scrollbarSet, enclosure, 
			hSizing, vSizing, x, y, w, h)
{
	itsDir 			= dir;
	itsList 		= NULL;
	itsUnlockTask	= NULL;

	itsMessageMenu = menuBar->AppendTextMenu(kMessageMenuTitleStr);
	itsMessageMenu->SetMenuItems(kMessageMenuStr);
	itsMessageMenu->SetUpdateAction(JXMenu::kDisableNone);
	ListenTo(itsMessageMenu);

	AppendCol(kPriorityColWidth);
	AppendCol(kIconColWidth);
	AppendCol(kAttachColWidth);
	AppendCol(kFromColWidth);
	AppendCol(kSubjectColWidth);
	AppendCol(kDateColWidth);
	AppendCol(kSizeColWidth);
//	AppendCol(kDefColWidth);
	SetRowBorderInfo(0, GetColormap()->GetBlackColor());
	SetColBorderInfo(0, GetColormap()->GetBlackColor());
	SetBackColor(GetColormap()->GetWhiteColor());
	
	JCoordinate ascent, descent;
	itsLineHeight = 
		(GetFontManager())->GetLineHeight(JGetDefaultFontName(), 
		12, JFontStyle(), &ascent, &descent) + 5;
	
	itsMailIcon = new JXImage(GetDisplay(), GetColormap(), JXPM(envelope_front));
	assert( itsMailIcon != NULL );
	itsMailIcon->ConvertToRemoteStorage();

	itsCheckIcon = new JXImage(GetDisplay(), GetColormap(), JXPM(check1_xpm));
	assert( itsCheckIcon != NULL );
	itsCheckIcon->ConvertToRemoteStorage();

	itsLastSelectedIndex = 0;

	const Atom dndName = (GetDNDManager())->GetDNDSelectionName();	
	itsMessageXAtom = AddSelectionTarget(dndName, kDragMessagesXAtomName);
	itsCurrentDndHereIndex = 0;
	itsIsDND = kFalse;

	itsMessageMenu->SetItemImage(kDeleteCmd, jx_edit_clear);
	itsMessageMenu->SetItemImage(kReplyCmd, reply_xpm);
	itsMessageMenu->SetItemImage(kReplySenderCmd, reply_sender_xpm);
	itsMessageMenu->SetItemImage(kReplyAllCmd, reply_all_xpm);
	itsMessageMenu->SetItemImage(kForwardCmd, forward_xpm);
	itsMessageMenu->SetItemImage(kRedirectCmd, redirect_xpm);
}

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

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

GMessageTable::~GMessageTable()
{
	delete itsMailIcon;
	delete itsCheckIcon;
}

/******************************************************************************
 Draw (virtual protected)


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

void
GMessageTable::Draw
	(
	JXWindowPainter& p, 
	const JRect& rect
	)
{
	JXTable::Draw(p, rect);
	if (itsIsDND && (itsCurrentDndHereIndex != 0) && (itsCurrentDndHereIndex <= GetRowCount()))
		{
		p.ResetClipRect();
		assert(CellValid(JPoint(1, itsCurrentDndHereIndex)));
		JRect crect = GetCellRect(JPoint(1, itsCurrentDndHereIndex));
		crect.bottom--;
		crect.top++;
		JRect rrect = GetAperture();
		if (itsCurrentDndHereIndex == 1 && itsAboveTop)
			{
			p.Line(rrect.left, crect.top, rrect.right, crect.top);
			}
		else
			{
			p.Line(rrect.left, crect.bottom, rrect.right, crect.bottom);
			}
		}
}

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

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

void
GMessageTable::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);
			}
		else if (message.Is(JOrderedSetT::kElementsRemoved))
			{
			JOrderedSetT::ElementsRemoved* info = 
				dynamic_cast(JOrderedSetT::ElementsRemoved*, &message);
			assert(info != NULL);
			RemoveRow(info->GetFirstIndex());
			if (GetRowCount() < itsLastSelectedIndex)
				{
				itsLastSelectedIndex = 0;
				}
			}
		else if (message.Is(JOrderedSetT::kAllElementsRemoved))
			{
			RemoveAllRows();
			if (GetRowCount() < itsLastSelectedIndex)
				{
				itsLastSelectedIndex = 0;
				}
			}
		TableRefresh();
		}
	else if (sender == itsMessageMenu && message.Is(JXMenu::kNeedsUpdate))
		{
		UpdateMessageMenu();
		}
	else if (sender == itsMessageMenu && message.Is(JXMenu::kItemSelected))
		{
		 const JXMenu::ItemSelected* selection =
			dynamic_cast(const JXMenu::ItemSelected*, &message);
		assert( selection != NULL );
		HandleMessageMenu(selection->GetIndex());
		}
	else if (sender == itsUnlockTask && message.Is(JXTimerTask::kTimerWentOff))
		{
		if (!IsDNDSource())
			{
			JString lockfile = itsDir->GetMailFile() + ".lock";
			if (JFileExists(lockfile))
				{
				GUnlockFile(itsDir->GetMailFile());
				}
			delete itsUnlockTask;
			itsUnlockTask = NULL;
			}
		}
	else
		{
		JXTable::Receive(sender, message);
		}
}

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

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

void
GMessageTable::HandleKeyPress
	(
	const int key,				
	const JXKeyModifiers& modifiers
	)
{
	if ((key == kJEscapeKey) || (key == ' '))
		{
		itsLastSelectedIndex = 0;
		(GetTableSelection()).ClearSelection();
		TableRefresh();
		}
	else if (key == kJForwardDeleteKey || key == kJDeleteKey)
		{
		JTableSelection& selection = GetTableSelection();
		JTableSelectionIterator* iter = 
			new JTableSelectionIterator(&selection);
		JPoint cell;
		while(iter->Next(&cell))
			{
			GMessageHeader* header = itsList->NthElement(cell.y);
			header->SetMessageStatus(GMessageHeader::kDelete);
			itsDir->CloseMessage(header);
			itsDir->DataModified();
			}
		TableRefresh();
		delete iter;
		}
	else if (key == kJUpArrow && !modifiers.control())
		{
		JTableSelection& selection = GetTableSelection();
		JTableSelectionIterator* iter = 
			new JTableSelectionIterator(&selection);
		JPoint cell;
		if (modifiers.shift())
			{
			cell.x = 1;
			cell.y = itsLastSelectedIndex;
			if(selection.IsSelected(cell))
				{
				do
					{
					cell.y++;
					}
				while(cell.y <= (JCoordinate)GetRowCount() && selection.IsSelected(cell));

				if (cell.y > (JCoordinate)itsLastSelectedIndex + 1) //shrink
					{
					JCoordinate last = cell.y - 2;
					selection.ClearSelection();
					for (cell.y = itsLastSelectedIndex; cell.y <= last; cell.y++)
						{
						selection.SelectCell(cell);
						}
					cell.y = last;
					TableScrollToCell(cell, kFalse);
					TableRefresh();
					}
				else //expand
					{
					cell.y = itsLastSelectedIndex - 1;
					while (cell.y >=1 && selection.IsSelected(cell))
						{
						cell.y--;
						}
					if (cell.y >= 1)
						{
						JCoordinate first = cell.y;
						for (cell.y = first; cell.y <= (JCoordinate)itsLastSelectedIndex; cell.y++)
							{
							selection.SelectCell(cell);
							}
						cell.y = first;
						TableScrollToCell(cell, kFalse);
						TableRefresh();
						}
					}
				}
			}
		else if (modifiers.meta())
			{
			cell.x = 1;
			cell.y = 1;
			selection.ClearSelection();
			selection.SelectCell(cell);
			assert(CellValid(cell));
			TableScrollToCell(cell, kFalse);
			itsLastSelectedIndex = 1;
			TableRefresh();
			}
		else
			{
			if(iter->Next(&cell))
				{
				if (cell.y > 1)
					{
					selection.ClearSelection();
					cell.y--;
					selection.SelectCell(cell);
					assert(CellValid(cell));
					TableScrollToCell(cell, kFalse);
					itsLastSelectedIndex = cell.y;
					TableRefresh();
					}
				else if (cell.y == 1)
					{
					selection.ClearSelection();
					selection.SelectCell(cell);
					assert(CellValid(cell));
					TableScrollToCell(cell, kFalse);
					itsLastSelectedIndex = cell.y;
					TableRefresh();
					}
				}
			else if (GetRowCount() >= 1)
				{
				cell.x = 1;
				cell.y = GetRowCount();
				selection.SelectCell(cell);
				assert(CellValid(cell));
				TableScrollToCell(cell, kFalse);
				itsLastSelectedIndex = cell.y;
				TableRefresh();
				}
			}
		delete iter;
		}
	else if (key == kJDownArrow && !modifiers.control())
		{
		JTableSelection& selection = GetTableSelection();
		JTableSelectionIterator* iter = 
			new JTableSelectionIterator(&selection);
		JPoint cell;
		if (modifiers.shift())
			{
			cell.x = 1;
			cell.y = itsLastSelectedIndex;
			if(selection.IsSelected(cell))
				{
				do
					{
					cell.y--;
					}
				while(cell.y >= 1 && selection.IsSelected(cell));

				if (cell.y < (JCoordinate)itsLastSelectedIndex - 1) //shrink
					{
					JCoordinate first = cell.y + 2;
					selection.ClearSelection();
					for (cell.y = first; cell.y <= (JCoordinate)itsLastSelectedIndex; cell.y++)
						{
						selection.SelectCell(cell);
						}
					cell.y = first;
					TableScrollToCell(cell, kFalse);
					TableRefresh();
					}
				else //expand
					{
					cell.y = itsLastSelectedIndex + 1;
					while (cell.y <= (JCoordinate)GetRowCount() && selection.IsSelected(cell))
						{
						cell.y++;
						}
					if (cell.y <= (JCoordinate)GetRowCount())
						{
						JCoordinate last = cell.y;
						selection.ClearSelection();
						for (cell.y = itsLastSelectedIndex; cell.y <= last; cell.y++)
							{
							selection.SelectCell(cell);
							}
						cell.y = last;
						TableScrollToCell(cell, kFalse);
						TableRefresh();
						}
					}
				}

			}
		else if (modifiers.meta())
			{
			cell.x = 1;
			cell.y = GetRowCount();
			selection.ClearSelection();
			selection.SelectCell(cell);
			assert(CellValid(cell));
			TableScrollToCell(cell, kFalse);
			itsLastSelectedIndex = cell.y;
			TableRefresh();
			}
		else
			{
			if(iter->Next(&cell))
				{
				if (cell.y < (JCoordinate)GetRowCount())
					{
					selection.ClearSelection();
					cell.y++;
					selection.SelectCell(cell);
					assert(CellValid(cell));
					TableScrollToCell(cell, kFalse);
					itsLastSelectedIndex = cell.y;
					TableRefresh();
					}
				}
			else if (GetRowCount() >= 1)
				{
				cell.x = 1;
				cell.y = 1;
				selection.SelectCell(cell);
				assert(CellValid(cell));
				TableScrollToCell(cell, kFalse);
				itsLastSelectedIndex = cell.y;
				TableRefresh();
				}
			}
		delete iter;
		}
	else if (key == kJReturnKey)
		{
		JTableSelection& selection = GetTableSelection();
		JTableSelectionIterator* iter = 
			new JTableSelectionIterator(&selection);
		JPoint cell;
		while(iter->Next(&cell))
			{
			assert(itsList->IndexValid(cell.y));
			GMessageHeader* header = itsList->NthElement(cell.y);
			itsDir->ViewMessage(header);
//			GMessageViewDir* dir = 
//				new GMessageViewDir(itsDir, itsDir->GetMailFile(), header);
//			assert(dir != NULL);
//			dir->Activate();
			if (header->GetMessageStatus() == GMessageHeader::kNew)
				{
				itsDir->DataModified();
				header->SetMessageStatus(GMessageHeader::kRead);
				TableRefreshRow(cell.y);
				}
			}
		delete iter;
		}
	else if (key == 'w' && modifiers.meta())
		{
		(GetWindow()->GetDirector())->Close();
		}
	else if (key == 'd' && modifiers.meta())
		{
		JTableSelection& selection = GetTableSelection();
		JTableSelectionIterator* iter = 
			new JTableSelectionIterator(&selection);
		assert(iter != NULL);
		JPoint cell;
		while(iter->Next(&cell))
			{
			assert(itsList->IndexValid(cell.y));
			GMessageHeader* header = itsList->NthElement(cell.y);
			header->SetMessageStatus(GMessageHeader::kDelete);
			itsDir->CloseMessage(header);
			itsDir->DataModified();
			}
		TableRefresh();
		}
	else
		{
		JXTable::HandleKeyPress(key, modifiers);
		}
}

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

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

void
GMessageTable::TableDrawCell
	(
	JPainter& p, 
	const JPoint& cell, 
	const JRect& rect
	)
{
	if ((GetTableSelection()).IsSelected(cell.y, 1))
		{
		p.SetPenColor(GetColormap()->GetDefaultSelectionColor());
		p.SetFilling(kTrue);
		p.Rect(rect);
		p.SetPenColor(GetColormap()->GetBlackColor());
		p.SetFilling(kFalse);
		}
	GMessageHeader* header = itsList->NthElement(cell.y);
	JBoolean isNew = kFalse;
	if (header->GetMessageStatus() == GMessageHeader::kNew)
		{
		isNew = kTrue;
		}
	JBoolean isDelete = kFalse;
	if (header->GetMessageStatus() == GMessageHeader::kDelete)
		{
		isDelete = kTrue;
		}
	JString str;
	JPainter::HAlignment align = JPainter::kHAlignLeft;
	if (cell.x == kFromIndex)
		{
		str = header->GetFromNames();
		}
	else if (cell.x == kSubjectIndex)
		{
		str = header->GetSubject();
		}
	else if (cell.x == kDateIndex)
		{
		str = " " + header->GetShortDate();
		}
	else if (cell.x == kSizeIndex)
		{
		JIndex size = header->GetMessageEnd() - header->GetHeaderEnd();
		if (size < 10000)
			{
			str = JString(size);
			}
		else if (size < 10000000)
			{
			str = JString(size/1000) + "K";
			}
		else
			{
			str = JString(size/1000000) + "M";
			}
		str += "  ";
		align = JPainter::kHAlignRight;
		}
	JRect r(rect);
	r.left += kCellBuffer;
//	p.SetFontSize(kJSmallFontSize);
	JColorIndex cindex = JGetCurrColormap()->GetBlackColor();
	if (header->GetTo().IsEmpty())
		{
		cindex = JGetCurrColormap()->GetGray50Color();
		}
	p.SetFontStyle(JFontStyle(isNew, kFalse, 0, kFalse, cindex));
	if (cell.x > (JCoordinate)kIconIndex)
		{
		p.String(r, str, align, JPainter::kVAlignCenter);
		}
	if (cell.x == kIconIndex)
		{
		JRect irect = rect;
		irect.left += 3;
		p.Image(*itsMailIcon, itsMailIcon->GetBounds(), irect);
		}
	if (cell.x == kPriorityIndex)
		{
		if (isDelete)
			{
			JRect irect = rect;
			irect.left += 8;
			p.Image(*itsCheckIcon, itsCheckIcon->GetBounds(), irect);
//			p.Image(*itsCheckIcon, itsCheckIcon->GetBounds(), irect.left, irect.top);
			}
		else if (isNew)
			{
//			p.String(r, "N", JPainter::kHAlignCenter, JPainter::kVAlignCenter);
			}
		}
}

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

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

void
GMessageTable::HandleMouseDown
	(
	const JPoint& pt, 
	const JXMouseButton button,
	const JSize clickCount,
	const JXButtonStates& buttonStates,
	const JXKeyModifiers& modifiers
	)
{
	itsIsWaitingForDeselect = kFalse;
	if (button > kJXRightButton)
		{
		ScrollForWheel(button, modifiers);
		return;
		}
	JPoint cell;
	if (!GetCell(pt, &cell))
		{
		itsDownInCell = kFalse;
		(GetTableSelection()).ClearSelection();
		itsLastSelectedIndex = 0;
		TableRefresh();
		return;
		}
	
	itsDownInCell = kTrue;
	itsDownPt = pt;
	if (clickCount == 1)
		{
		if (modifiers.control())
			{
			AddToSelection(cell.y);
			}			
		else if (modifiers.shift())
			{
			ExtendSelection(cell.y);
			} 			
		else if (button == kJXRightButton)
			{
			ExtendSelection(cell.y);
			}
		else 
			{
			if (GetTableSelection().IsSelected(cell.y, 1))
				{
				itsIsWaitingForDeselect = kTrue;
				}
			else
				{
				ElementSelected(cell.y);
				}
			}
		}
	else if (clickCount == 2)
		{
		assert(itsList->IndexValid(cell.y));
		GMessageHeader* header = itsList->NthElement(cell.y);
		itsDir->ViewMessage(header);
//		GMessageViewDir* dir = 
//			new GMessageViewDir(itsDir, itsDir->GetMailFile(), header);
//		assert(dir != NULL);
//		dir->Activate();
		if (header->GetMessageStatus() == GMessageHeader::kNew)
			{
			itsDir->DataModified();
			header->SetMessageStatus(GMessageHeader::kRead);
			TableRefresh();
			}
		}
}

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

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

void
GMessageTable::HandleMouseDrag
	(
	const JPoint&			pt,
	const JXButtonStates&	buttonStates,
	const JXKeyModifiers&	modifiers
	)
{
	JPoint cell;
	if (itsDownInCell)
		{
		JPoint p = itsDownPt - pt;
		if (sqrt(p.x*p.x + p.y*p.y) > kDragBeginBuffer)
			{
			itsIsWaitingForDeselect = kFalse;
//			cout << "a1" << flush;
			JBoolean updated = itsDir->Update();
//			cout << "a2" << flush;
			if (!updated && !GFileLocked(itsDir->GetMailFile()) &&
				GLockFile(itsDir->GetMailFile()))
				{
//				cout << "a3" << flush;
				itsUnlockTask = new JXTimerTask(500);
				assert(itsUnlockTask != NULL);
				JXGetApplication()->InstallIdleTask(itsUnlockTask);
				ListenTo(itsUnlockTask);
				BeginDND(pt, buttonStates, modifiers);
				}
			}
		}
//	cout << "a4" << endl;
}

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

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

void
GMessageTable::HandleMouseUp
	(
	const JPoint& pt, 
	const JXMouseButton button,
	const JXButtonStates& buttonStates,
	const JXKeyModifiers& modifiers
	)
{
	JPoint cell;
	if (GetCell(pt, &cell) && itsIsWaitingForDeselect)
		{
		ElementSelected(cell.y);
		}
	itsIsWaitingForDeselect = kFalse;
}

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


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

void
GMessageTable::DNDInit
	(
	const JPoint& pt, 
	const JXButtonStates& buttonStates,
	const JXKeyModifiers& modifiers
	)
{
}

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

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

void
GMessageTable::HandleDNDHere
	(
	const JPoint& pt,
	const JXWidget* source
	)
{
	JIndex oldIndex = itsCurrentDndHereIndex;
	itsIsDND = kTrue;
	ScrollForDrag(pt);
	JPoint cell;
	itsAboveTop = kFalse;
	if (GetCell(pt, &cell))
		{
		if (cell.y == 1)
			{
			assert(CellValid(JPoint(1, 1)));
			JRect rectt = GetCellRect(JPoint(1, 1));
			if (pt.y < rectt.ycenter())
				{
				itsAboveTop = kTrue;
				}
			}
		itsCurrentDndHereIndex = cell.y;
		assert(RowIndexValid(itsCurrentDndHereIndex));
		TableRefreshRow(itsCurrentDndHereIndex);
		}
	else if (GetRowCount() > 0)
		{
		assert(CellValid(JPoint(1, 1)));
		JRect rectt = GetCellRect(JPoint(1, 1));
		
		assert(CellValid(JPoint(1, GetRowCount())));
		JRect rectb = GetCellRect(JPoint(1, GetRowCount()));

		if (pt.y < rectt.ycenter())
			{
			itsCurrentDndHereIndex = 1;
			itsAboveTop = kTrue;
			assert(RowIndexValid(itsCurrentDndHereIndex));
			TableRefreshRow(itsCurrentDndHereIndex);
			}
		else if (pt.y > rectb.bottom)
			{
			itsCurrentDndHereIndex = GetRowCount();
			assert(RowIndexValid(itsCurrentDndHereIndex));
			TableRefreshRow(itsCurrentDndHereIndex);
			}
		else
			{
			itsCurrentDndHereIndex = 0;
			}
		}
	else
		{
		itsCurrentDndHereIndex = 0;
		}
	if (oldIndex != 0)
		{
		if (RowIndexValid(oldIndex))
			{
			TableRefreshRow(oldIndex);
			}
		}
}

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

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

void
GMessageTable::HandleDNDLeave()
{
	itsIsDND = kFalse;
	JIndex oldIndex = itsCurrentDndHereIndex;
//	itsCurrentDndHereIndex = 0;
	if (oldIndex != 0)
		{
		assert(RowIndexValid(oldIndex));
		TableRefreshRow(oldIndex);
		}
}

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

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

void
GMessageTable::HandleDNDDrop
	(
	const JPoint& 		pt,
	const JArray<Atom>& typeList,
	const Atom 			action,
	const Time			time,
	const JXWidget* 	source
	)
{
	JBoolean dropOnSelf = kTrue;
	if (source != this)
		{
		dropOnSelf = kFalse;
		}
	Atom realAction = action;
	JXDNDManager* dndMgr  = GetDNDManager();
	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;
			}
		}
		
	itsIsDND = kFalse;
	HandleDNDLeave();
	JPoint cell;
	JBoolean isCell = GetCell(pt, &cell);
	GMessageHeader* dest;
	JTableSelection& selection = GetTableSelection();
	if (isCell)
		{
		JIndex row = cell.y;
		assert(itsList->IndexValid(row));
		dest = itsList->NthElement(row);
		if (selection.IsSelected(row, 1) && dropOnSelf)
			{
			return;
			}
		}
	if (dropOnSelf)
		{
		assert(selection.HasSelection());
		itsDir->DataModified();
		JTableSelectionIterator* iter = 
			new JTableSelectionIterator(&selection);
		JPoint cell;
		JPtrArray<GMessageHeader> list;
		if (isCell)
			{
			iter->MoveTo(kJIteratorStartAtEnd, cell);
			while(iter->Prev(&cell))
				{
				GMessageHeader* src = itsList->NthElement(cell.y);
				itsList->Remove(src);
				if ((itsCurrentDndHereIndex == 1) && itsAboveTop)
					{
					//itsList->InsertBefore(dest, src);
					itsList->Prepend(src);
					list.Prepend(src);
					}
				else
					{
					itsList->InsertAfter(dest, src);
					list.Append(src);
					}				
				}
			}
		else 
			{
			while(iter->Next(&cell))
				{
				GMessageHeader* src = itsList->NthElement(cell.y);
				itsList->Remove(src);
				if ((itsCurrentDndHereIndex == 1) && itsAboveTop)
					{
					itsList->Prepend(src);
					list.Prepend(src);
					}
				else
					{
					itsList->Append(src);
					list.Append(src);
					}
				}				
			}
		selection.ClearSelection();
		for (JSize index = 1; index <= list.GetElementCount(); index++)
			{
			JIndex findindex;
			GMessageHeader* src = list.NthElement(index);
			JBoolean ok = itsList->Find(src, &findindex);
			assert(ok);
			selection.SelectCell(findindex, 1);
			itsLastSelectedIndex = findindex;	
			assert(CellValid(JPoint(1, findindex)));
			TableScrollToCell(JPoint(1, findindex));		
			}
		TableRefresh();
		}
	else
		{
		unsigned char* data = NULL;
		JSize dataLength;
		Atom returnType;
		JXSelectionManager* selManager = GetSelectionManager();
		JXSelectionManager::DeleteMethod delMethod;
		const Atom dndName = (GetDNDManager())->GetDNDSelectionName();
		if (selManager->GetSelectionData(dndName, time, GetWindow(), itsMessageXAtom,
										 &returnType, &data, &dataLength, &delMethod))
			{
			jistrstream(is, reinterpret_cast<char*>(data), dataLength);
			JPtrArray<GMessageHeader> list;
			JString mbox;
			is >> mbox;
			int count;
			is >> count;
			for (int index = 1; index <= count; index++)
				{
				GMessageHeader* header = new GMessageHeader();
				assert(header != NULL);
				is >> *header;
				list.Append(header);
				}
//			cout << "*a4*" << flush;
			GBlockUntilFileUnlocked(itsDir->GetMailFile());
//			cout << "*a5*" << endl;
			if (!GLockFile(itsDir->GetMailFile()))
				{
				selManager->DeleteSelectionData(&data, delMethod);
				return;
				}
			JString mailfile = itsDir->GetMailFile();
			JString tempname = mailfile + ".tmp";
			JBoolean first 	= kTrue;
			JBoolean ok 	= kTrue;
			mode_t perms, perms2;			
			while (itsDir->Update() || first) 
				{
				first = kFalse;
				JError err = JGetPermissions(mailfile, &perms);
				if (!err.OK())
					{
					perms = 0600;
					}
				err = JGetPermissions(mbox, &perms2);
				if (!err.OK())
					{
					perms2 = 0600;
					}
				fstream is(mailfile, ios::in|ios::out, perms);
				fstream mboxis(mbox, ios::in|ios::out, perms2);
//				ifstream is(mailfile);
//				ifstream mboxis(mbox);
				ofstream os(tempname);
				ok = JConvertToBoolean(os.good());
				if (!ok)
					{
					tempname = JGetTempFileName();
					os.close();
					os.open(tempname);
					}
				
				JSize count = itsList->GetElementCount();
				JSize lcount = list.GetElementCount();
				JSize index;
				if (isCell && ((itsCurrentDndHereIndex != 1) || !itsAboveTop))
					{
					JIndex row = cell.y;
					assert(itsList->IndexValid(row));
					GMessageHeader* rowHeader = itsList->NthElement(row);
					JSize bytes = rowHeader->GetMessageEnd();
				//	JCopyBinaryData(is, os, bytes);
				// The following is new:
					for (index = 1; index <= row; index++)
						{
						GMessageHeader* header = itsList->NthElement(index);
						JSize pos = JTellp(os);
						GSaveMessage(is, os, header, kTrue, kFalse);
						}

					for (index = 1; index <= lcount; index++)
						{
						GMessageHeader* header = list.NthElement(index);
						JSize pos = JTellp(os);
						GSaveMessage(mboxis, os, header, kTrue, kFalse);
						}
					for (index = row+1; index <= count; index++)
						{
						GMessageHeader* header = itsList->NthElement(index);
						JSize pos = JTellp(os);
						GSaveMessage(is, os, header, kTrue, kFalse);
						}
					GMessageHeader* dest = itsList->NthElement(row);
					for (index = lcount; index >= 1; index--)
						{
						GMessageHeader* src = list.NthElement(index);
						if (row == 1 && itsAboveTop)
							{
							itsList->InsertBefore(dest, src);
							}
						else
							{
							itsList->InsertAfter(dest, src);
							}
						}
					}
				else
					{
					if ((itsCurrentDndHereIndex == 1) && itsAboveTop)
						{
						for (index = 1; index <= lcount; index++)
							{
							GMessageHeader* header = list.NthElement(index);
							GSaveMessage(mboxis, os, header, kTrue, kFalse);
							}
						for (index = 1; index <= count; index++)
							{
							GMessageHeader* header = itsList->NthElement(index);
							GSaveMessage(is, os, header, kTrue, kFalse);
							}
						for (index = lcount; index >= 1; index--)
							{
							GMessageHeader* header = list.NthElement(index);
							itsList->Prepend(header);
							}
						}
					else
						{
//						if (count > 0)
//							{
//							GMessageHeader* lastHeader = itsList->NthElement(count);
//							JSize bytes = lastHeader->GetMessageEnd();
//							JCopyBinaryData(is, os, bytes);
//							}
// The following is new:
						for (index = 1; index <= count; index++)
							{
							GMessageHeader* header = itsList->NthElement(index);
							JSize pos = JTellp(os);
							GSaveMessage(is, os, header, kTrue, kFalse);
							}
						for (index = 1; index <= lcount; index++)
							{
							GMessageHeader* header = list.NthElement(index);
							JSize pos = JTellp(os);
							GSaveMessage(mboxis, os, header, kTrue, kFalse);
							itsList->Append(header);
							}
						}
					}
				is.close();
				os.close();
				mboxis.close();
				}

			GMReplaceFileWithTemp(tempname, mailfile);

//			JSetPermissions(mailfile, perms);
//			JSetPermissions(mbox, perms2);
			GUnlockFile(mailfile);
			GUnlockFile(mbox);
			itsDir->UpdateFileEntry();
			itsDir->GenerateFromList();
			selManager->DeleteSelectionData(&data, delMethod);
			selection.ClearSelection();
			for (JSize index = 1; index <= list.GetElementCount(); index++)
				{
				JIndex findindex;
				GMessageHeader* src = list.NthElement(index);
				JBoolean ok = itsList->Find(src, &findindex);
				assert(ok);
				selection.SelectCell(findindex, 1);
				itsLastSelectedIndex = findindex;	
				assert(CellValid(JPoint(1,findindex)));
				TableScrollToCell(JPoint(1, findindex));		
				}
			if (realAction == dndMgr->GetDNDActionMoveXAtom())
				{
				selManager->SendDeleteSelectionRequest(dndName, time, GetWindow());
				}
			}
		}
	itsDir->UpdateMessageCount();
}

/******************************************************************************
 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
GMessageTable::GetDNDAction
	(
	const JXContainer*		target,
	const JXButtonStates&	buttonStates,
	const JXKeyModifiers&	modifiers
	)
{
	if (target == this)
		{
		return (GetDNDManager())->GetDNDActionMoveXAtom();
		}
	else if (modifiers.control())
		{
		return (GetDNDManager())->GetDNDActionAskXAtom();
		}
	else if (modifiers.meta())
		{
		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
GMessageTable::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);
}

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

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

JBoolean
GMessageTable::WillAcceptDrop
	(
	const JArray<Atom>& typeList, 
	Atom*				action,
	const Time			time,
	const JXWidget*		source
	)
{
	JBoolean found = kFalse;
	const JSize typeCount = typeList.GetElementCount();
	for (JSize i=1; i<=typeCount; i++)
		{
		Atom type = typeList.GetElement(i);
		if (type == itsMessageXAtom)
			{
			found = kTrue;
			}
		}
	return found;
}

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

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

JBoolean 
GMessageTable::ConvertSelection
	(
	const Atom name, 
	const Atom type, 
	Atom* returnType,
	unsigned char** data,
	JSize* dataLength, 
	JSize* bitsPerBlock
	)
{
	const Atom dndName = (GetDNDManager())->GetDNDSelectionName();
	JXSelectionManager* selMgr = GetSelectionManager();
	*bitsPerBlock = 8;

	if (type == itsMessageXAtom)
		{
		ostrstream os;
		
		JTableSelection& selection = GetTableSelection();
		JTableSelectionIterator iter(&selection);
		os << itsDir->GetMailFile();
		os << selection.GetSelectedCellCount() << ' ';
		JPoint cell;
		while(iter.Next(&cell))
			{
			assert(itsList->IndexValid(cell.y));
			GMessageHeader* header = itsList->NthElement(cell.y);
			os << *header;
			}
		os << ends;
		*dataLength = strlen(os.str());
		*data = new unsigned char[ *dataLength ];
		assert (*data != NULL);
		memcpy(*data, os.str(), *dataLength);
		*returnType = itsMessageXAtom;
		return kTrue;
		}
	else if (type == selMgr->GetDeleteSelectionXAtom() && name == dndName)
		{
		JTableSelectionIterator iter(&GetTableSelection());
		JPoint cell;
		while(iter.Next(&cell))
			{
			GMessageHeader* header = itsList->NthElement(cell.y);
			header->SetMessageStatus(GMessageHeader::kDelete);
			itsDir->DataModified();
			itsDir->CloseMessage(header);
			}
		TableRefresh();
		
		*data       = new unsigned char[1];
		*dataLength = 0;
		*returnType = selMgr->GetNULLXAtom();
		return kTrue;
		}
	return kFalse;
}
/******************************************************************************
 ExtendSelection

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

void 
GMessageTable::ExtendSelection
	(
	const JIndex index
	)
{
	if (itsLastSelectedIndex == 0)
		{
		return;
		}

	(GetTableSelection()).ClearSelection();
	if (itsLastSelectedIndex > index)
		{
		for (JIndex i = index; i <= itsLastSelectedIndex; i++)
			{
			(GetTableSelection()).SelectCell(i, 1);
			}
		}
	else
		{
		for (JIndex i = itsLastSelectedIndex; i <= index; i++)
			{
			(GetTableSelection()).SelectCell(i, 1);
			}
		}
	TableRefresh();	
}

/******************************************************************************
 AddToSelection

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

void 
GMessageTable::AddToSelection
	(
	const JIndex index
	)
{
	if ((GetTableSelection()).IsSelected(index, 1))
		{
		(GetTableSelection()).SelectCell(index, 1, kFalse);
		}
		
	else
		{
		(GetTableSelection()).SelectCell(index, 1);
		itsLastSelectedIndex = index;
		}
	TableRefreshRow(index);
}

/******************************************************************************
 ElementSelected

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

void 
GMessageTable::ElementSelected
	(
	const JIndex index
	)
{
	(GetTableSelection()).ClearSelection();
	(GetTableSelection()).SelectCell(index, 1);
	itsLastSelectedIndex = index;
	TableRefresh();
}

/******************************************************************************
 SetList

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

void
GMessageTable::SetList
	(
	GMessageHeaderList* list
	)
{
	if (itsList != NULL)
		{
		StopListening(itsList);
		}
	itsList = list;
	ListenTo(itsList);
	SyncWithList();
	JBoolean found = kFalse;
	JSize count = itsList->GetElementCount();
	if (count == 0)
		{
		return;
		}
	// is last element new?
	GMessageHeader* header = itsList->NthElement(count);
	if (header->GetMessageStatus() != GMessageHeader::kNew)
		{
		TableScrollToCell(JPoint(1, GetRowCount()));
		return;
		}

	// find first new message (from the bottom)
	JIndex firstNew = count;
	while (firstNew > 1)
		{
		GMessageHeader* header = itsList->NthElement(firstNew - 1);
		if (header->GetMessageStatus() == GMessageHeader::kNew)
			{
			firstNew--;
			}
		else
			{
			break;
			}
		}
	
	// scroll the first message to the top of the screen
	UpdateScrollbars();
	JRect rect = GetCellRect(JPoint(1, firstNew));
	ScrollTo(0, rect.top);
}

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

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

void
GMessageTable::SyncWithList()
{
	for (JSize index = 1; index <= itsList->GetElementCount(); index++)
		{
		AppendRow(itsLineHeight);
		}
}

/******************************************************************************
 UpdateMessageMenu	 


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

void
GMessageTable::UpdateMessageMenu()
{
	JTableSelection& selection = GetTableSelection();
	JTableSelectionIterator iter(&selection);
	JPoint cell;
	JBoolean deleted 	= kFalse;
	JBoolean undeleted	= kFalse;
	while(iter.Next(&cell))
		{
		assert(itsList->IndexValid(cell.y));
		GMessageHeader* header = itsList->NthElement(cell.y);
		if (header->GetMessageStatus() == GMessageHeader::kDelete)
			{
			deleted 	= kTrue;
			}
		else
			{
			undeleted	= kTrue;
			}
		}
	if (deleted)
		{
		itsMessageMenu->EnableItem(kUndeleteCmd);
		}
	else
		{
		itsMessageMenu->DisableItem(kUndeleteCmd);
		}
	if (undeleted)
		{
		itsMessageMenu->EnableItem(kDeleteCmd);
		}
	else
		{
		itsMessageMenu->DisableItem(kDeleteCmd);
		}
	if (selection.GetSelectedCellCount() == 1)
		{
		itsMessageMenu->EnableItem(kReplyCmd);
		itsMessageMenu->EnableItem(kReplySenderCmd);
		itsMessageMenu->EnableItem(kReplyAllCmd);
		itsMessageMenu->EnableItem(kForwardCmd);
		itsMessageMenu->EnableItem(kRedirectCmd);
		}
	else
		{
		itsMessageMenu->DisableItem(kReplyCmd);
		itsMessageMenu->DisableItem(kReplySenderCmd);
		itsMessageMenu->DisableItem(kReplyAllCmd);
		itsMessageMenu->DisableItem(kForwardCmd);
		itsMessageMenu->DisableItem(kRedirectCmd);
		}
}

/******************************************************************************
 HandleMessageMenu 


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

void
GMessageTable::HandleMessageMenu
	(
	const JIndex index
	)
{
	JTableSelection& selection = GetTableSelection();
	if (index == kSelectAllCmd)
		{
		if (GetRowCount() > 0)
			{
			selection.SelectCol(1);
			TableRefresh();
			}
		}
	else if (index == kDeleteCmd)
		{
		JTableSelectionIterator iter(&selection);
		JPoint cell;
		while(iter.Next(&cell))
			{
			assert(itsList->IndexValid(cell.y));
			GMessageHeader* header = itsList->NthElement(cell.y);
			if (header->GetMessageStatus() != GMessageHeader::kDelete)
				{
				itsDir->DataModified();
				}
			header->SetMessageStatus(GMessageHeader::kDelete);
			itsDir->CloseMessage(header);
			}
		TableRefresh();
		}
	else if (index == kUndeleteCmd)
		{
		JTableSelectionIterator iter(&selection);
		JPoint cell;
		while(iter.Next(&cell))
			{
			assert(itsList->IndexValid(cell.y));
			GMessageHeader* header = itsList->NthElement(cell.y);
			if (header->GetMessageStatus() == GMessageHeader::kDelete)
				{
				itsDir->DataModified();
				}
			header->SetMessageStatus(GMessageHeader::kRead);
			}
		TableRefresh();
		}
	else if (index == kReplyCmd)
		{
		JTableSelectionIterator iter(&selection);
		JPoint cell;
		JBoolean ok = iter.Next(&cell);
		assert(ok);
		assert(itsList->IndexValid(cell.y));
		GMessageHeader* header = itsList->NthElement(cell.y);
		Reply(header);
		}
	else if (index == kReplySenderCmd)
		{
		JTableSelectionIterator iter(&selection);
		JPoint cell;
		JBoolean ok = iter.Next(&cell);
		assert(ok);
		assert(itsList->IndexValid(cell.y));
		GMessageHeader* header = itsList->NthElement(cell.y);
		ReplySender(header);
		}
	else if (index == kReplyAllCmd)
		{
		JTableSelectionIterator iter(&selection);
		JPoint cell;
		JBoolean ok = iter.Next(&cell);
		assert(ok);
		assert(itsList->IndexValid(cell.y));
		GMessageHeader* header = itsList->NthElement(cell.y);
		ReplyAll(header);
		}
	else if (index == kForwardCmd)
		{
		JTableSelectionIterator iter(&selection);
		JPoint cell;
		JBoolean ok = iter.Next(&cell);
		assert(ok);
		assert(itsList->IndexValid(cell.y));
		GMessageHeader* header = itsList->NthElement(cell.y);
		Forward(header);
		}
	else if (index == kRedirectCmd)
		{
		JTableSelectionIterator iter(&selection);
		JPoint cell;
		JBoolean ok = iter.Next(&cell);
		assert(ok);
		assert(itsList->IndexValid(cell.y));
		GMessageHeader* header = itsList->NthElement(cell.y);
		Redirect(header);
		}
}

/******************************************************************************
 DeleteHeader 


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

void
GMessageTable::DeleteHeader
	(
	GMessageHeader* header
	)
{
	JIndex findindex;
	if (itsList->Find(header, &findindex))
		{
		if (header->GetMessageStatus() != GMessageHeader::kDelete)
			{
			itsDir->DataModified();
			}
		header->SetMessageStatus(GMessageHeader::kDelete);
		TableRefreshRow(findindex);
		}
}

/******************************************************************************
 ShowMessageRead 


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

void
GMessageTable::ShowMessageRead
	(
	GMessageHeader* header
	)
{
	JIndex findindex;
	if (itsList->Find(header, &findindex))
		{
		if (header->GetMessageStatus() == GMessageHeader::kNew)
			{
			itsDir->DataModified();
			header->SetMessageStatus(GMessageHeader::kRead);
			TableRefreshRow(findindex);
			}		
		}
}

/******************************************************************************
 SelectHeader 


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

void
GMessageTable::SelectHeader
	(
	GMessageHeader* header
	)
{
	JIndex findindex;
	if (itsList->Find(header, &findindex))
		{
		GetTableSelection().ClearSelection();
		GetTableSelection().SelectCell(findindex, 1);
		assert(CellValid(JPoint(1, findindex)));
		TableScrollToCell(JPoint(1, findindex));
		itsLastSelectedIndex = findindex;
		TableRefresh();
		}
}

/******************************************************************************
 UnSelectHeader 


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

void
GMessageTable::UnSelectHeader
	(
	GMessageHeader* header
	)
{
	JIndex findindex;
	if (itsList->Find(header, &findindex))
		{
		GetTableSelection().SelectCell(findindex, 1, kFalse);
		TableRefreshRow(findindex);
		}
}

/******************************************************************************
 Reply 


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

void
GMessageTable::Reply
	(
	GMessageHeader*	header,
	const JBoolean 	useReplyTo,
	const JBoolean 	useCC, 
	const JBoolean 	fillTo, 
	const JBoolean 	insertText,
	const JBoolean 	quoteText
	)
{
	GMessageEditDir* dir = new GMessageEditDir(JXGetApplication());
	assert(dir != NULL);
	JString sub = header->GetSubject();
	GFixHeaderForReply(&sub);
	dir->SetSubject(sub);
	dir->SetSentFrom(header->GetFrom());
	dir->SetSentDate(header->GetDate());
	if (useCC)
		{
		JString cc = header->GetCC();
		if (cc.IsEmpty())
			{
			cc = header->GetTo();
			}
		else
			{
			cc += ", " + header->GetTo();
			}
		dir->SetCC(cc);
		}
	JString tempfile = JGetTempFileName("/tmp/");
	ofstream os(tempfile);
	fstream is(itsDir->GetMailFile(), ios::in|ios::out);
	assert(is.good());
	is.seekp(header->GetHeaderEnd());
	JString text = JRead(is, header->GetMessageEnd() - header->GetHeaderEnd());
	text.Print(os);
	os.close();
	dir->SetText(tempfile);
	dir->Activate();
	JString replyTo = header->GetReplyTo();
	if (replyTo.IsEmpty())
		{
		replyTo = header->GetFrom();
		}
	else if (useCC)
		{
		replyTo += ", " + header->GetFrom();
		}
	if (fillTo)
		{
		if (replyTo.IsEmpty() || !useReplyTo)
			{
			dir->SetTo(header->GetFrom());
			}
		else
			{
			dir->SetTo(replyTo);
			}
		}
	if (insertText)
		{
		dir->InsertText(quoteText);
		}
	else if (GGetPrefsMgr()->AutoInsertingQuote() && (text.GetLength() <= GGetPrefsMgr()->GetMaxQuote()))
		{
		dir->InsertText(kTrue);
		}
}

/******************************************************************************
 ReplySender 


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

void
GMessageTable::ReplySender
	(
	GMessageHeader* header
	)
{
	Reply(header, kFalse);
}

/******************************************************************************
 ReplyAll 


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

void
GMessageTable::ReplyAll
	(
	GMessageHeader* header
	)
{
	Reply(header, kTrue, kTrue);
}

/******************************************************************************
 Forward 


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

void
GMessageTable::Forward
	(
	GMessageHeader* header
	)
{
	Reply(header, kFalse, kFalse, kFalse, kTrue, kTrue);
}

/******************************************************************************
 Redirect 


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

void
GMessageTable::Redirect
	(
	GMessageHeader* header
	)
{
	Reply(header, kFalse, kFalse, kFalse, kTrue, kFalse);
}

/******************************************************************************
 AbortMessageDND (public)

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

void
GMessageTable::AbortMessageDND()
{
if (IsDNDSource())
	{
	if (GetDNDManager()->CancelDND())
		{
		itsDir->GetWindow()->EndDrag(this, JPoint(0,0), 
									 GetDisplay()->GetLatestButtonStates(),
									 GetDisplay()->GetLatestKeyModifiers());
		}
	GUnlockFile(itsDir->GetMailFile());
	}
}