//	Zinc Application Framework - I_TREE1.CPP
//	COPYRIGHT (C) 1990-1999.  All Rights Reserved.
//	Zinc Software Incorporated.  Pleasant Grove, Utah  USA

#include <z_scrll2.hpp>
#include <z_string.hpp>
#include <z_tree.hpp>
#include <z_utils.hpp>

#if defined(ZAF_TEXT_MODE)
const int ARROW_SIZE	= 1;
#else
const int ARROW_SIZE	= 13;
#endif

// ----- ZafTreeItem ------------------------------------------------------

ZafWindowObject *ZafTreeItem::Add(ZafWindowObject *object, ZafWindowObject *position)
{
	ZafTreeItem *item = DynamicPtrCast(object, ZafTreeItem);

	if (item)
	{
		// current is not used (only TreeList's current is used) but it
		// must be set so that ZafWindow::Add() doesn't assign focus.
		current = this;

		bool saveAutomaticUpdate = automaticUpdate;
		automaticUpdate = false;
		ZafWindow::Add(object, position);
		automaticUpdate = saveAutomaticUpdate;

		if (screenID)
		{
			ZafTreeList *treeList = TreeList();
			treeList->ResizeVisibleItems(ZAF_NULLP(ZafTreeItem));
			treeList->Event(S_VSCROLL_COMPUTE);
			if (treeList->ItemInView(item) || item == treeList->ViewLast())
				treeList->Event(ZafEventStruct(S_REDISPLAY));
		}
		current = 0;
	}

	return (object);
}

ZafRegionStruct ZafTreeItem::ConvertToDrawRegion(const ZafWindowObject *object,
	const ZafRegionStruct *zafRegion) const
{
	return (ZafWindowObject::ConvertToDrawRegion(object, zafRegion));
}

ZafPositionStruct ZafTreeItem::ConvertToOSPosition(const ZafWindowObject *,
	const ZafPositionStruct *zafPosition) const
{
	ZafPositionStruct osPosition;

	if (zafPosition)
	{
		osPosition.column = zafPosition->column + zafRegion.left;
		osPosition.line = zafPosition->line + zafRegion.top;
		osPosition.coordinateType = zafPosition->coordinateType;
	}
	else
	{
		osPosition.column = osPosition.line = 0;
#if defined(ZAF_GRAPHICS_MODE)
		osPosition.coordinateType = ZAF_PIXEL;
#elif defined(ZAF_TEXT_MODE)
		osPosition.coordinateType = ZAF_CELL;
#endif
	}

	ZafWindowObject *tree = TreeList();
	osPosition = tree->ConvertToOSPosition(this, &osPosition);

	return (osPosition);
}

ZafPositionStruct ZafTreeItem::ConvertToZafPosition(const ZafWindowObject *,
	const ZafPositionStruct *osPosition) const
{
	ZafPositionStruct zafPosition;

	if (osPosition)
	{
		zafPosition.column = osPosition->column - zafRegion.left;
		zafPosition.line = osPosition->line - zafRegion.top;
		zafPosition.coordinateType = osPosition->coordinateType;
	}
	else
	{
		zafPosition.column = zafPosition.line = 0;
#if defined(ZAF_GRAPHICS_MODE)
		zafPosition.coordinateType = ZAF_PIXEL;
#elif defined(ZAF_TEXT_MODE)
		zafPosition.coordinateType = ZAF_CELL;
#endif
	}

	ZafWindowObject *tree = TreeList();
	zafPosition = tree->ConvertToZafPosition(this, &zafPosition);

	return (zafPosition);
}

ZafEventType ZafTreeItem::DragDropEvent(const ZafEventStruct &event)
{
	ZafEventType ccode = event.type;

	if (this != windowManager->dragObject)
	{
		ZafTreeList *treeList = TreeList();
		switch (ccode)
		{
		case S_DROP_COPY:
		case S_DROP_MOVE:
		case S_DROP_DEFAULT:
//			if (Disabled() || !AcceptDrop() || windowManager->dragObject == this)
			if (Disabled() || (treeList && !treeList->AcceptDrop()))
				return (S_ERROR);
			else
			{
				ZafTreeItem *treeItem = DynamicPtrCast(windowManager->dragObject, ZafTreeItem);
				if (treeItem)
				{
					if (ccode == S_DROP_COPY)
					{
						if (expandable)
							Add(treeItem->Duplicate());
						else
							DynamicPtrCast(parent, ZafWindow)->Add(treeItem->Duplicate());
					}
					else
					{
						ZafWindow *dragParent = DynamicPtrCast(treeItem->parent, ZafWindow);
						dragParent->Subtract(treeItem);
						if (expandable)
							Add(treeItem);
						else
							DynamicPtrCast(parent, ZafWindow)->Add(treeItem);
					}
				}
			}
			break;

		default:
			ccode = treeList ? treeList->DragDropEvent(event) : S_ERROR;
			break;
		}
	}

	return (ccode);
}

#if defined(ZAF_DEFAULT_DRAW)
ZafEventType ZafTreeItem::Draw(const ZafEventStruct &, ZafEventType ccode)
{
	// Begin the drawing operation.
	ZafRegionStruct drawRegion = BeginDraw();

	ZafPaletteState state = PaletteState();

	if (ccode != S_REDISPLAY_DATA)
	{
		// Draw the expansion button.
		ZafRegionStruct region = expandRegion;
		region.left += drawRegion.left;
		region.right += drawRegion.left;
		region--;
		if (expandable)
		{
			if (Expanded())
			{
				region.left += (region.Width() - expandBitmap->Width()) / 2;
				region.top += (region.Height() - expandBitmap->Height()) / 2;
				display->Bitmap(region.left, region.top, *expandBitmap);
			}
			else
			{
				region.left += (region.Width() - compressBitmap->Width()) / 2;
				region.top += (region.Height() - compressBitmap->Height()) / 2;
				display->Bitmap(region.left, region.top, *compressBitmap);
			}
		}

		// Draw the bitmap.
		if (Selected() && selectedBitmap)
			display->Bitmap(drawRegion.left + bitmapRegion.left, bitmapRegion.top, *selectedBitmap);
		else if (!Selected() && normalBitmap)
			display->Bitmap(drawRegion.left + bitmapRegion.left, bitmapRegion.top, *normalBitmap);

		// Draw the tree lines.
		ZafTreeList *treeList = TreeList();
		if (treeList->DrawLines())
		{
			int top = drawRegion.top;
			int bottom = drawRegion.bottom;
			int centerLine = (top + bottom) / 2;
			int centerColumn = drawRegion.left + (expandRegion.left + expandRegion.right) / 2;
			display->SetPalette(LogicalPalette(ZAF_PM_OUTLINE, state));

			ZafTreeItem *root = DynamicPtrCast(parent, ZafTreeItem);
			if (expanded && First())
				display->Line(centerColumn, expandRegion.bottom, centerColumn, bottom);
			if (root)
			{
				centerColumn = drawRegion.left + (root->expandRegion.left + root->expandRegion.right) / 2;
				display->Line(centerColumn, centerLine, drawRegion.left + expandRegion.left - 1, centerLine);
				display->Line(centerColumn, top, centerColumn, Next() ? bottom : centerLine - 1);
			}
			while (root)
				if (root->Next())
				{
					root = DynamicPtrCast(root->parent, ZafTreeItem);
					if (root)
					{
						centerColumn = drawRegion.left + (root->expandRegion.left + root->expandRegion.right) / 2;
						display->Line(centerColumn, top, centerColumn, bottom);
					}
				}
				else
					root = DynamicPtrCast(root->parent, ZafTreeItem);
		}
	}

	ZafRegionStruct backgroundRegion = textRegion;
	backgroundRegion.right = drawRegion.right;
	DrawBackground(backgroundRegion, ccode);

	// Compute the focus region.
	display->SetPalette(LogicalPalette(ZAF_PM_TEXT, state));
	ZafRegionStruct focusRegion = textRegion;
	focusRegion.left += drawRegion.left;
	focusRegion.right += drawRegion.left;
	ZafRegionStruct textSize = display->TextSize(*stringData);
	int centerY = focusRegion.top + focusRegion.Height() / 2;
	focusRegion.top = centerY - textSize.Height() / 2;
	focusRegion.bottom = focusRegion.top + textSize.Height() - 1;
	focusRegion.right = focusRegion.left + textSize.Width() - 1;
	focusRegion.left--;

	if (ccode == S_REDISPLAY_DATA)
	{
		// Erase the background.
		ZafRegionStruct backgroundRegion = drawRegion;
		backgroundRegion.left = focusRegion.left;
//		DrawBackground(backgroundRegion, ccode);
	}

	// Draw the text.
	if (Selected())
		display->SetPalette(LogicalPalette(ZAF_PM_TEXT, state));
	else
		display->SetPalette(LogicalPalette(ZAF_PM_BACKGROUND, state));
	display->Rectangle(focusRegion, 0, true);
	if (!Selected())
		display->SetPalette(LogicalPalette(ZAF_PM_TEXT, state));
	display->Text(focusRegion, *stringData, -1);

	// Draw the focus rectangle.
	if (Focus())
		DrawFocus(focusRegion, S_CURRENT);

	// Return the control code.
	EndDraw();
	return (ccode);
}

ZafEventType ZafTreeItem::DrawFocus(ZafRegionStruct &region, ZafEventType ccode)
{
	display->SetPalette(*((ZafScreenDisplay *)display)->xorPalette);
	display->SetMode(ZAF_MODE_XOR);
	display->Rectangle(region, -1);
	display->SetMode(ZAF_MODE_COPY);
	return (ccode);
}
#endif

ZafEventType ZafTreeItem::Event(const ZafEventStruct &event)
{
	// Check for zinc events.
	ZafEventType ccode = event.type;
	switch (ccode)
	{
	// ----- Create messages -----
	case S_INITIALIZE:
//		SetSystemObject(true);
		SetSystemObject(false);
	case S_DEINITIALIZE:
		// Initialize the object and its children.
		ccode = ZafWindowObject::Event(event);
		BroadcastEvent(event);
		break;

	case S_DESTROY:
		if (focus)
		{
			Event(ZafEventStruct(N_NON_CURRENT));
			Event(ZafEventStruct(S_NON_CURRENT));
		}
		Event(ZafEventStruct(S_DEINITIALIZE));
		break;

	// ----- Position & Size messages -----
	case S_CURRENT:
	case S_NON_CURRENT:
		{
		ccode = ZafWindowObject::Event(event);
		ZafTreeList *treeList = TreeList();
		if (treeList && treeList->ItemInView(this) && Visible() && AutomaticUpdate())
			Draw(event, ccode);
		}
		break;

	case S_COMPUTE_SIZE:
	case N_SIZE:
		// Compute the tree item's sub-regions.
		{
			int column = 0;

			// expand/compress.
			if (expandable)
			{
				expandRegion.left = ZAF_MARGIN_SIZE + ViewLevel() * (ARROW_SIZE + ZAF_MARGIN_SIZE);
				expandRegion.top = (zafRegion.Height() - ARROW_SIZE) / 2;
				expandRegion.right = expandRegion.left + ARROW_SIZE - 1;
				expandRegion.bottom = expandRegion.top + ARROW_SIZE - 1;
#if defined(ZAF_GRAPHICS_MODE)
				expandRegion.coordinateType = ZAF_PIXEL;
				column = expandRegion.right + ZAF_MARGIN_SIZE;

#elif defined(ZAF_TEXT_MODE)
				expandRegion.coordinateType = ZAF_CELL;
				column = ViewLevel() + 1;
#endif
			}
			else
			{
				expandRegion.left = expandRegion.right = ViewLevel() * (ARROW_SIZE + ZAF_MARGIN_SIZE);
				expandRegion.top = expandRegion.bottom = -1;
#if defined(ZAF_GRAPHICS_MODE)
				column = ZAF_MARGIN_SIZE + ViewLevel() * (ARROW_SIZE + ZAF_MARGIN_SIZE);
#else
				column = ViewLevel() + 1;
#endif
			}

			// bitmap.
			if (normalBitmap || selectedBitmap)
			{
				int height = 1, width = 1;
#if defined(ZAF_GRAPHICS_MODE)
				if (normalBitmap)
				{
					height = normalBitmap->Height();
					width = normalBitmap->Width();
				}
				if (selectedBitmap)
				{
					height = ZafMax(height, selectedBitmap->Height());
					width = ZafMax(width, selectedBitmap->Width());
				}
#endif

				bitmapRegion.left = column;
				bitmapRegion.top = (zafRegion.Height() - height) / 2;
				bitmapRegion.right = bitmapRegion.left + width - 1;
				bitmapRegion.bottom = bitmapRegion.top + height - 1;
#if defined(ZAF_GRAPHICS_MODE)
				bitmapRegion.coordinateType = ZAF_PIXEL;
				column = bitmapRegion.right + ZAF_MARGIN_SIZE;

#elif defined(ZAF_TEXT_MODE)
				bitmapRegion.coordinateType = ZAF_CELL;
				column = ViewLevel() + 1;
#endif
			}
			else
			{
				bitmapRegion.left = bitmapRegion.right = -1;
				bitmapRegion.top = bitmapRegion.bottom = -1;
			}

			// text
			display->SetFont(LogicalPalette(ZAF_PM_TEXT, ZAF_PM_ANY_STATE).font);
			textRegion.left = column;
			textRegion.top = 0;
			textRegion.right = column + display->TextSize(stringData->Text()).Width();
			textRegion.bottom = zafRegion.Height() - 1;
#if defined(ZAF_GRAPHICS_MODE)
			textRegion.coordinateType = ZAF_PIXEL;
#elif defined(ZAF_TEXT_MODE)
			textRegion.coordinateType = ZAF_CELL;
#endif
		}
		if (ccode == S_COMPUTE_SIZE)
		{
			// Get the text size.
			display->SetFont(LogicalPalette(ZAF_PM_TEXT, 0).font);
			ZafRegionStruct textSize = display->TextSize(*stringData);

			// Adjust the size.
			zafRegion.left = zafRegion.top = 0;
			zafRegion.right = ZafMax(TreeList()->ClientRegion().Width() - 1, textRegion.right);
//			zafRegion.bottom = ZafMax(textSize.Height() - 1, ARROW_SIZE - 1);
			zafRegion.bottom = ZafMax(zafRegion.bottom, 
				ZafMax((normalBitmap ? normalBitmap->Height() - 1 : 0), (selectedBitmap ? selectedBitmap->Height() - 1 : 0)));
		}
		break;

	case S_REGISTER_OBJECT:
		// Define the children to have the same screenID as their parent.
		screenID = parent->screenID;
		ccode = ZafWindow::Event(event);
		break;

	case E_MOUSE:
		{
			if (editMode && windowManager->MouseEventRoute() == this)
			{
				windowManager->CaptureMouse(0);
				return 0;
			}

			ZafTreeList *tree = TreeList();
			if (tree)
				ccode = tree->Event(event);
		}
		break;

	case E_KEY:
		ccode = LogicalEvent(event);
		if (ccode == L_SELECT && Expandable() && !Disabled())
		{
			ToggleExpanded();
			break;
		}

	// ----- Unknown or error messages -----
	default:
		ccode = ZafWindowObject::Event(event);
		break;
	}

	// Return the control code.
	return (ccode);
}

void ZafTreeItem::GetClip(ZafWindowObject *, ZafRegionStruct &clip,
	ZafRegionStruct &viewPort)
{
	// Let the tree list adjust the regions first.
	TreeList()->GetClip(this, clip, viewPort);

	// Adjust the viewPort region.
	viewPort.left += zafRegion.left;
	viewPort.top += zafRegion.top;
	viewPort.right += zafRegion.left;
	viewPort.bottom += zafRegion.top;

	// Adjust the clip region.
	if (viewPort.left > clip.left)
		clip.left = viewPort.left;
	if (viewPort.top > clip.top)
		clip.top = viewPort.top;
	clip.right = ZafMin(clip.right, clip.left + zafRegion.Width() - 1);
	clip.bottom = ZafMin(clip.bottom, clip.top + zafRegion.Height() - 1);
}

ZafRegionStruct ZafTreeItem::MaxRegion(ZafWindowObject *object, ZafVtJustify vWeight, ZafHzJustify hWeight)
{
	return (ZafWindow::MaxRegion(object, vWeight, hWeight));
}

ZafWindowObject *ZafTreeItem::NotifyFocus(ZafWindowObject *object, bool focus)
{
	return (ZafWindow::NotifyFocus(object, focus));
}

ZafWindowObject *ZafTreeItem::NotifySelection(ZafWindowObject *selectedObject, bool setSelected)
{
	// Defer to the parent.
	return (parent->NotifySelection(selectedObject, setSelected));
}

// ----- OS Specific Functions ----------------------------------------------

void ZafTreeItem::OSDestroy(void)
{
	// Defer to the base class.
	ZafWindow::OSDestroy();
}

ZafEventType ZafTreeItem::OSEvent(const ZafEventStruct &event)
{
	return (ZafWindow::OSEvent(event));
}

void ZafTreeItem::OSRegisterObject(void)
{
	screenID = parent->screenID;
}

OSWindowID ZafTreeItem::OSScreenID(ZafScreenIDType ) const
{
	return (screenID);
}

void ZafTreeItem::OSSize(void)
{
	if (!screenID)
		return;

//	clientRegion = MaxRegion(this, ZAF_VT_TOP, ZAF_HZ_LEFT);
	clientRegion = zafRegion;
	clientRegion.HzShift(-zafRegion.left);
	clientRegion.VtShift(-zafRegion.top);

	if (zafRegion == oldRegion)
		return;

	oldRegion = zafRegion;
	Event(N_SIZE);
}

ZafPaletteState ZafTreeItem::PaletteState(void)
{
	ZafPaletteState state = ZafWindowObject::PaletteState();
	if (TreeList()->Current() == this && Focus())
		state |= ZAF_PM_CURRENT;
	else
		state &= ~ZAF_PM_CURRENT;

	return (state);
}

// ----- Atrributes ---------------------------------------------------------

bool ZafTreeItem::SetExpanded(bool setExpanded)
{
	// Make sure the attribute has changed.
	if (expandable && expanded != setExpanded)
	{
		// Update the expanded status.
		expanded = setExpanded;

		// Update the tree.
		if (screenID)
		{
			ZafTreeList *treeList = TreeList();
			if (setExpanded)
			{
				if (treeList->ItemInView(this))
					treeList->ResizeVisibleItems(this);
			}
			else
			{
				if (treeList->firstVisible->parent == this)
				{
					treeList->firstVisible = this;
					treeList->ResizeVisibleItems(this);
				}
				else if (treeList->ItemInView(this))
					treeList->ResizeVisibleItems(this);
			}
			treeList->Event(S_VSCROLL_COMPUTE);
			treeList->Event(S_HSCROLL_COMPUTE);
			treeList->Event(S_REDISPLAY);
		}
	}

	// Return the current attribute.
	return (expanded);
}

bool ZafTreeItem::SetSelected(bool setSelected)
{
	// Defer to the root-base class.
	bool saveSelected = selected;
	ZafWindowObject::SetSelected(setSelected);
	if (selected != saveSelected && screenID && TreeList()->ItemInView(this) &&
		AutomaticUpdate() && Visible())
		Draw(ZafEventStruct(L_SELECT), L_SELECT);
	return (selected);
}

void ZafTreeItem::Sort(void)
{
	// Sort on the Zinc level.
	ZafList::Sort();
	for (ZafTreeItem *item = (ZafTreeItem *)first; item; item = (ZafTreeItem *)item->Next())
		item->Sort();
}

ZafWindowObject *ZafTreeItem::Subtract(ZafWindowObject *object)
{
	if (screenID && !SystemObject())
	{
		ZafTreeList *treeList = TreeList();
		ZafTreeItem *treeItem = DynamicPtrCast(object, ZafTreeItem);
		bool recompute = treeList->ItemInView(treeItem) ? true : false;

		if (!recompute)
		{
			ZafWindowObject *tObject = treeList->firstVisible;
			while (tObject != object)
				tObject = tObject->parent;
			if (tObject)
			{
				treeList->firstVisible = treeList->lastVisible = 0;
				recompute = true;
			}
		}
		// Redirect pointers.
		ZafTreeItem *replaceItem = ZAF_NULLP(ZafTreeItem);
		ZafTreeItem *tReplaceItem = DynamicPtrCast(treeItem->Next(), ZafTreeItem);

		// If object had a next, it is the replaceItem.
		if (tReplaceItem)
			replaceItem = tReplaceItem;
		// Otherwise, will need to get the ViewNext of the object's ViewPrevious.
		else
			tReplaceItem = treeItem->ViewPrevious();

		object->SetSelected(false);
		ZafWindow::Subtract(object);

		// Get ViewNext if don't already have the replaceItem.
		if (!replaceItem)
			replaceItem = tReplaceItem->ViewNext() ? tReplaceItem->ViewNext() : tReplaceItem;

		ZafWindowObject *currentTest = treeList->Current();
		while (currentTest && currentTest != object)
		{
			currentTest = currentTest->parent;
			if (currentTest == treeList)
				currentTest = ZAF_NULLP(ZafWindowObject);
		}
		if (currentTest)
			treeList->current = replaceItem;
		if (treeList->firstVisible == treeItem || treeItem->Index(treeList->firstVisible) != -1)
			treeList->firstVisible = replaceItem;
		if (treeList->SelectionType() == ZAF_SINGLE_SELECTION && treeList->current == replaceItem)
			replaceItem->SetSelected(true);

		if (recompute)
		{
			treeList->ResizeVisibleItems(replaceItem);
			treeList->Event(ZafEventStruct(S_REDISPLAY));
		}
	}
	else
		ZafWindow::Subtract(object);

	return (object);
}

bool ZafTreeItem::ToggleExpanded(void)
{
	// Toggle the expansion.
	return (SetExpanded(!expanded));
}

void ZafTreeItem::OSInvalidateRegion(ZafRegionStruct *region,
	bool immediateRedraw, bool )
{
	if (AutomaticUpdate() && Visible() && screenID)
	{
		ZafRegionStruct tRegion = region ? *region : zafRegion;

		// If this is an overlapped child object, make sure we only
		// invalidate the exposed pieces of the object.
		if (parent && overlapped)
		{
			// The comparison with other regions is based on
			// zafRegion.  ie, with respect to all the siblings'
			// parent origin.  So, need to shift the region that 
			// got passed in, since it was relative to the object's
			// origin.
			if (region)
			{
				tRegion.HzShift(zafRegion.left);
				tRegion.VtShift(zafRegion.top);
			}

			// tRegion now has the region that is being requested 
			// to get redrawn.  Need to match that with the actual 
			// exposed parts of the object.
			ZafRegionList regionList;
			regionList.Allocate(0, tRegion);
			OSGetExposedRegionList(regionList);

			ZafTreeList *list = TreeList();
			for (ZafRegionElement *updateElement = regionList.First(); updateElement;
				updateElement = updateElement->Next())
			{
				ZafRegionStruct updateRegion = updateElement->region;
				list->OSInvalidateRegion(&updateRegion, immediateRedraw, !SupportObject());
			}
		}
		else
		{
			if (!region)
			{
				tRegion.HzShift(-zafRegion.left);
				tRegion.VtShift(-zafRegion.top);
			}
			tRegion.HzShift(zafRegion.left);
			tRegion.VtShift(zafRegion.top);
			ZafTreeList *list = TreeList();
			list->OSInvalidateRegion(&tRegion, immediateRedraw, !SupportObject());
		}

		return;
	}
}

