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

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

extern "C"
{
#include _DrawP_
#include _PrimitiveP_
}

const int ARROW_SIZE	= 13;

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

ZafWindowObject *ZafTreeItem::Add(ZafWindowObject *object, ZafWindowObject *position)
{
	// Check for a support object.
	if (object->parent)
		return (object);
	else if (object->SupportObject())
		return (ZafWindow::Add(object, position));

	// Add the object to the list.
	object->SetBordered(false);
	object->SetSystemObject(false);
	object->SetParentPalette(true);
	ZafWindow::Add(object, position);

	// Reposition the item if the parent is registered.
	ZafTreeItem *item = DynamicPtrCast(object, ZafTreeItem);
	if (screenID && expanded && item)
	{
		// Optimize the update.
		ZafTreeList *treeList = TreeList();
		bool saveUpdate = treeList->AutomaticUpdate();
		treeList->SetAutomaticUpdate(false);

		// Determine the object's list position.
		ZafTreeItem *pItem = item->ViewPrevious();
		ZafScrollBar *horizontalScrollBar = treeList->HorizontalScrollBar();
		int column = horizontalScrollBar ? (int)-horizontalScrollBar->ScrollData()->Current() : 0;
		int line = pItem ? pItem->zafRegion.bottom : 0;
		for (ZafTreeItem *temp = item; temp; temp = temp->ViewNext())
		{
			treeList->ComputeNextPosition(temp, column, line);
			temp->Event(N_SIZE);
		}

		// Restore the update.
		if (saveUpdate && object->zafRegion.bottom <= treeList->lastVisible->zafRegion.bottom)
			treeList->SetAutomaticUpdate(saveUpdate);
		else if (saveUpdate)
		{
			treeList->automaticUpdate = saveUpdate;
			treeList->ScrollEvent(S_VSCROLL_COMPUTE);
		}
	}

	// Return the object pointer.
	return (object);
}

void ZafTreeItem::CheckMembers(ZafTreeItem *object)
{
	// Check for a valid tree list.
	ZafTreeList *treeList = TreeList();
	if (!treeList)
		return;

	// Check cancelObject.
	ZafWindowObject *temp;
	for (temp = treeList->cancelObject; temp; temp = temp->parent)
		if (temp == this)
		{
			treeList->cancelObject = ZAF_NULLP(ZafTreeItem);
			break;
		}

	// Check viewCurrent.
	for (temp = treeList->viewCurrent; temp; temp = temp->parent)
		if (temp == this)
		{
			treeList->viewCurrent = object ? object->Next() : this;
			if (!treeList->viewCurrent)
				treeList->viewCurrent = object->ViewPrevious();
			break;
		}

	// Check firstVisible.
	for (temp = treeList->firstVisible; temp; temp = temp->parent)
		if (temp == this)
		{
			treeList->firstVisible = object ? object->Next() : this;
			if (!treeList->firstVisible)
				treeList->firstVisible = object->ViewPrevious();
			break;
		}

	// Check lastVisible.
	for (temp = treeList->lastVisible; temp; temp = temp->parent)
		if (temp == this)
		{
			treeList->lastVisible = object ? object->Next() : this;
			if (!treeList->lastVisible)
				treeList->lastVisible = object->ViewPrevious();
			break;
		}
}

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

ZafPositionStruct ZafTreeItem::ConvertToOSPosition(const ZafWindowObject *object,
	const ZafPositionStruct *position) const
{
	// Compute the position.
	ZafPositionStruct osPosition;
	if (position)
		osPosition = *position;
	else
	{
		osPosition.column = object->zafRegion.left;
		osPosition.line = object->zafRegion.top;
	}

	// Adjust the position for non-system parent objects.
	for (ZafWindowObject *root = TreeList(); root && !root->SystemObject(); root = root->parent)
	{
		osPosition.column += root->zafRegion.left;
		osPosition.line += root->zafRegion.top;
	}

	// Return the position.
	return (osPosition);
}

ZafPositionStruct ZafTreeItem::ConvertToZafPosition(const ZafWindowObject *object,
	const ZafPositionStruct *position) const
{
	// Adjust the position for non-system parent objects.
	ZafPositionStruct zafPosition;
	if (position)
		zafPosition = *position;
	else
	{
		zafPosition.column = object->zafRegion.left;
		zafPosition.line = object->zafRegion.top;
	}

	// Adjust the position for non-system parent objects.  This must be done
	// in two pieces because object is "const."
	for (const ZafWindowObject *root = TreeList(); root && !root->SystemObject(); root = root->parent)
	{
		zafPosition.column -= root->zafRegion.left;
		zafPosition.line -= root->zafRegion.top;
	}

	// Return the position.
	return (zafPosition);
}

ZafEventType ZafTreeItem::DragDropEvent(const ZafEventStruct &)
{
	return (S_ERROR);
}

#if defined(ZAF_DEFAULT_DRAW)
ZafEventType ZafTreeItem::Draw(const ZafEventStruct &, ZafEventType ccode)
{
	// Check for draw errors.
	if (!screenID)
		return (S_ERROR);

	// Compute the actual draw region.
	ZafPaletteState state = PaletteState();
	ZafRegionStruct drawRegion = BeginDraw();

	// Fill the background.
	ZafPaletteStruct background = LogicalPalette(ZAF_PM_BACKGROUND, state & ~ZAF_PM_SELECTED);
	if (ccode != S_REDISPLAY_DATA)
	{
		display->SetPalette(background);
		display->Rectangle(drawRegion, 0, true);
	}

	// Draw the expansion button.
	ZafRegionStruct region = expandRegion;
	if (expandable && ccode != S_REDISPLAY_DATA)
	{
		DrawShadow(region, 1, ccode);
		region--;
		drawRegion.left = region.right + ZAF_MARGIN_SIZE;

		// Draw the arrow.
		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 (normalBitmap && ccode != S_REDISPLAY_DATA)
//		display->Bitmap(bitmapRegion.left, bitmapRegion.top, *normalBitmap);
	if (!Selected() && normalBitmap && ccode != S_REDISPLAY_DATA)
		display->Bitmap(bitmapRegion.left, bitmapRegion.top, *normalBitmap);
	else if (Selected() && selectedBitmap && ccode != S_REDISPLAY_DATA)
		display->Bitmap(bitmapRegion.left, bitmapRegion.top, *selectedBitmap);

	// Draw the tree lines.
	ZafTreeList *treeList = TreeList();
	if (treeList->DrawLines() && ccode != S_REDISPLAY_DATA)
	{
		int top = zafRegion.top;
		int bottom = zafRegion.bottom;
		int centerLine = (top + bottom) / 2;
		int centerColumn = (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 = (root->expandRegion.left + root->expandRegion.right) / 2;
			display->Line(centerColumn, centerLine, 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 = (root->expandRegion.left + root->expandRegion.right) / 2;
					display->Line(centerColumn, top, centerColumn, bottom);
				}
			}
			else
				root = DynamicPtrCast(root->parent, ZafTreeItem);
	}

	// Draw the focus.
	drawRegion = textRegion;
	DrawFocus(drawRegion, (focus && treeList->ViewCurrent() == this) ? S_CURRENT : S_NON_CURRENT);
	if ((state & ZAF_PM_SELECTED) || ccode == S_REDISPLAY_DATA)
	{
		display->SetPalette(LogicalPalette(ZAF_PM_BACKGROUND, state));
		display->Rectangle(drawRegion, 0, true);
	}

	// Draw the text.
	if (stringData)
	{
		display->SetPalette(LogicalPalette(ZAF_PM_TEXT, state));
		display->Text(drawRegion, stringData->Text(), -1, ZAF_HZ_LEFT, ZAF_VT_CENTER);
	}

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

ZafEventType ZafTreeItem::DrawFocus(ZafRegionStruct &region, ZafEventType ccode)
{
	// Defer to the base class.
	return (parent->DrawFocus(region, ccode));
}
#endif

ZafEventType ZafTreeItem::Event(const ZafEventStruct &event)
{
	ZafWindowObject *object;

	// Check for zinc events.
	ZafEventType ccode = event.type;
	switch (ccode)
	{
	// ----- Create messages -----
	case S_INITIALIZE:
	case S_DEINITIALIZE:
	case S_REGISTER_OBJECT:
		// Defer to the window class.
		ccode = ZafWindow::Event(event);
		break;

	// ----- Position & Size messages -----
	case S_COMPUTE_SIZE:
		{
		zafRegion.left = zafRegion.top = 0;
		zafRegion.right = TreeList()->ClientRegion().Width() - 1;
		zafRegion.bottom = display->cellHeight -
			display->preSpace - display->postSpace -
			2 * (ZAF_BORDER_SIZE + ZAF_MARGIN_SIZE);
		int height = ZafMax((normalBitmap ? normalBitmap->Height() : 0), (selectedBitmap ? selectedBitmap->Height() : 0));
		if ((normalBitmap || selectedBitmap) && height + 2 * ZAF_BORDER_SIZE - 1 >= zafRegion.Height())
			zafRegion.bottom = height + 2 * ZAF_BORDER_SIZE - 1;
		}
		break;

	case N_SIZE:
		// Compute the tree item's sub-regions.
		{
		int column = zafRegion.left;
		int line = zafRegion.top;

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

		// bitmap.
		bitmapRegion.coordinateType = zafRegion.coordinateType;
		if (normalBitmap || selectedBitmap)
		{
			int height = ZafMax((normalBitmap ? normalBitmap->Height() : 0), (selectedBitmap ? selectedBitmap->Height() : 0));
			int width = ZafMax((normalBitmap ? normalBitmap->Width() : 0), (selectedBitmap ? selectedBitmap->Width() : 0));
			bitmapRegion.left = column;
			bitmapRegion.top = line + (zafRegion.Height() - height) / 2;
			bitmapRegion.right = bitmapRegion.left + width - 1;
			bitmapRegion.bottom = bitmapRegion.top + height - 1;
			column = bitmapRegion.right + ZAF_MARGIN_SIZE;
		}
		else
		{
			bitmapRegion.left = bitmapRegion.right = -1;
			bitmapRegion.top = bitmapRegion.bottom = -1;
		}

		// text
		textRegion.coordinateType = zafRegion.coordinateType;
		textRegion.left = column;
		textRegion.top = zafRegion.top;
		display->SetPalette(LogicalPalette(ZAF_PM_TEXT, PaletteState()));
		if (stringData)
		{
			textRegion.right = textRegion.left + display->TextSize(stringData->Text()).Width() - 1;
			textRegion.right += 2 * (ZAF_BORDER_SIZE + 1); // focus margin.
		}
		else
			textRegion.right = zafRegion.right;
		textRegion.bottom = zafRegion.bottom;
		}
		break;

	case S_REDISPLAY:
	case S_REDISPLAY_DATA:
		// First, defer to the base class.
		ccode = ZafWindowObject::Event(event);

		// Next, broadcast to all of the children.
		if (expanded)
		{
			for (object = First(); object; object = object->Next())
				object->Event(event);
		}
		break;

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

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

void ZafTreeItem::GetClip(ZafWindowObject *object, ZafRegionStruct &clip, ZafRegionStruct &virtualRegion)
{
	// Defer to the base class.
	ZafWindowObject::GetClip(object, clip, virtualRegion);
}

ZafRegionStruct ZafTreeItem::MaxRegion(ZafWindowObject *object, ZafVtJustify vWeight, ZafHzJustify hWeight)
{
	// Defer to the parent class.
	return (parent->MaxRegion(object, vWeight, hWeight));
}

ZafWindowObject *ZafTreeItem::NotifyFocus(ZafWindowObject *focusObject, bool setFocus)
{
	// Defer to the base class.
	return (ZafWindow::NotifyFocus(focusObject, setFocus));
}

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

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

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

		// Recompute the list items.
		ZafTreeList *treeList = TreeList();
		if (treeList && treeList->screenID)
		{
			// Check for invalid pointers.
			ZafTreeItem *oldFirst = treeList->firstVisible;
			bool saveUpdate = treeList->AutomaticUpdate();
			if (!expanded)
			{
				CheckMembers(ZAF_NULLP(ZafTreeItem));
				treeList->SetAutomaticUpdate(false);
			}

			// Recompute the tree items.
			treeList->Event(N_SIZE);

			// Restore the update.
			if (saveUpdate && oldFirst == treeList->firstVisible)
			{
				treeList->automaticUpdate = saveUpdate;
				ZafEventStruct event(S_REDISPLAY_REGION);
				event.region = zafRegion;
				event.region.bottom = treeList->clientRegion.Height() - 1;
				treeList->Event(event);
			}
			else
				treeList->SetAutomaticUpdate(saveUpdate);
		}
	}

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

bool ZafTreeItem::SetSelected(bool setSelected)
{
	// Defer to the root-base class.
	return (ZafWindowObject::SetSelected(setSelected));
}

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 *temp)
{
	// Make sure the object is part of this list.
	ZafTreeItem *object = temp->SupportObject() ? ZAF_NULLP(ZafTreeItem) : DynamicPtrCast(temp, ZafTreeItem);
	if (!object)
		return (ZafWindow::Subtract(temp));
	ZafTreeList *treeList = TreeList();

	// Optimize the update.
	bool saveUpdate = treeList->AutomaticUpdate();
	treeList->SetAutomaticUpdate(false);

	// Check for invalid pointers.
	CheckMembers(object);

	// Make sure the object that is subracted is not the current object.
	if (object->Focus())
		treeList->ViewCurrent()->SetFocus(true);
	else 
		treeList->ZafList::SetCurrent(treeList->ViewCurrent());

	// Remove the object from the list.
	ZafWindowObject *nextObject = ZafWindow::Subtract(object);

	// Reposition items if the list is registered.
	if (screenID)
		treeList->Event(N_SIZE);

	// Restore the update and return the next object.
	treeList->SetAutomaticUpdate(saveUpdate);
	return (nextObject);
}

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

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

void ZafTreeItem::OSDestroy(void)
{
	// Check for invalid pointers.
	CheckMembers(ZAF_NULLP(ZafTreeItem));

	// Defer the notification to the base class.
	ZafWindow::OSDestroy();
}

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

void ZafTreeItem::OSRegisterObject(void)
{
	// No action is required here.
}

OSWindowID ZafTreeItem::OSScreenID(ZafScreenIDType type) const
{
	// Return the os handle.
	return (ZafWindowObject::OSScreenID(type));
}

void ZafTreeItem::OSSize(void)
{
	// Default to the base window object class.
	ZafWindowObject::OSSize();
}
