/******************************************************************************
 GAddressBookTreeWidget.cc

	<Description>

	BASE CLASS = public JXNamedTreeListWidget

	Copyright © 1997 by Glenn W. Bach.  All rights reserved.
	
	Base code generated by Codemill v0.1.0

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

#include <GAddressBookTreeWidget.h>
#include <GAddressBookTreeDir.h>
#include <GAddressBaseTreeNode.h>
#include <GMessageHeader.h>
#include <JTree.h>
#include <GAddressBookTreeDir.h>
#include <GAddressBookTreeNode.h>
#include <GAddressItemTreeNode.h>
#include <GAddressEntryTreeNode.h>
#include <GMGlobals.h>
#include "envelopes_cursor.h"
#include "GMApp.h"
#include "gMailUtils.h"
#include <GAddressBookMgr.h>

#include "address_entry.xpm"
#include "address_entry_selected.xpm"
#include "address_email.xpm"
#include "address_comment.xpm"
#include "address_name.xpm"
#include "address_book.xpm"
#include "address_book_selected.xpm"
#include "envelopes.xpm"

#include <JNamedTreeNode.h>
#include <JNamedTreeList.h>

#include <JXToolBar.h>
#include <JXMenuBar.h>
#include <JXTextMenu.h>
#include <JString.h>
#include <JXWindow.h>
#include <JXWindowPainter.h>
#include <JXDirector.h>
#include <JXSelectionManager.h>
#include <JXUserNotification.h>
#include <JXApplication.h>
#include <JXDNDManager.h>
#include <JXChooseSaveFile.h>
#include <JXImage.h>
#include <JXDisplay.h>
#include <JXColormap.h>
#include <JXInputField.h>

#include <JFontManager.h>
#include <JTableSelection.h>
#include <JTableSelectionIterator.h>
#include <JOrderedSetIterator.h>
#include <JUNIXDirInfo.h>
#include <JUNIXDirEntry.h>
#include <JError.h>
#include <JMinMax.h>

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

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

static const JCharacter* kGAddressBookMenuTitleStr = "Addresses";
static const JCharacter* kGAddressBookMenuStr = 
	"New Name %k Meta-N %i \"NewName::Arrow\""
	"| New address %k Meta-Shift-N %i \"NewAddress::Arrow\""
	"|New address book %i \"NewAddressBook::Arrow\""
	"|Add address book %i \"AddAddressBook::Arrow\""
	"%l| Remove from list %k Meta-R %i \"Remove::Arrow\""
	"| Delete from file system %i \"Delete::Arrow\""
	"%l|Show fcc %k Meta-Shift-F %i \"ShowFCC::Arrow\""
	"| Show comment %k Meta-Shift-C %i \"ShowComment::Arrow\""
	"%l|Edit tool bar... %i \"EditToolBar::Arrow\""
	"%l|Close %k Meta-W %i \"Close::Arrow\"";

enum
{
	kNewNameCmd = 1,
	kNewAddCmd,
	kNewBookCmd,
	kAddBookCmd,
	kRemoveCmd,
	kDeleteCmd,
	kShowFccCmd,
	kShowCommentCmd,
	kEditToolBarCmd,
	kCloseCmd
};

const JCoordinate kDragBeginBuffer 		= 5;
const JCoordinate kCurrentPrefsVersion	= 0;
const JIndex kValueColIndex				= 3;
const JSize kValueColWidth 				= 200;
const JCoordinate kMinRowHeight			= 18;
const JFloat kRowHeightFactor			= 1.2;

static const JCharacter* kDragMessagesXAtomName     = "GMailMessages";
static const JCharacter* kDragAddressBooksXAtomName = "GAddressBooks";
static const JCharacter* kDNDActionCopyDescrip  	= "copy the items";
static const JCharacter* kDNDActionMoveDescrip  	= "move the items";

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

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

GAddressBookTreeWidget*
GAddressBookTreeWidget::Create
	(
	JTree*				tree,
	JNamedTreeList* 	treeList, 
	JXMenuBar* 			menuBar,
	JXScrollbarSet* 	scrollbarSet, 
	JXContainer* 		enclosure,
	const HSizingOption	hSizing, 
	const VSizingOption vSizing,
	const JCoordinate 	x, 
	const JCoordinate 	y,
	const JCoordinate 	w, 
	const JCoordinate 	h
	)
{
	GAddressBookTreeWidget* widget = 
		new GAddressBookTreeWidget(tree, treeList, menuBar, scrollbarSet,
			enclosure, hSizing, vSizing, x, y, w, h);
	assert(widget != NULL);
	widget->JXTreeListWidgetX();
	widget->AppendCol(kValueColWidth);
	widget->SetElasticColIndex(kValueColIndex);
	return widget;
}

GAddressBookTreeWidget::GAddressBookTreeWidget
	(
	JTree*				tree,
	JNamedTreeList* 	treeList, 
	JXMenuBar* 			menuBar,
	JXScrollbarSet* 	scrollbarSet, 
	JXContainer* 		enclosure,
	const HSizingOption	hSizing, 
	const VSizingOption vSizing,
	const JCoordinate 	x, 
	const JCoordinate 	y,
	const JCoordinate 	w, 
	const JCoordinate 	h
	)
	:
   JXNamedTreeListWidget(treeList, scrollbarSet,
   							enclosure, hSizing, vSizing, x, y, w, h),
   	itsStringInputField(NULL)
{
	itsTree 	= tree;

	ListenTo(treeList);
	
	itsAddressBookMenu = menuBar->AppendTextMenu(kGAddressBookMenuTitleStr);
	itsAddressBookMenu->SetMenuItems(kGAddressBookMenuStr);
	itsAddressBookMenu->SetUpdateAction(JXMenu::kDisableNone);
	ListenTo(itsAddressBookMenu);

	itsCurrentDndHereIndex 	= 0;
	itsIsBookDnd			= kFalse;
	itsOutsideRoot			= kFalse;
	itsWaitingToEdit		= kFalse;

	const Atom dndName 		= (GetDNDManager())->GetDNDSelectionName();	
	itsMessageXAtom    		= GetDisplay()->RegisterXAtom(kDragMessagesXAtomName);
	itsAddressBookXAtom 	= AddSelectionTarget(dndName, kDragAddressBooksXAtomName);

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

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

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

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

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

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

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

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

	itsAddressBookMenu->SetItemImage(kNewNameCmd, itsEntryIcon, kFalse);
	itsAddressBookMenu->SetItemImage(kNewAddCmd, itsEMailIcon, kFalse);
	itsAddressBookMenu->SetItemImage(kNewBookCmd, itsBookIcon, kFalse);
	itsAddressBookMenu->SetItemImage(kShowCommentCmd, itsCommentIcon, kFalse);
	
/*	if (!(GetDisplay())->GetCursor(kEnvelopesCursorName, &itsDNDCursor))
		{
		itsDNDCursor =
			(GetDisplay())->CreateCustomCursor(kEnvelopesCursorName, kEnvelopesCursor);
		}*/

	SetDrawOrder(JTable::kDrawByRow);

	JCoordinate height = GetDefaultRowHeight();
	height = JMax(JCoordinate(height*kRowHeightFactor), kMinRowHeight);	
	SetDefaultRowHeight(height);
}

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

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

GAddressBookTreeWidget::~GAddressBookTreeWidget()
{
	delete itsEntryIcon;
	delete itsSelectedEntryIcon;
	delete itsBookIcon;
	delete itsSelectedBookIcon;
	delete itsEMailIcon;
	delete itsNameIcon;
	delete itsMailboxIcon;
	delete itsCommentIcon;
}

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


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

void
GAddressBookTreeWidget::Draw
	(
	JXWindowPainter& p, 
	const JRect& rect
	)
{
	JXNamedTreeListWidget::Draw(p, rect);
	if (itsOutsideRoot)
		{
		p.ResetClipRect();
		if (itsCurrentDndHereIndex == 0)
			{
			JRect crect = GetCellRect(JPoint(3, GetRowCount()));
			crect.bottom -= 2;
			p.Line(0, crect.bottom, crect.right, crect.bottom);
			}
		else
			{
			JRect crect = GetCellRect(JPoint(3, itsCurrentDndHereIndex));
			crect.top++;
			p.Line(0, crect.top, crect.right, crect.top);
			}
		}
}

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

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

void
GAddressBookTreeWidget::TableDrawCell
	(
	JPainter& 		p, 
	const JPoint& 	cell, 
	const JRect& 	rect
	)
{
	if (cell.x == 3)
		{
		if (GetTableSelection().IsSelected(cell.y, 2))
			{
			const JColorIndex origColor = p.GetPenColor();
			const JBoolean origFill     = p.IsFilling();

			p.SetPenColor(JGetCurrColormap()->GetDefaultSelectionColor());
			p.SetFilling(kTrue);
			p.Rect(rect);
			p.SetPenColor(origColor);
			p.SetFilling(origFill);
			}
		JTreeNode* jnode = GetTreeList()->GetNode(cell.y);
		if (jnode->GetDepth() == 3)
			{
			GAddressItemTreeNode* node = 
				dynamic_cast(GAddressItemTreeNode*, jnode);
			p.String(rect, node->GetText(), 
					 JPainter::kHAlignLeft, JPainter::kVAlignCenter);
			}
		}
	else
		{
		JXNamedTreeListWidget::TableDrawCell(p, cell, rect);
		}
}

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


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

void
GAddressBookTreeWidget::Receive
	(
	JBroadcaster*	sender,
	const Message&	message
	)
{
	if (sender == itsAddressBookMenu && message.Is(JXMenu::kItemSelected))
		{
		 const JXMenu::ItemSelected* selection =
			dynamic_cast(const JXMenu::ItemSelected*, &message);
		assert( selection != NULL );
		HandleAddressBookMenu(selection->GetIndex());
		}
	else if (sender == itsAddressBookMenu && message.Is(JXMenu::kNeedsUpdate))
		{
		UpdateAddressBookMenu();
		}
	JXNamedTreeListWidget::Receive(sender, message);
}

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

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

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

	if (key == kJReturnKey && !IsEditing() && (itsStringInputField == NULL))
		{
		if (hadSelection)
			{
			SelectionType type = GetSelectionType();
			if (s.GetSelectedCellCount() == 1 && 
				type == kOneBookSelected)
				{
				if (GetNamedTreeList()->IsOpen(topSelCell.y))
					{
					GetNamedTreeList()->Close(topSelCell.y);
					}
				else
					{
					GetNamedTreeList()->Open(topSelCell.y);
					}
				}
			else if ((type == kOneEntrySelected) ||
					 (type == kEntriesSelected))
				{
				if (modifiers.meta())
					{
					if (GetNamedTreeList()->IsOpen(topSelCell.y))
						{
						GetNamedTreeList()->Close(topSelCell.y);
						}
					else
						{
						GetNamedTreeList()->Open(topSelCell.y);
						}
					}
				else
					{
					JTableSelectionIterator iter(&s);
					JPoint cell;
					while (iter.Next(&cell))
						{
						NewMailFromAddress(cell.y);
						}
					}
				}
			else if ((type == kExtraItemsSelected) ||
					 (type == kItemsSelected))
				{
				JPtrArray<JString> names;
				JTableSelectionIterator iter(&s);
				JPoint cell;
				JBoolean ok = kTrue;
				while (iter.Next(&cell))
					{
					JTreeNode* jnode = GetNamedTreeList()->GetNode(cell.y);
					assert(jnode->GetDepth() == 3);
					GAddressItemTreeNode* item =
						dynamic_cast(GAddressItemTreeNode*, jnode);
					assert(item != NULL);
					if (item->GetType() == GAddressItemTreeNode::kEMail)
						{
						JString* str = new JString(item->GetText());
						assert(str != NULL);
						names.Append(str);
						}
					else
						{
						ok = kFalse;
						}
					}
				if (ok)
					{
					JSize count = names.GetElementCount();
					for (JSize i = 1; i <= count; i++)
						{
						GMGetApplication()->NewMessage(*(names.NthElement(i)));
						}
					}
				names.DeleteAll();
				}
			}
		}

	else if ((key == kJReturnKey) && IsEditing())
		{
		EndEditing();
		}

	else if ((key == kJReturnKey) && (itsStringInputField != NULL))
		{
		EndEditing();
		}

	else if (key == kJForwardDeleteKey || key == kJDeleteKey)
		{
//		DeleteSelected();
		}

	else if ((key == kJEscapeKey) && 
			 !IsEditing() && (itsStringInputField == NULL))
		{
		GetWindow()->GetDirector()->Deactivate();
		}

	else if ((key == kJUpArrow) && !IsEditing() && (itsStringInputField == NULL))
		{
		if (hadSelection)
			{
			if (modifiers.shift())
				{
				if (s.OKToExtendSelection())
					{
					if (oldBoat.y > 1)
						{
						s.ExtendSelection(JPoint(2, oldBoat.y - 1));
						TableScrollToCell(JPoint(1, oldBoat.y - 1));
						TableRefresh();
//						(GetWindow())->Update();
						}
					}
				}
			else
				{
				s.ClearSelection();
				JIndex index = topSelCell.y;
				if (index > 1)
					{
					index -= 1;
					}
				s.SelectCell(index, 2);
				s.SetBoat(JPoint(2, index));
				s.SetAnchor(JPoint(2, index));
				TableScrollToCell(JPoint(1, index));
				TableRefresh();
//				(GetWindow())->Update();
				}
			}
		else
			{
			if (GetRowCount() > 0)
				{
				s.SelectCell(GetRowCount(), 2);
				s.SetBoat(JPoint(2, GetRowCount()));
				s.SetAnchor(JPoint(2, GetRowCount()));
				TableScrollToCell(JPoint(1, GetRowCount()));
				TableRefreshRow(GetRowCount());
				}
			}
		}
	else if ((key == kJDownArrow) && !IsEditing() && (itsStringInputField == NULL))
		{
		if (hadSelection)
			{
			if (modifiers.shift())
				{
				if (s.OKToExtendSelection())
					{
					if (oldBoat.y < (JCoordinate)GetRowCount())
						{
						s.ExtendSelection(JPoint(2, oldBoat.y + 1));
//						(GetWindow())->Update();
						TableScrollToCell(JPoint(1, oldBoat.y + 1));
						TableRefresh();
						}
					}
				}
			else
				{
				s.ClearSelection();
				JIndex index = topSelCell.y;
				if (index < GetRowCount())
					{
					index += 1;
					}
				s.SelectCell(index, 2);
				s.SetBoat(JPoint(2, index));
				s.SetAnchor(JPoint(2, index));
				TableScrollToCell(JPoint(1, index));
				TableRefresh();
//				(GetWindow())->Update();
				}
			}
		else
			{
			if (GetRowCount() > 0)
				{
				s.SelectCell(1, 2);
				s.SetBoat(JPoint(2, 1));
				s.SetAnchor(JPoint(2, 1));
				TableScrollToCell(JPoint(1, 1));
				TableRefreshRow(1);
				}
			}
		}
	else
		{
		JXNamedTreeListWidget::HandleKeyPress(key, modifiers);
		}
}
/******************************************************************************
 UpdateAddressBookMenu 


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

void
GAddressBookTreeWidget::UpdateAddressBookMenu()
{
	itsAddressBookMenu->DisableItem(kNewNameCmd);
	itsAddressBookMenu->DisableItem(kNewAddCmd);
	itsAddressBookMenu->DisableItem(kRemoveCmd);
	itsAddressBookMenu->DisableItem(kDeleteCmd);
	itsAddressBookMenu->DisableItem(kShowFccCmd);
	itsAddressBookMenu->DisableItem(kShowCommentCmd);

	SelectionType type 	= GetSelectionType();
	EditType etype 		= GetEditType();
	if ((type == kNoneSelected) && (etype == kNoneEditing))
		{
		GAddressBookTreeNode* book;
		if (GetFirstWritableBook(&book))
			{
			itsAddressBookMenu->EnableItem(kNewNameCmd);
			}
		}
	else if ((type == kOneBookSelected) || (etype == kBookEditing))
		{
		itsAddressBookMenu->EnableItem(kRemoveCmd);
		JPoint cell;
		if (type == kOneBookSelected)
			{
			JTableSelection& s = GetTableSelection();
			JBoolean ok = s.GetFirstSelectedCell(&cell);
			assert(ok);
			}
		else if (etype == kBookEditing)
			{
			JBoolean ok = GetEditedCell(&cell);
			assert(ok);
			}
		JTreeNode* jnode = GetNamedTreeList()->GetNode(cell.y);
		GAddressBookTreeNode* book =
			dynamic_cast(GAddressBookTreeNode*, jnode);
		assert(book != NULL);
		if (book->OKToChange())
			{
			itsAddressBookMenu->EnableItem(kNewNameCmd);
			itsAddressBookMenu->EnableItem(kDeleteCmd);
			}
		}
	else if (type == kBooksSelected)
		{
		itsAddressBookMenu->EnableItem(kRemoveCmd);
		itsAddressBookMenu->EnableItem(kDeleteCmd);
		}
	else if ((type == kOneEntrySelected) || (etype == kEntryEditing))
		{
		JPoint cell;
		if (type == kOneEntrySelected)
			{
			JTableSelection& s = GetTableSelection();
			JBoolean ok = s.GetFirstSelectedCell(&cell);
			assert(ok);
			}
		else if (etype == kEntryEditing)
			{
			JBoolean ok = GetEditedCell(&cell);
			assert(ok);
			}
		JTreeNode* jnode = GetNamedTreeList()->GetNode(cell.y);
		GAddressEntryTreeNode* node = 
			dynamic_cast(GAddressEntryTreeNode*, jnode);
		assert(node != NULL);
		if (node->OKToChange())
			{
			itsAddressBookMenu->EnableItem(kNewAddCmd);
			itsAddressBookMenu->EnableItem(kRemoveCmd);
			if (!node->HasFcc())
				{
				itsAddressBookMenu->EnableItem(kShowFccCmd);
				}
			if (!node->HasComment())
				{
				itsAddressBookMenu->EnableItem(kShowCommentCmd);
				}
			}
		}
	else if (type == kEntriesSelected)
		{
		itsAddressBookMenu->EnableItem(kRemoveCmd);
		}
	else if (type == kExtraItemsSelected)
		{
		JTableSelection& s = GetTableSelection();
		JTableSelectionIterator iter(&s);
		JBoolean ok = kTrue;
		JPoint cell;
		while (iter.Next(&cell))
			{
			JTreeNode* jnode = GetNamedTreeList()->GetNode(cell.y);
			GAddressItemTreeNode* node = 
				dynamic_cast(GAddressItemTreeNode*, jnode);
			assert(node != NULL);
			if (!node->OKToChange())
				{
				ok = kFalse;
				}
			}
		if (ok)
			{
			itsAddressBookMenu->EnableItem(kRemoveCmd);
			}
		}
	else if (etype == kExtraItemEditing)
		{
		itsAddressBookMenu->EnableItem(kRemoveCmd);
		}
	
}

/******************************************************************************
 HandleAddressBookMenu 


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

void
GAddressBookTreeWidget::HandleAddressBookMenu
	(
	const JIndex index
	)
{
	SelectionType type 	= GetSelectionType();
	EditType etype 		= GetEditType();
	if (index == kNewNameCmd)
		{
		GAddressBookTreeNode* parent;
		if ((type == kNoneSelected) && (etype == kNoneEditing))
			{
			JTreeNode* base = itsTree->GetRoot();
			if (!GetFirstWritableBook(&parent))
				{
				return;
				}
			}
		else if ((type == kOneBookSelected) || (etype == kBookEditing))
			{
			JPoint cell;
			if (type == kOneBookSelected)
				{
				JTableSelection& s = GetTableSelection();
				JBoolean ok = s.GetFirstSelectedCell(&cell);
				assert(ok);
				}
			else if (etype == kBookEditing)
				{
				JBoolean ok = GetEditedCell(&cell);
				assert(ok);
				}
			JBoolean ok = kFalse;
			JTreeNode* jnode = GetTreeList()->GetNode(cell.y);
			parent = dynamic_cast(GAddressBookTreeNode*, jnode);
			assert(parent != NULL);
			if (!parent->OKToChange())
				{
				return;
				}
			}
		JPtrArray<JString> addresses;
		NewNickName(parent, addresses);
		}
	else if (index == kNewAddCmd)
		{
		JPoint cell;
		if (type == kOneEntrySelected)
			{
			JTableSelection& s = GetTableSelection();
			JBoolean ok = s.GetFirstSelectedCell(&cell);
			assert(ok);
			}
		else if (etype == kEntryEditing)
			{
			JBoolean ok = GetEditedCell(&cell);
			assert(ok);
			}
		JTreeNode* jnode = GetNamedTreeList()->GetNode(cell.y);
		GAddressEntryTreeNode* node = 
			dynamic_cast(GAddressEntryTreeNode*, jnode);
		assert(node != NULL);
		GAddressItemTreeNode* item =
			new GAddressItemTreeNode(GAddressItemTreeNode::kEMail, 
									 node, "", kFalse);
		assert(item != NULL);
		JIndex findex;
		JBoolean ok = GetNamedTreeList()->FindNode(node, &findex);
		assert(ok);
		GetNamedTreeList()->Open(findex);
		ok = GetNamedTreeList()->FindNode(item, &findex);
		assert(ok);
		TableScrollToCell(JPoint(1, findex));
		assert(item->OKToChange());
		GetTableSelection().ClearSelection();
		BeginEditing(JPoint(3, findex));
		TableScrollToCell(JPoint(1, findex));
		}
	else if (index == kNewBookCmd)
		{
		JString book;
		NewBook(&book);
		}
	else if (index == kAddBookCmd)
		{
		JPtrArray<JString> fullNameList;
		if (JXGetChooseSaveFile()->ChooseFiles("Select the address books to add", "", &fullNameList))
			{
			JSize count = fullNameList.GetElementCount();
			for (JSize i = 1; i <= count; i++)
				{
				GGetAddressBookMgr()->AddAddressBook(*(fullNameList.NthElement(i)), itsTree);
				}
			}
		}
	else if (index == kShowFccCmd)
		{
		JPoint cell;
		JBoolean ok;
		if (type == kOneEntrySelected)
			{
			JTableSelection& s = GetTableSelection();
			ok = s.GetFirstSelectedCell(&cell);
			assert(ok);
			}
		else if (etype == kEntryEditing)
			{
			ok = GetEditedCell(&cell);
			assert(ok);
			}
		JTreeNode* jnode = GetNamedTreeList()->GetNode(cell.y);
		GAddressEntryTreeNode* node = 
			dynamic_cast(GAddressEntryTreeNode*, jnode);
		assert(node != NULL);
		GAddressItemTreeNode* item =
			new GAddressItemTreeNode(GAddressItemTreeNode::kFcc, 
									 node, "", kFalse);
		assert(item != NULL);
		JIndex findex;
		ok = GetNamedTreeList()->FindNode(node, &findex);
		assert(ok);
		GetNamedTreeList()->Open(findex);
		ok = GetNamedTreeList()->FindNode(item, &findex);
		assert(ok);
		TableScrollToCell(JPoint(1, findex));
		assert(item->OKToChange());
		GetTableSelection().ClearSelection();
		BeginEditing(JPoint(3, findex));
		}
	else if (index == kShowCommentCmd)
		{
		JPoint cell;
		JBoolean ok;
		if (type == kOneEntrySelected)
			{
			JTableSelection& s = GetTableSelection();
			ok = s.GetFirstSelectedCell(&cell);
			assert(ok);
			}
		else if (etype == kEntryEditing)
			{
			ok = GetEditedCell(&cell);
			assert(ok);
			}
		JTreeNode* jnode = GetNamedTreeList()->GetNode(cell.y);
		GAddressEntryTreeNode* node = 
			dynamic_cast(GAddressEntryTreeNode*, jnode);
		assert(node != NULL);
		GAddressItemTreeNode* item =
			new GAddressItemTreeNode(GAddressItemTreeNode::kComment, 
									 NULL, "", kFalse);
		assert(item != NULL);
		if (node->HasFcc())
			{
			JTreeNode* fcc = node->GetChild(node->GetChildCount());
			node->InsertBefore(fcc, item);
			}
		else
			{
			node->Append(item);
			}
		node->SetHasComment(kTrue);
		JIndex findex;
		ok = GetNamedTreeList()->FindNode(node, &findex);
		assert(ok);
		GetNamedTreeList()->Open(findex);
		ok = GetNamedTreeList()->FindNode(item, &findex);
		assert(ok);
		TableScrollToCell(JPoint(1, findex));
		assert(item->OKToChange());
		GetTableSelection().ClearSelection();
		BeginEditing(JPoint(3, findex));
		}
	else if (index == kRemoveCmd)
		{
		if ((type == kOneBookSelected) ||
			(type == kBooksSelected) ||
			(type == kOneEntrySelected) ||
			(type == kEntriesSelected) ||
			(type == kExtraItemsSelected))
			{
			JTableSelection& s = GetTableSelection();
			JTableSelectionIterator iter(&s);
			JPoint cell;
			while (iter.Next(&cell))
				{
				JTreeNode* node = GetNamedTreeList()->GetNode(cell.y);
				delete node;
				}
			}
		else if ((etype == kExtraItemEditing) ||
				 (etype == kEntryEditing) ||
				 (etype == kBookEditing))
			{
			JPoint cell;
			JBoolean ok = GetEditedCell(&cell);
			assert(ok);
			JTreeNode* node = GetNamedTreeList()->GetNode(cell.y);
			delete node;
			}
		}
	else if (index == kDeleteCmd)
		{
		JBoolean ok = JGetUserNotification()->AskUserYes("Are you sure you want to delete the selected address books from the file system?");
		if (ok)
			{
			if ((type == kOneBookSelected) || (type == kBooksSelected))
				{
				JTableSelection& s = GetTableSelection();
				JTableSelectionIterator iter(&s);
				JPoint cell;
				while (iter.Next(&cell))
					{
					JTreeNode* jnode = GetNamedTreeList()->GetNode(cell.y);
					GAddressBookTreeNode* node = dynamic_cast(GAddressBookTreeNode*, jnode);
					JError err = JRemoveFile(node->GetFullPathAndName());
					if (err.OK())
						{
						delete node;
						}
					}
				}
			else if (etype == kBookEditing)
				{
				JPoint cell;
				ok = GetEditedCell(&cell);
				assert(ok);
				JTreeNode* jnode = GetNamedTreeList()->GetNode(cell.y);
				GAddressBookTreeNode* node = dynamic_cast(GAddressBookTreeNode*, jnode);
				JError err = JRemoveFile(node->GetFullPathAndName());
				if (err.OK())
					{
					delete node;
					}
				}
			}
		}
	else if (index == kEditToolBarCmd)
		{
		JXDirector* dir = GetWindow()->GetDirector();
		GAddressBookTreeDir* aDir = 
			dynamic_cast(GAddressBookTreeDir*, dir);
		assert(aDir != NULL);
		aDir->EditToolBar();
		}
	else if (index == kCloseCmd)
		{
		GetWindow()->GetDirector()->Deactivate();
		}
}

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

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

void
GAddressBookTreeWidget::HandleMouseDown
	(
	const JPoint&			pt,
	const JXMouseButton 	button,
	const JSize 			clickCount,
	const JXButtonStates& 	buttonStates,
	const JXKeyModifiers& 	modifiers
	)
{
	ClearIncrementalSearchBuffer();
			
	itsDownInCell 		= kFalse;
	itsWaitingToEdit 	= kFalse;
	itsDownPt 			= pt;
	JTableSelection& s  = GetTableSelection();
	JPoint cell;
	if (GetCell(pt, &cell))
		{
		JTreeNode* jnode = GetTreeList()->GetNode(cell.y);
		if ((cell.x == 3) && (!modifiers.shift()) &&
			(!modifiers.meta()) && (!modifiers.control()) &&
			(button == kJXLeftButton) && (jnode->GetDepth() == 3))
			{
			itsWaitingToEdit 	= kTrue;
			}
		}

	JXNamedTreeListWidget::NodePart part;
	if (GetNode(pt, &cell, &part))
		{
		JIndex index			= cell.y;
		const JPoint newBoat 	= JPoint(2, index);
		const JPoint newAnchor 	= JPoint(2, index);
		JTreeNode* jnode 		= GetTreeList()->GetNode(index);
		if ((part > JXNamedTreeListWidget::kInImage) && (!modifiers.shift()) &&
			(!modifiers.meta()) && (!modifiers.control()) &&
			(button == kJXLeftButton) && (jnode->GetDepth() < 3))
			{
			itsWaitingToEdit 	= kTrue;
			}

		if ((part == JXNamedTreeListWidget::kInImage) && (!modifiers.shift()) &&
			(!modifiers.meta()) && (!modifiers.control()) &&
			(button == kJXLeftButton))
			{
			itsDownInCell = kTrue;
			if (clickCount == 2)
				{
				DoubleClickIcon(index, modifiers);
				}
			}
			
		if ((cell.x > 1) && (!itsWaitingToEdit))
			{
			if ((button == kJXLeftButton && modifiers.shift()) ||
				(button == kJXRightButton))
				{
				if (s.OKToExtendSelection())
					{
					s.ExtendSelection(newBoat);
					TableRefresh();
//					(GetWindow())->Update();
					}
				}
			else if (button == kJXLeftButton)
				{
				if (modifiers.control())
					{
					if (s.IsSelected(index, 2))
						{
						s.SelectCell(index, 2, kFalse);
						s.ClearBoat();
						s.ClearAnchor();
						TableRefreshRow(index);
//						(GetWindow())->Update();
						}
					else
						{
						s.SelectCell(index, 2, kTrue);
						s.SetBoat(newBoat);
						s.SetAnchor(newAnchor);
						TableRefreshRow(index);
//						(GetWindow())->Update();
						}
					}
				else if (!s.IsSelected(index, 2))
					{
					s.ClearSelection();
					s.SelectCell(index, 2);
					s.SetBoat(newBoat);
					s.SetAnchor(newAnchor);
					TableRefresh();
//					(GetWindow())->Update();
					}
				}
			}
		else
			{
			s.ClearSelection();
			}
		}
	else
		{
		itsDownInCell = kFalse;
		s.ClearSelection();
		}
	JXNamedTreeListWidget::HandleMouseDown(pt, button, 
		clickCount, buttonStates, modifiers);
}

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

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

void
GAddressBookTreeWidget::HandleMouseDrag
	(
	const JPoint&			pt,
	const JXButtonStates&	buttonStates,
	const JXKeyModifiers&	modifiers
	)
{
	JPoint cell;
	JXNamedTreeListWidget::NodePart part;
	if (GetNode(pt, &cell, &part))
		{
		JPoint p = itsDownPt - pt;
		if ((JLAbs(p.x) > kDragBeginBuffer) || (JLAbs(p.y) > kDragBeginBuffer))
			{
			itsWaitingToEdit = kFalse;
			if (itsDownInCell && OkToDND())
				{
				BeginDND(pt, buttonStates, modifiers);
				}
			}
		}
	else
		{
		itsWaitingToEdit = kFalse;
		}
		
	JXNamedTreeListWidget::HandleMouseDrag(pt,buttonStates,modifiers);
}

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

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

void
GAddressBookTreeWidget::HandleMouseUp
	(
	const JPoint& pt, 
	const JXMouseButton button,
	const JXButtonStates& buttonStates,
	const JXKeyModifiers& modifiers
	)
{
	JPoint cell;
	if (GetCell(pt, &cell))
		{
		if ((cell.x == 1) && modifiers.meta())
			{
			JBoolean before = GetNamedTreeList()->IsOpen(cell.y);
			JXNamedTreeListWidget::HandleMouseUp(pt, button, buttonStates, modifiers);
			JBoolean after = GetNamedTreeList()->IsOpen(cell.y);
			if (before != after)
				{
				JTreeNode* jnode = GetNamedTreeList()->GetNode(cell.y);
				if (jnode->GetDepth() == 1)
					{
					JTreeNode* base = itsTree->GetRoot();
					JSize count = base->GetChildCount();
					for (JSize i = 1; i <= count; i++)
						{
						JIndex index;
						if (GetNamedTreeList()->FindNode(base->GetChild(i), &index))
							{
							if (after)
								{
								GetNamedTreeList()->Open(index);
								}
							else
								{
								GetNamedTreeList()->Close(index);
								}
							}
						}
					}
				else if (jnode->GetDepth() == 2)
					{
					JTreeNode* parent = jnode->GetParent();
					JSize count = parent->GetChildCount();
					for (JSize i = 1; i <= count; i++)
						{
						JTreeNode* child = parent->GetChild(i);
						JIndex findex;
						if (GetNamedTreeList()->FindNode(child, &findex))
							{
							if (after)
								{
								GetNamedTreeList()->Open(findex);
								}
							else
								{
								GetNamedTreeList()->Close(findex);
								}
							}
						}
					}
				TableRefresh();
				}
			}
		else if (itsWaitingToEdit)
			{
			JXNamedTreeListWidget::HandleMouseUp(pt, button, buttonStates, modifiers);
			JTreeNode* jnode = GetNamedTreeList()->GetNode(cell.y);
			GAddressBaseTreeNode* node = dynamic_cast(GAddressBaseTreeNode*, jnode);
			assert(node != NULL);
			if (node->OKToChange())
				{
				if ((node->GetDepth() < 3) || (cell.x == 3))
					{
					GetTableSelection().ClearSelection();
					BeginEditing(cell);
					TableScrollToCell(JPoint(1, cell.y));
					}
				}
			}
		else
			{
			JXNamedTreeListWidget::HandleMouseUp(pt, button, buttonStates, modifiers);
			}
		}
	else
		{
		JXNamedTreeListWidget::HandleMouseUp(pt, button, buttonStates, modifiers);
		}
}

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

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

void
GAddressBookTreeWidget::HandleDNDResponse
	(
	const JXContainer*  target,
	const JBoolean 		dropAccepted, 
	const Atom 			action
	)
{
//	DisplayCursor(itsDNDCursor);
	JXNamedTreeListWidget::HandleDNDResponse(target, dropAccepted, action);
}

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

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

void
GAddressBookTreeWidget::HandleDNDHere
	(
	const JPoint& 	pt,
	const JXWidget* source
	)
{
	ScrollForDrag(pt);
	JIndex oldIndex = itsCurrentDndHereIndex;
	if (oldIndex != 0)
		{
		TableRefreshRow(oldIndex);
		}
	else if (itsOutsideRoot)
		{
		TableRefreshRow(GetRowCount());
		}

	JPoint cell;
	JXNamedTreeListWidget::NodePart part;
	if (GetNode(pt, &cell, &part))
		{
		itsCurrentDndHereIndex = ClosestSelection(pt, cell.y, part);
		TableRefreshRow(itsCurrentDndHereIndex);
		}
	else
		{
		itsCurrentDndHereIndex = ClosestSelection(pt, 0, part);
		if (itsCurrentDndHereIndex == 0)
			{
			TableRefreshRow(GetRowCount());
			}
		}
}

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

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

void
GAddressBookTreeWidget::HandleDNDLeave()
{
	JIndex oldIndex 		= itsCurrentDndHereIndex;
	itsCurrentDndHereIndex 	= 0;
	if (oldIndex != 0)
		{
		TableRefreshRow(oldIndex);
		}
	else if (itsOutsideRoot)
		{
		TableRefreshRow(GetRowCount());
		}
	itsIsBookDnd 		= kFalse;
	itsOutsideRoot		= kFalse;
}

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

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

void
GAddressBookTreeWidget::HandleDNDDrop
	(
	const JPoint& 		pt,
	const JArray<Atom>& typeList,
	const Atom 			action,
	const Time			time,
	const JXWidget* 	source
	)
{
	ClearIncrementalSearchBuffer();
	JBoolean dropOnSelf = kTrue;

	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))
			{
			itsOutsideRoot	= kFalse;
			return;
			}
		}
	JPoint cell;
	JIndex oldIndex = itsCurrentDndHereIndex;
	itsCurrentDndHereIndex = 0;
	if (oldIndex != 0)
		{
		TableRefreshRow(oldIndex);
		}
	if (GetNamedTreeList()->GetElementCount() < oldIndex)
		{
		itsOutsideRoot	= kFalse;
		return;
		}
	if (source != this)
		{
		dropOnSelf = kFalse;
		}
	
	if (dropOnSelf)
		{
		JTableSelection& s = GetTableSelection();
		JTableSelectionIterator iter(&s);
		JTreeNode* base = itsTree->GetRoot();
		if (itsDragType == kDraggingBooks)
			{
			if (itsOutsideRoot)
				{
				JBoolean after = kFalse;
				JTreeNode* jnode;
				JPoint cell;
				if (oldIndex == 0)
					{
					JTreeNode* lastChild = base->GetChild(base->GetChildCount());
					JIndex findex;
					JBoolean ok = GetNamedTreeList()->FindNode(lastChild, &findex);
					assert(ok);
					if (s.IsSelected(findex, 2))
						{
						itsOutsideRoot	= kFalse;
						return;
						}
					jnode = GetNamedTreeList()->GetNode(GetNamedTreeList()->GetElementCount());
					after = kTrue;
					}
				else
					{
					assert(oldIndex <= GetNamedTreeList()->GetElementCount());
					jnode = GetNamedTreeList()->GetNode(oldIndex);
					}
				while (iter.Next(&cell))
					{
					JTreeNode* book = GetNamedTreeList()->GetNode(cell.y);
					JBoolean opened = GetNamedTreeList()->IsOpen(book);
					if (after)
						{
						base->InsertAfter(jnode, book);
						}
					else
						{
						base->InsertBefore(jnode, book);
						}
					if (opened)
						{
						JIndex findex;
						JBoolean ok = GetNamedTreeList()->FindNode(book, &findex);
						assert(ok);
						GetNamedTreeList()->Open(findex);
						}
					}
				}
			else
				{
				assert(oldIndex <= GetNamedTreeList()->GetElementCount());
				JTreeNode* jnode = GetNamedTreeList()->GetNode(oldIndex);
				GAddressBookTreeNode* nnode =
					dynamic_cast(GAddressBookTreeNode*, jnode);
				assert(nnode != NULL);				
				JBoolean move = kTrue;
				if (realAction == dndMgr->GetDNDActionCopyXAtom())
					{
					move = kFalse;
					}
				JPoint cell;
				while (iter.Next(&cell))
					{
					JTreeNode* jbook = GetNamedTreeList()->GetNode(cell.y);
					GAddressBookTreeNode* gbook =
						dynamic_cast(GAddressBookTreeNode*, jbook);
					assert(gbook != NULL);
					if (!gbook->OKToChange())
						{
						move = kFalse;
						}
					JSize count = jbook->GetChildCount();
					for (JSize i = 1; i <= count; i++)
						{
						JTreeNode* jchild = jbook->GetChild(i);
						JNamedTreeNode* nchild = 
							dynamic_cast(JNamedTreeNode*, jchild);
						assert(nchild != NULL);
						AddEntryToBook(nchild, nnode, move);
						}
					}
				}
			}
		else if (itsDragType == kDraggingEntries)
			{
			if (itsOutsideRoot)
				{
				JString book;
				if (NewBook(&book))
					{
					JIndex findex = GetNamedTreeList()->GetElementCount();
					JTreeNode* jbook = GetNamedTreeList()->GetNode(findex);
					GetNamedTreeList()->Open(findex);
					GAddressBookTreeNode* nbook =
						dynamic_cast(GAddressBookTreeNode*, jbook);
					assert(nbook != NULL);
					JBoolean move = kTrue;
					if (realAction == dndMgr->GetDNDActionCopyXAtom())
						{
						move = kFalse;
						}
					AddSelectedToBook(nbook, move);
					}
				}
			else
				{
				assert(oldIndex <= GetNamedTreeList()->GetElementCount());
				JTreeNode* jnode = GetNamedTreeList()->GetNode(oldIndex);
				JNamedTreeNode* nnode =
					dynamic_cast(JNamedTreeNode*, jnode);
				assert(nnode != NULL);				
				JSize depth = jnode->GetDepth();
				if (depth == 1)
					{
					JBoolean move = kTrue;
					if (realAction == dndMgr->GetDNDActionCopyXAtom())
						{
						move = kFalse;
						}
					AddSelectedToBook(nnode, move);
					}
				else if (depth == 2)
					{
					JPoint cell;
					while (iter.Next(&cell))
						{
						JTreeNode* jdrag = GetNamedTreeList()->GetNode(cell.y);
						if (jdrag != jnode)
							{
							JTreeNode* jchild = jdrag->GetChild(1);
							GAddressItemTreeNode* item =
								dynamic_cast(GAddressItemTreeNode*, jchild);
							assert(item != NULL);
							AddAddressToEntry(nnode, item->GetText());
							}
						}
					}
				}
			}
		else if (itsDragType == kDraggingEMails)
			{
			if (itsOutsideRoot)
				{
				JString book;
				if (NewBook(&book))
					{
					JIndex findex = GetNamedTreeList()->GetElementCount();
					JTreeNode* jbook = GetNamedTreeList()->GetNode(findex);
					GetNamedTreeList()->Open(findex);
					JNamedTreeNode* nbook =
						dynamic_cast(JNamedTreeNode*, jbook);
					assert(nbook != NULL);
					JBoolean move = kTrue;
					SelectionType type = GetSelectionType();
					if ((realAction == dndMgr->GetDNDActionCopyXAtom()) ||
						(type != kExtraItemsSelected))
						{
						move = kFalse;
						}
					AddSelectedToBook(nbook, kFalse);
					}
				}
			else
				{
				assert(oldIndex <= GetNamedTreeList()->GetElementCount());
				JTreeNode* jnode = GetNamedTreeList()->GetNode(oldIndex);
				JNamedTreeNode* nnode =
					dynamic_cast(JNamedTreeNode*, jnode);
				assert(nnode != NULL);				
				JSize depth = jnode->GetDepth();
				SelectionType type = GetSelectionType();
				JBoolean move = kTrue;
				if ((realAction == dndMgr->GetDNDActionCopyXAtom()) ||
					(type != kExtraItemsSelected))
					{
					move = kFalse;
					}
				if (depth == 1)
					{
					AddSelectedToBook(nnode, kFalse);
					}
				else if (depth == 2)
					{
					JPoint cell;
					while (iter.Next(&cell))
						{
						JTreeNode* jdrag = GetNamedTreeList()->GetNode(cell.y);
						JTreeNode* jparent = jdrag->GetParent();
						if (jparent != jnode)
							{
							GAddressItemTreeNode* ndrag = 
								dynamic_cast(GAddressItemTreeNode*, jdrag);
							assert(ndrag != NULL);
							AddAddressToEntry(nnode, ndrag->GetText());
//							if (move && ndrag->OKToChange())
//								{
//								delete ndrag;
//								}
							}
						}
					}
				}
			}
		}
	else
		{
		JXSelectionManager* selManager = GetSelectionManager();
		Atom textAtom = selManager->GetMimePlainTextXAtom();
		unsigned char* data = NULL;
		JSize dataLength;
		Atom returnType;
		JXSelectionManager::DeleteMethod delMethod;
		const Atom dndName = (GetDNDManager())->GetDNDSelectionName();
		if (selManager->GetSelectionData(dndName, time, GetWindow(), textAtom,
										 &returnType, &data, &dataLength, &delMethod))
			{
			JString textData(reinterpret_cast<char*>(data), dataLength);
			JPtrArray<JString> list;
			GParseNameList(textData, list);
			JSize count = list.GetElementCount();
			if (itsOutsideRoot)
				{
				JString book;
				if (NewBook(&book))
					{
					JIndex findex = GetNamedTreeList()->GetElementCount();
					JTreeNode* jbook = GetNamedTreeList()->GetNode(findex);
					GetNamedTreeList()->Open(findex);
					JNamedTreeNode* nbook =
						dynamic_cast(JNamedTreeNode*, jbook);
					assert(nbook != NULL);
					NewNickName(nbook, list);
					}
				}
			else
				{
				assert(oldIndex <= GetNamedTreeList()->GetElementCount());
				JTreeNode* jnode = GetNamedTreeList()->GetNode(oldIndex);
				JNamedTreeNode* nnode =
					dynamic_cast(JNamedTreeNode*, jnode);
				assert(nnode != NULL);				
				JSize depth = jnode->GetDepth();
				if (depth == 1)
					{
					NewNickName(nnode, list);
					}
				else if (depth == 2)
					{
					for (JSize i = count; i >= 1; i--)
						{
						AddAddressToEntry(nnode, *(list.NthElement(i)));
						}
					}
				}
			selManager->DeleteSelectionData(&data, delMethod);
			list.DeleteAll();
			if (realAction == dndMgr->GetDNDActionMoveXAtom())
				{
				selManager->SendDeleteSelectionRequest(dndName, time, GetWindow());
				}
			}
		}
	itsIsBookDnd 	= kFalse;
	itsOutsideRoot	= kFalse;
}

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

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

JBoolean
GAddressBookTreeWidget::WillAcceptDrop
	(
	const JArray<Atom>& typeList, 
	Atom*				action,
	const Time			time,
	const JXWidget*		source
	)
{
	Atom textAtom				= GetSelectionManager()->GetMimePlainTextXAtom();
	JBoolean found 				= kFalse;
	const JSize typeCount 		= typeList.GetElementCount();
	for (JSize i=1; i<=typeCount; i++)
		{
		Atom type 				= typeList.GetElement(i);
		if (type == textAtom)
			{
			found 			= kTrue;
			}
		else if (type == itsAddressBookXAtom && source == this)
			{
			found			= kTrue;
			}
		}
	return found;
}

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

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

JBoolean 
GAddressBookTreeWidget::ConvertSelection
	(
	const Atom 		name, 
	const Atom 		requestType,
	Atom* 			returnType,
	unsigned char**	data,
	JSize* 			dataLength, 
	JSize* 			bitsPerBlock
	)
{
/*	*bitsPerBlock = 8;
	ostrstream os;
	
	JTableSelection& selection = GetTableSelection();
	JTableSelectionIterator* iter = 
		new JTableSelectionIterator(&selection);

	JPoint cell;
	if (itsDraggingBranch)
		{
		JSize count = 0;
		while(iter->Next(&cell))
			{
			JTreeNode* jnode = GetNamedTreeList()->GetNode(cell.y);
			count += jnode->GetChildCount();
			}
		os << count;
		iter->MoveTo(kIteratorStartAtBeginning, cell);
		while(iter->Next(&cell))
			{
			JTreeNode* jnode = GetNamedTreeList()->GetNode(cell.y);
			JSize count = jnode->GetChildCount();
			for (JSize j = 1; j <= count; j++)
				{
				JTreeNode* child = jnode->GetChild(j);
				GSimpleFileTreeNode* node = dynamic_cast(GSimpleFileTreeNode*, child);
				assert(node != NULL);
				node->WriteFileName(os);
				}
			
			}
		}
	else
		{
		os << selection.GetSelectedCellCount();
		while(iter->Next(&cell))
			{
			JTreeNode* jnode = GetNamedTreeList()->GetNode(cell.y);
			GMailboxTreeNode* node = dynamic_cast(GMailboxTreeNode*, jnode);
			assert(node != NULL);
			node->WriteFileName(os);
			}
		}
		
	os << ends;
	delete iter;
	*dataLength = strlen(os.str());
	*data = new unsigned char[ *dataLength ];
	assert (*data != NULL);
	memcpy(*data, os.str(), *dataLength);
	return kTrue;
*/
	return kFalse;
}

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

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

JBoolean 
GAddressBookTreeWidget::GetImage
	(
	const JIndex 	index,
	const JXImage**	image
	)
	const
{
	const JTreeNode* jnode	= GetNamedTreeList()->GetNode(index);
	JSize depth 			= jnode->GetDepth();
	if (depth == 1)
		{
		if (index == itsCurrentDndHereIndex && !itsOutsideRoot)
			{
			*image = itsSelectedBookIcon;
			return kTrue;
			}
		*image = itsBookIcon;
		return kTrue;
		}
	if (depth == 2)
		{
		if (index == itsCurrentDndHereIndex)
			{
			*image = itsSelectedEntryIcon;
			return kTrue;
			}
		*image = itsEntryIcon;
		return kTrue;
		}
	if (depth == 3)
		{
		GAddressItemTreeNode* node = 
			dynamic_cast(GAddressItemTreeNode*, jnode);
		assert(node != NULL);
		if (node->GetType() == GAddressItemTreeNode::kFcc)
			{
			*image = itsMailboxIcon;
			return kTrue;
			}
		if (node->GetType() == GAddressItemTreeNode::kComment)
			{
			*image = itsCommentIcon;
			return kTrue;
			}
		if (node->GetType() == GAddressItemTreeNode::kEMail)
			{
			*image = itsEMailIcon;
			return kTrue;
			}
		if (node->GetType() == GAddressItemTreeNode::kName)
			{
			*image = itsNameIcon;
			return kTrue;
			}
		}
	return JXNamedTreeListWidget::GetImage(index, image);
}

/******************************************************************************
 ReadPrefs (public)

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

void
GAddressBookTreeWidget::ReadPrefs
	(
	istream& input
	)
{
	JCoordinate id;
	input >> id;
	assert(id <= kCurrentPrefsVersion);
	JSize count;
	input >> count;
	for (JSize i = 1; i <= count; i++)
		{
		JString book;
		input >> book;
		if (JFileExists(book))
			{
			GGetAddressBookMgr()->AddAddressBook(book, itsTree);
			}
		}
	input >> count;
	for (JSize i = 1; i <= count; i++)
		{
		JString name;
		input >> name;
		JBoolean found = kFalse;
		JSize listcount = GetNamedTreeList()->GetElementCount();
		JIndex index = 1;
		while (!found && (index <= listcount))
			{
			JTreeNode* jnode = GetNamedTreeList()->GetNode(index);
			GAddressBaseTreeNode* node = dynamic_cast(GAddressBaseTreeNode*, jnode);
			assert(node != NULL);
			if (node->GetFullPathAndName() == name)
				{
				found = kTrue;
				if (jnode->IsOpenable())
					{
					GetNamedTreeList()->Open(index);
					}
				}
			index ++;
			}
		}
}

/******************************************************************************
 WritePrefs (public)

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

void
GAddressBookTreeWidget::WritePrefs
	(
	ostream& output
	)
	const
{
	output << kCurrentPrefsVersion << ' ';
	JTreeNode* base = itsTree->GetRoot();
	JSize count = base->GetChildCount();
	output << count << ' ';
	for (JSize i = 1; i <= count; i++)
		{
		JTreeNode* child = base->GetChild(i);
		GAddressBaseTreeNode* node = 
			dynamic_cast(GAddressBaseTreeNode*, child);
		assert(node != NULL);
		output << node->GetFullPathAndName() << ' ';
		}
	count = GetNamedTreeList()->GetElementCount();
	JSize realcount = 0;
	for (JSize i = 1; i <= count; i++)
		{
		if (GetNamedTreeList()->IsOpen(i))
			{
			realcount++;
			}
		}
	output << realcount << ' ';
	for (JSize i = 1; i <= count; i++)
		{
		if (GetNamedTreeList()->IsOpen(i))
			{
			const JTreeNode* child = GetNamedTreeList()->GetNode(i);
			GAddressBaseTreeNode* node = 
			dynamic_cast(GAddressBaseTreeNode*, child);
			assert(node != NULL);
			output << node->GetFullPathAndName() << ' ';
			}
		}
	
}

/******************************************************************************
 Update (public)

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

void
GAddressBookTreeWidget::Update()
{
}

/******************************************************************************
 NewMailFromAddress (private)

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

void
GAddressBookTreeWidget::NewMailFromAddress
	(
	const JIndex index
	)
{
	JTreeNode* jnode = GetNamedTreeList()->GetNode(index);
	JTreeNode* child = jnode->GetChild(1);
	GAddressItemTreeNode* node =
		dynamic_cast(GAddressItemTreeNode*, child);
	assert(node != NULL);
	GMGetApplication()->NewMessage(node->GetText());
	ClearIncrementalSearchBuffer();
}

/******************************************************************************
ClosestSelection (private)

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

JIndex
GAddressBookTreeWidget::ClosestSelection
	(
	const JPoint& 							pt,
	const JIndex  							index,
	const JXNamedTreeListWidget::NodePart	part
	)
{
	JBoolean inNode		= kFalse;
	if ((part == JXNamedTreeListWidget::kInImage) ||
		(part == JXNamedTreeListWidget::kInText))
		{
		inNode 			= kTrue;
		}
	itsOutsideRoot		= kFalse;
	if (index == 0)
		{
		itsOutsideRoot	= kTrue;
		return 0;
		}
	JTreeNode* jnode = GetNamedTreeList()->GetNode(index);
	JSize depth = jnode->GetDepth();
	if (itsIsBookDnd)
		{
		JTreeNode* jBookNode;
		if (depth == 1)
			{
			GAddressBaseTreeNode* nnode = dynamic_cast(GAddressBaseTreeNode*, jnode);
			assert(nnode != NULL);
			if (inNode && nnode->OKToChange())
				{
				return index;
				}
			itsOutsideRoot	= kTrue;
			return index;
			}
		else if (depth == 2)
			{
			jBookNode = jnode->GetParent();
			}
		else if (depth == 3)
			{
			jBookNode = jnode->GetParent()->GetParent();
			}
		itsOutsideRoot	= kTrue;
		JIndex findex;
		JBoolean ok = GetNamedTreeList()->FindNode(jBookNode, &findex);
		assert(ok);
		return findex;
		}
	else
		{
		GAddressBaseTreeNode* nnode = dynamic_cast(GAddressBaseTreeNode*, jnode);
		assert(nnode != NULL);
		if (nnode->OKToChange())
			{
			if (depth == 3)
				{
				JTreeNode* jparent = jnode->GetParent();
				JIndex findex;
				JBoolean ok = GetNamedTreeList()->FindNode(jparent, &findex);
				assert(ok);
				return findex;
				}
			return index;
			}
		else
			{
			itsOutsideRoot	= kTrue;
			return 0;
			}
		}
	itsOutsideRoot	= kTrue;
	return 0;
}



/******************************************************************************
 OkToDND (private)

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

JBoolean
GAddressBookTreeWidget::OkToDND()
{
	itsIsBookDnd		= kFalse;
	SelectionType type = GetSelectionType();
	if ((type == kOneBookSelected) ||
		(type == kBooksSelected))
		{
		itsDragType		= kDraggingBooks;
		itsIsBookDnd	= kTrue;
		return kTrue;
		}
	else if ((type == kOneEntrySelected) ||
			 (type == kEntriesSelected))
		{
		itsDragType		= kDraggingEntries;
		return kTrue;
		}
	else if ((type == kExtraItemsSelected) ||
			 (type == kItemsSelected))
		{
		JTableSelection& s = GetTableSelection();
		JTableSelectionIterator iter(&s);
		JPoint cell;
		while (iter.Next(&cell))
			{
			JTreeNode* jnode = GetTreeList()->GetNode(cell.y);
			JSize depth = jnode->GetDepth();
			assert(depth == 3);
			GAddressItemTreeNode* node =
				dynamic_cast(GAddressItemTreeNode*, jnode);
			assert(node != NULL);
			if (node->GetType() != GAddressItemTreeNode::kEMail)
				{
				return kFalse;
				}
			}
		itsDragType		= kDraggingEMails;
		return kTrue;
		}
	return kFalse;
}

/******************************************************************************
 GetSelectionType (private)

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

GAddressBookTreeWidget::SelectionType
GAddressBookTreeWidget::GetSelectionType()
{

	JTableSelection& s = GetTableSelection();
	JTableSelectionIterator iter(&s);
	JPoint cell;
	SelectionType type = kNoneSelected;	
	while (iter.Next(&cell))
		{
		JTreeNode* jnode = GetTreeList()->GetNode(cell.y);
		JSize depth = jnode->GetDepth();
		if (type == kNoneSelected)
			{
			if (depth == 1)
				{
				type = kOneBookSelected;
				}
			else if (depth == 2)
				{
				type = kOneEntrySelected;
				}
			else
				{
				JTreeNode* jparent = jnode->GetParent();
				assert(jparent != NULL);
				GAddressItemTreeNode* node =
					dynamic_cast(GAddressItemTreeNode*, jnode);
				assert(node != NULL);
				JBoolean okToChange = node->OKToChange();
				GAddressItemTreeNode::Type itype = node->GetType();
				if (itype == GAddressItemTreeNode::kName)
					{
					type = kItemsSelected;
					}
				else if ((itype == GAddressItemTreeNode::kFcc) ||
					(itype == GAddressItemTreeNode::kComment))
					{
					if (okToChange)
						{
						type = kExtraItemsSelected;
						}
					else
						{
						type = kItemsSelected;
						}
					}
				else
					{
					JSize count = jparent->GetChildCount();
					JSize childCount = 0;
					for (JSize i = 1; i <= count; i++)
						{
						JTreeNode* jchild = jparent->GetChild(i);
						GAddressItemTreeNode* child =
							dynamic_cast(GAddressItemTreeNode*, jchild);
						if (child->GetType() == GAddressItemTreeNode::kEMail)
							{
							childCount++;
							}
						}
					if ((childCount == 1) || !okToChange)
						{
						type = kItemsSelected;
						}
					else
						{
						type = kExtraItemsSelected;
						}
					}
				}
			}
		else if ((type == kOneBookSelected) ||
				 (type == kBooksSelected))
			{
			if (depth == 1)
				{
				type = kBooksSelected;
				}
			else
				{
				type = kRandomSelection;
				break;
				}
			}
		else if ((type == kOneEntrySelected) ||
				 (type == kEntriesSelected))
			{
			if (depth == 2)
				{
				type = kEntriesSelected;
				}
			else
				{
				type = kRandomSelection;
				break;
				}
			}
		else if (type == kExtraItemsSelected)
			{
			if (depth != 3)
				{
				type = kRandomSelection;
				break;
				}
			JTreeNode* jparent = jnode->GetParent();
			assert(jparent != NULL);
			GAddressItemTreeNode* node =
				dynamic_cast(GAddressItemTreeNode*, jnode);
			assert(node != NULL);
			JBoolean okToChange = node->OKToChange();
			GAddressItemTreeNode::Type itype = node->GetType();
			if (itype == GAddressItemTreeNode::kName)
				{
				type = kItemsSelected;
				}
			JSize count = jparent->GetChildCount();
			JBoolean allSel = kTrue;
			for (JSize i = 1; i <= count; i++)
				{
				JTreeNode* jchild = jparent->GetChild(i);
				GAddressItemTreeNode* child =
					dynamic_cast(GAddressItemTreeNode*, jchild);
				if (child->GetType() == GAddressItemTreeNode::kEMail)
					{
					JIndex findex;
					JBoolean ok = GetNamedTreeList()->FindNode(child, &findex);
					assert(ok);
					if (!s.IsSelected(findex, 2))
						{
						allSel = kFalse;
						}
					}
				}
			if ((allSel) || !okToChange)
				{
				type = kItemsSelected;
				}
			}
		}
	return type;
}

/******************************************************************************
 GetEditType (private)

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

GAddressBookTreeWidget::EditType
GAddressBookTreeWidget::GetEditType()
{
	JPoint cell;
	if (GetEditedCell(&cell))
		{
		JTreeNode* jnode = GetNamedTreeList()->GetNode(cell.y);
		JSize depth = jnode->GetDepth();	
		if (depth == 1)
			{
			return kBookEditing;
			}
		else if (depth == 2)
			{
			return kEntryEditing;
			}
		else
			{
			JTreeNode* jparent = jnode->GetParent();
			assert(jparent != NULL);
			GAddressItemTreeNode* node =
				dynamic_cast(GAddressItemTreeNode*, jnode);
			assert(node != NULL);
			JBoolean okToChange = node->OKToChange();
			GAddressItemTreeNode::Type itype = node->GetType();
			if (itype == GAddressItemTreeNode::kName)
				{
				return kItemEditing;
				}
			else if ((itype == GAddressItemTreeNode::kFcc) ||
				(itype == GAddressItemTreeNode::kComment))
				{
				if (okToChange)
					{
					return kExtraItemEditing;
					}
				else
					{
					return kItemEditing;
					}
				}
			else
				{
				JSize count = jparent->GetChildCount();
				JSize childCount = 0;
				for (JSize i = 1; i <= count; i++)
					{
					JTreeNode* jchild = jparent->GetChild(i);
					GAddressItemTreeNode* child =
						dynamic_cast(GAddressItemTreeNode*, jchild);
					if (child->GetType() == GAddressItemTreeNode::kEMail)
						{
						childCount++;
						}
					}
				if ((childCount == 1) || !okToChange)
					{
					return kItemEditing;
					}
				else
					{
					return kExtraItemEditing;
					}
				}
			}
		}
	return kNoneEditing;
}

/******************************************************************************
 DeleteSelected (private)

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

void
GAddressBookTreeWidget::DeleteSelected()
{
	JTableSelection& s = GetTableSelection();
	JTableSelectionIterator iter(&s);
	JPoint cell;
	JTreeNode* base = itsTree->GetRoot();
	while (iter.Next(&cell))
		{
		JTreeNode* node = GetNamedTreeList()->GetNode(cell.y);
		base->Remove(node);
		delete node;
		}
	ClearIncrementalSearchBuffer();
}

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

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

JSize
GAddressBookTreeWidget::GetMinCellWidth
	(
	const JPoint& cell
	)
	const
{
	if (cell.x == 3)
		{
		const JTreeNode* jnode = GetTreeList()->GetNode(cell.y);
		if (jnode->GetDepth() == 3)
			{
			GAddressItemTreeNode* node = 
				dynamic_cast(GAddressItemTreeNode*, jnode);
			assert(node != NULL);
			JString str = node->GetText();
			JSize size;
			JString fontname = GetFont(&size);
			JSize w = GetFontManager()->GetStringWidth(fontname, 
				size, JFontStyle(), str);
			return w;
			}
		else
			{
			return 0;
			}
		}
	return JXNamedTreeListWidget::GetMinCellWidth(cell);
}

/******************************************************************************
 AdjustToTree

 ******************************************************************************

void
GAddressBookTreeWidget::AdjustToTree()
{
	JSize maxSize = 5;
	const JFontManager* theFontManager = GetFontManager();
	for (JSize i = 1; i<= GetTreeList()->GetElementCount(); i++)
		{
		JTreeNode* jnode = GetTreeList()->GetNode(i);
		if (jnode->GetDepth() == 3)
			{
			GAddressItemTreeNode* node = 
				dynamic_cast(GAddressItemTreeNode*, jnode);
			assert(node != NULL);
			JString str = node->GetText();
			JSize size;
			JString fontname = GetFont(&size);
			JSize w = theFontManager->GetStringWidth(fontname, 
				size, JFontStyle(), str);
			if (w > maxSize)
				{
				maxSize = w + 5;
				SetColWidth(3, maxSize);
				}
			}
		}
	JXNamedTreeListWidget::AdjustToTree();
	JCoordinate currentWidth = GetColWidth(1) + GetColWidth(2) + GetColWidth(3);
	if (currentWidth < GetApertureWidth())
		{
		JCoordinate delta = GetApertureWidth() - currentWidth;
		SetColWidth(3, GetColWidth(3) + delta);
		}
}*/

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

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

JXInputField*
GAddressBookTreeWidget::CreateXInputField
	(
	const JPoint&    		cell,
	const JCoordinate       x,
	const JCoordinate       y,
	const JCoordinate       w,
	const JCoordinate       h
	)
{
	if (cell.x == (JCoordinate)GetColCount())
		{
		assert( itsStringInputField == NULL );
		itsStringInputField = 
			new JXInputField(this, kFixedLeft, kFixedTop, x - 3,y + 1, w,h);
		assert( itsStringInputField != NULL );
		JSize size;
		itsStringInputField->SetFontName(GetFont(&size));
		itsStringInputField->SetFontSize(size);
		itsStringInputField->SetFontStyle(JFontStyle());

		JTreeNode* jnode = GetTreeList()->GetNode(cell.y);
		assert(jnode->GetDepth() == 3);
		GAddressItemTreeNode* node = 
			dynamic_cast(GAddressItemTreeNode*, jnode);
		assert(node != NULL);
		itsStringInputField->SetText(node->GetText());
	//	if (itsEMEditor != NULL)
	//		{
	//		itsStringInputField->ShareEditMenu(itsEMEditor, kFalse, kFalse);
	//		}
		return itsStringInputField;
		}
	return JXNamedTreeListWidget::CreateXInputField(cell, x, y, w, h);
}

/******************************************************************************
 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
GAddressBookTreeWidget::ExtractInputData
	(
	const JPoint& cell
	)
{
	if (cell.x == (JCoordinate)GetColCount())
		{
		assert( itsStringInputField != NULL );

		const JString& newName = itsStringInputField->GetText();
		JTreeNode* jnode = GetTreeList()->GetNode(cell.y);
		assert(jnode->GetDepth() == 3);
		GAddressItemTreeNode* node = 
			dynamic_cast(GAddressItemTreeNode*, jnode);
		assert(node != NULL);
		if (node->GetText() == newName)
			{
			return kTrue;
			}
		if (node->GetType() == GAddressItemTreeNode::kName)
			{
			if (GGetAddressBookMgr()->ChangeNickName(node->GetText(), newName))
				{
				node->SetText(newName);
				return kTrue;
				}
			else
				{
				return kFalse;
				}
			}
		else
			{
			node->SetText(newName);
			}

		JTreeNode* jparent = node->GetParent();
		GAddressEntryTreeNode* parent = 
			dynamic_cast(GAddressEntryTreeNode*, jparent);
		assert(parent != NULL);
		AdjustToTree();
		TableRefreshRow(cell.y);
		GGetAddressBookMgr()->AdjustProperties(parent);
		return kTrue;
		}
	JIndex index 		= cell.y;
	JXInputField* input;
	JBoolean ok			= GetXInputField(&input);
	assert(ok);
	JString newName		= input->GetText();
	JTreeNode* jnode 	= GetNamedTreeList()->GetNode(index);
	JSize depth 		= jnode->GetDepth();	
	if (depth == 1)
		{
		GAddressBookTreeNode* node = 
			dynamic_cast(GAddressBookTreeNode*, jnode);
		assert(node != NULL);
		JString fullname = node->GetFullPathAndName();
		if (fullname == "/")
			{
			return kFalse;
			}
		if (fullname.EndsWith("/"))
			{
			fullname.RemoveSubstring(fullname.GetLength(), fullname.GetLength());
			}
		JString path;
		JString name;
		JSplitPathAndName(fullname, &path, &name);
		JAppendDirSeparator(&path);
		JString newFullName = path + newName;
		JError err = JRenameFile(fullname, newFullName);
		if (err.OK())
			{
			node->SetNameAndUpdate(newName);
			return JXNamedTreeListWidget::ExtractInputData(cell);
			}
		else
			{
			return kFalse;
			}
		}
	else if (depth == 2)
		{
		GAddressEntryTreeNode* node = 
			dynamic_cast(GAddressEntryTreeNode*, jnode);
		assert(node != NULL);
		if (node->GetName() == newName)
			{
			return kTrue;
			}
		JBoolean ok = JXNamedTreeListWidget::ExtractInputData(cell);
		assert(ok);
		JBoolean nodeOpen = GetNamedTreeList()->IsOpen(node);
		JTreeNode* jparent = node->GetParent();
		GAddressBookTreeNode* parent = 
			dynamic_cast(GAddressBookTreeNode*, jparent);
		parent->Remove(node);
		parent->InsertSorted(node);
		JIndex findex;
		ok = GetNamedTreeList()->FindNode(node, &findex);
		assert(ok);
		if (nodeOpen)
			{
			GetNamedTreeList()->Open(findex);
			}
		TableScrollToCell(JPoint(1, findex));
		JTableSelection& s = GetTableSelection();
		s.SelectCell(findex, 2);
		return kTrue;
		}
	ok = JXNamedTreeListWidget::ExtractInputData(cell);
	return ok;
}

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

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

void
GAddressBookTreeWidget::DeleteXInputField()
{
	if (itsStringInputField != NULL)
		{
		delete itsStringInputField;
		itsStringInputField = NULL;
		}
	else
		{
		JXNamedTreeListWidget::DeleteXInputField();
		}
}


/******************************************************************************
 GetFirstWritableBook (private)

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

JBoolean
GAddressBookTreeWidget::GetFirstWritableBook
	(
	GAddressBookTreeNode** book
	)
{
	JTreeNode* base = itsTree->GetRoot();
	const JSize count = base->GetChildCount();
	for (JSize i = 1; i <= count; i++)
		{
		JTreeNode* jnode = base->GetChild(i);
		*book = dynamic_cast(GAddressBookTreeNode*, jnode);
		assert(*book != NULL);
		if ((*book)->OKToChange())
			{
			return kTrue;
			}
		}
	return kFalse;
}

/******************************************************************************
 DoubleClickIcon (virtual public)

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

void
GAddressBookTreeWidget::DoubleClickIcon
	(
	const JIndex 			index, 
	const JXKeyModifiers& 	modifiers
	)
{
	if (IsEditing())
		{
		return;
		}
	SelectionType type = GetSelectionType();
	if ((type == kOneEntrySelected) ||
		(type == kEntriesSelected))
		{
		NewMailFromAddress(index);
		}

	JTreeNode* jnode = GetNamedTreeList()->GetNode(index);
	if (jnode->GetDepth() == 3)
		{
		GAddressItemTreeNode* item = 
			dynamic_cast(GAddressItemTreeNode*, jnode);
		assert(item != NULL);
		if (item->GetType() == GAddressItemTreeNode::kEMail)
			{
			GMGetApplication()->NewMessage(item->GetText());
			}
		}
	ClearIncrementalSearchBuffer();
}

/******************************************************************************
 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
GAddressBookTreeWidget::GetDNDAction
	(
	const JXContainer*		target,
	const JXButtonStates&	buttonStates,
	const JXKeyModifiers&	modifiers
	)
{
	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
GAddressBookTreeWidget::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);
}

/******************************************************************************
 NewNickName (private)

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

void
GAddressBookTreeWidget::NewNickName
	(
	JNamedTreeNode*			  parent, 
	const JPtrArray<JString>& addresses
	)
{
	JString add;
	const JSize count = addresses.GetElementCount();
	if (count == 0)
		{
		add = "nobody";
		}
	if (count > 1)
		{
		add = "(";
		}
	for (JSize i = 1; i <= count; i++)
		{
		if (i > 1)
			{
			add += ",";
			}
		add += *(addresses.NthElement(i));
		}
	
	JString name;
	if (GGetAddressBookMgr()->NewNickName(add, &name))
		{
		GAddressEntryTreeNode* aEntry = 
			new GAddressEntryTreeNode(parent, name);
		assert(aEntry != NULL);
		GAddressItemTreeNode* item =
			new GAddressItemTreeNode(GAddressItemTreeNode::kName, 
									 aEntry, name, kFalse);
		assert(item != NULL);
		if (count == 0)
			{
			item =
				new GAddressItemTreeNode(GAddressItemTreeNode::kEMail, 
										 aEntry, add, kFalse);
			assert(item != NULL);
			}
		for (JSize i = 1; i <= count; i++)
			{
			item =
				new GAddressItemTreeNode(GAddressItemTreeNode::kEMail, 
										 aEntry, *(addresses.NthElement(i)), kFalse);
			assert(item != NULL);
			}
		parent->InsertSorted(aEntry);
		JIndex findex;
		JBoolean ok = GetNamedTreeList()->FindNode(parent, &findex);
		assert(ok);
		GetNamedTreeList()->Open(findex);
		ok = GetNamedTreeList()->FindNode(aEntry, &findex);
		assert(ok);
		GetNamedTreeList()->Open(findex);
		TableScrollToCell(JPoint(1, findex));
		assert(aEntry->OKToChange());
		GetTableSelection().ClearSelection();
		BeginEditing(JPoint(2, findex));
		TableScrollToCell(JPoint(2, findex));
		}
	ClearIncrementalSearchBuffer();
}

/******************************************************************************
 NewBook (private)

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

JBoolean
GAddressBookTreeWidget::NewBook
	(
	JString* book
	)
{
	if (JXGetChooseSaveFile()->SaveFile("Name of new address book:", "", "",  book))
		{
		ofstream os(*book);
		if (!os.good())
			{
			JGetUserNotification()->ReportError("Unable to create address book.");
			return kFalse;
			}
		else
			{
			os.close();
			GGetAddressBookMgr()->AddAddressBook(*book, itsTree);
			}
		return kTrue;
		}
	return kFalse;
}

/******************************************************************************
 AddSelectedToBook (private)

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

void
GAddressBookTreeWidget::AddSelectedToBook
	(
	JNamedTreeNode* book, 
	const JBoolean  dndmove
	)
{
	JTableSelection& s = GetTableSelection();
	JTableSelectionIterator iter(&s);
	JBoolean move = dndmove;
	JPoint cell;
	if (itsDragType == kDraggingEMails)
		{
		JPtrArray<JString> addresses;
		while (iter.Next(&cell))
			{
			JTreeNode* jinode = GetNamedTreeList()->GetNode(cell.y);
			GAddressItemTreeNode* item =
				dynamic_cast(GAddressItemTreeNode*, jinode);
			JString* name = new JString(item->GetText());
			addresses.Append(name);
			if (dndmove && item->OKToChange())
				{
				delete item;
				}
			}
		NewNickName(book, addresses);
		addresses.DeleteAll();
		}
	else if (itsDragType == kDraggingEntries);
		{
		while (iter.Next(&cell))
			{
			move = dndmove;
			JTreeNode* jdrag = GetNamedTreeList()->GetNode(cell.y);
			JTreeNode* jparent = jdrag->GetParent();
			JNamedTreeNode* nparent = 
				dynamic_cast(JNamedTreeNode*, jparent);
			assert(nparent != NULL);
			if (nparent != book)
				{
				GAddressBaseTreeNode* ndrag = 
					dynamic_cast(GAddressBaseTreeNode*, jdrag);
				assert(ndrag != NULL);
				if (!ndrag->OKToChange())
					{
					move = kFalse;
					}
				AddEntryToBook(ndrag, book, dndmove);
				}
			}
		}
	ClearIncrementalSearchBuffer();
}

/******************************************************************************
 AddEntryToBook (private)

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

void
GAddressBookTreeWidget::AddEntryToBook
	(
	JNamedTreeNode* entry, 
	JNamedTreeNode* book, 
	const JBoolean  dndmove
	)
{
	JNamedTreeNode* jdrag = entry;
	JBoolean opened = GetNamedTreeList()->IsOpen(entry);
	if (dndmove)
		{
		book->InsertSorted(entry);
		}
	else // copy entry
		{
		GAddressEntryTreeNode* aEntry = 
			new GAddressEntryTreeNode(book, entry->GetName());
		assert(aEntry != NULL);
		JSize count = entry->GetChildCount();
		for (JSize i = 1; i <= count; i ++)
			{
			JTreeNode* jchild = entry->GetChild(i);
			GAddressItemTreeNode* ichild =
				dynamic_cast(GAddressItemTreeNode*, jchild);
			assert(ichild != NULL);
			GAddressItemTreeNode* newchild = 
				new GAddressItemTreeNode(ichild->GetType(), 
										 aEntry, ichild->GetText(), kFalse);
			assert(newchild != NULL);
			}
		book->InsertSorted(aEntry);
		jdrag = aEntry;
		}
	if (opened)
		{
		JIndex findex;
		if (GetNamedTreeList()->FindNode(jdrag, &findex))
			{
			GetNamedTreeList()->Open(findex);
			}
		}
	ClearIncrementalSearchBuffer();
}

/******************************************************************************
 AddAddressToEntry (private)

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

void
GAddressBookTreeWidget::AddAddressToEntry
	(
	JNamedTreeNode* node, 
	const JString& 	address
	)
{
	GAddressEntryTreeNode* entry =
		dynamic_cast(GAddressEntryTreeNode*, node);
	assert(entry != NULL);
	JBoolean before = kFalse;
	JSize endcount = 0;
	if (entry->HasComment())
		{
		endcount++;
		before = kTrue;
		}
	if (entry->HasFcc())
		{
		endcount++;
		before = kTrue;
		}
	
	GAddressItemTreeNode* newchild = 
		new GAddressItemTreeNode(GAddressItemTreeNode::kEMail, 
								 NULL, address, kFalse);
	assert(newchild != NULL);
	if (before)
		{
		JIndex findex = node->GetChildCount() - endcount + 1;
		JTreeNode* jnode = node->GetChild(findex);
		node->InsertBefore(jnode, newchild);
		}
	else
		{
		node->Append(newchild);
		}
	ClearIncrementalSearchBuffer();
}

/******************************************************************************
 Save (public)

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

void
GAddressBookTreeWidget::Save()
{
	JTreeNode* base = itsTree->GetRoot();
	JSize count = base->GetChildCount();
	for (JSize i = 1; i <= count; i++)
		{
		JTreeNode* jbook = base->GetChild(i);
		GAddressBookTreeNode* book = 
			dynamic_cast(GAddressBookTreeNode*, jbook);
		assert(book != NULL);
		book->Save();
		}
}

/******************************************************************************
 IsEditable (virtual)

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

JBoolean
GAddressBookTreeWidget::IsEditable
	(
	const JPoint& cell
	)
	const
{
	const JTreeNode* jnode = GetNamedTreeList()->GetNode(cell.y);
	GAddressBaseTreeNode* nnode =
		dynamic_cast(GAddressBaseTreeNode*, jnode);
	assert(nnode != NULL);
	JSize depth = jnode->GetDepth();
	if (cell.x == 2)
		{
		if ((depth <= 2) && (nnode->OKToChange()))
			{
			return kTrue;
			}
		}
	else if ((cell.x == 3) && (depth == 3) && (nnode->OKToChange()))
		{
		return kTrue;
		}
	return kFalse;
}

/******************************************************************************
 AddDefaultButtcons (public)

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

void
GAddressBookTreeWidget::AddDefaultButtcons
	(
	JXToolBar* toolbar
	)
{
	toolbar->AppendButton(itsAddressBookMenu, kNewNameCmd);
	toolbar->AppendButton(itsAddressBookMenu, kNewAddCmd);
	toolbar->AppendButton(itsAddressBookMenu, kNewBookCmd);
	toolbar->NewGroup();
}
