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

#include <time.h>
#include "i_notebk.hpp"
#include <z_app.hpp>
#define USE_RAW_KEYS
#include <z_keymap.hpp>
#include <z_mouse2.hpp>
#include <z_string.hpp>
#include <z_title.hpp>
#include <z_timer.hpp>
#include <z_utils.hpp>

// ----- ZafNotebook -------------------------------------------------------

ZafWindowObject *ZafNotebook::Add(ZafWindowObject *object, ZafWindowObject *position)
{
	// Modify the addition to be the notebook page, not its tab.
	if (!object)
		return (ZAF_NULLP(ZafWindowObject));
	else if (object->SupportObject())
		object = (ZafWindowObject *)ZafList::Get(support.Index(object));

	// Check for an added page.
	if (object == Current())
		return (object);
	else if (Index(object) == -1)
	{
		// Create the notebook tab.
		ZafWindow *window = DynamicPtrCast(object, ZafWindow);

		// Get the hotkey character.
		ZafIChar hotKey = window->Title()->HotKeyChar();
		int hotKeyIndex = window->Title()->HotKeyIndex();
		ZafNotebookTab *tab = new ZafNotebookTab((ZafIChar *)window->Text());
		tab->SetHotKey(hotKey, hotKeyIndex);
		tab->SetDisabled(window->Disabled());

		ZafWindowObject *tabPosition = position;
		if (tabPosition)
			tabPosition = tabControl->Get(Index(position));
		tabControl->Add(tab, tabPosition);
		if (!tabControl->firstVisible)
			tabControl->firstVisible = tab;
		if (!tabControl->currentTab)
			tabControl->SetCurrentTab(tab);

		// Remove invalid support objects.
		for (ZafWindowObject *wObject = window->SupportFirst(); wObject; )
		{
			// Remove invalid support objects (geometry manager is valid).
			ZafNumberID objID = wObject->NumberID();
			if (objID < 0 && objID != ZAF_NUMID_GEOMETRY)
			{
				ZafWindowObject *tobject = wObject->Next();
				window->Subtract(wObject);
				delete wObject;
				wObject = tobject;
			}
			else
				wObject = wObject->Next();
		}

		// Add the notebook page.
		ZafWindow::Add(object, position);

		// Pages can't be considered to be overlapping.
		object->overlapped = false;

		// Non-current pages have visible false so they don't draw.
		if (object != current)
			object->SetVisible(false);

		if (screenID)
			Event(N_SIZE);
	}

	return (object);
}

ZafEventType ZafNotebook::DispatchMouseEvent(const ZafEventStruct &event)
{
	ZafEventType ccode = LogicalEvent(event);

	// This object behaves as an atom for mouse events not over the client.
	ZafPositionStruct mousePosition = event.position;
	if (tabControl && tabControl->zafRegion.Overlap(mousePosition))
	{
		if (editMode)
			tabControl->Event(event);
		ccode = ZafWindowObject::DispatchMouseEvent(event);
	}
	else if (clientRegion.Overlap(mousePosition) && current)
		ccode = Current()->DispatchMouseEvent(event);
	else
		ccode = ZafWindow::DispatchMouseEvent(event);

	return (ccode);
}

ZafWindowObject *ZafNotebook::FindMouseObject(const ZafEventStruct &event)
{
	// If the object is disabled return a null pointer.
	if (Disabled())
		return (ZAF_NULLP(ZafWindowObject));

	if (modifyMode)
		return (this);

	if (windowManager->MouseEventRoute() == this)
		return (this);

	ZafPositionStruct position;
	position.line = event.osEvent.position.line;
	position.column = event.osEvent.position.column;
#if defined(ZAF_GRAPHICS_MODE)
	position.coordinateType = ZAF_PIXEL;
#else
	position.coordinateType = ZAF_CELL;
#endif
	ZafPositionStruct mousePosition = ConvertToZafPosition(this, &position);
	ZafWindowObject *mouseObject = 0;

	// This object behaves as an atom for mouse events not over the client.
	if (tabControl && tabControl->zafRegion.Overlap(mousePosition))
		mouseObject = this;
	else if (clientRegion.Overlap(mousePosition) && current)
		mouseObject = Current()->FindMouseObject(event);
	else
		mouseObject = this;

	return (mouseObject);
}

#if defined(ZAF_DEFAULT_DRAW)
ZafEventType ZafNotebook::Draw(const ZafEventStruct &event, ZafEventType ccode)
{
	// Compute the actual draw region.
	ZafRegionStruct drawRegion = BeginDraw();

	ZafRegionStruct saveClip = display->ClipRegion();
	display->SetClipRegion(event.region);

	if (Bordered())
		drawRegion--;

	DrawBackground(drawRegion, ccode);

	EndDraw();
	return (ccode);
}

ZafEventType ZafNotebook::DrawFrame(const ZafEventStruct &, ZafEventType ccode)
{
	ZafRegionStruct drawRegion = BeginDrawFrame();
	if (bordered)
		DrawBorder(drawRegion, ccode);
	DrawShadow(drawRegion, 0, ccode);
	EndDraw();
	return (ccode);
}

ZafEventType ZafNotebook::DrawShadow(ZafRegionStruct &region, int,
	ZafEventType ccode)
{
	ZafPaletteStruct lightShadow = LogicalPalette(ZAF_PM_LIGHT_SHADOW, ZAF_PM_ANY_STATE);
	ZafPaletteStruct darkShadow = LogicalPalette(ZAF_PM_DARK_SHADOW, ZAF_PM_ANY_STATE);
	ZafPaletteStruct whiteShadow = lightShadow;
	ZafPaletteStruct blackShadow = darkShadow;

	// Copy the background colors to the foreground since display->Line() uses
	// the foreground color.  The "shadow" palettes have different colors in
	// the foreground and background entries so they can generate two shades
	// of shadow.
	whiteShadow.colorForeground = whiteShadow.colorBackground;
	blackShadow.colorForeground = blackShadow.colorBackground;

	// Bottom edge.
	display->SetPalette(blackShadow);
	display->Line(region.left, region.bottom, region.right, region.bottom);

	// Right edge.
	display->Line(region.right, region.top + tabHeight, region.right, region.bottom);

	// Bottom edge.
	display->SetPalette(darkShadow);
	display->Line(region.left + 1, region.bottom - 1, region.right - 1, region.bottom - 1);

	// Right edge.
	display->Line(region.right - 1, region.top + tabHeight, region.right - 1, region.bottom - 1);

	// Left edge.
	display->SetPalette(whiteShadow);
	display->Line(region.left, region.top + tabHeight, region.left, region.bottom - 1);
	display->Line(region.left + 1, region.top + tabHeight, region.left + 1, region.bottom - 2);

	region -= ZAF_BORDER_SIZE;
	return (ccode);
}
#endif

ZafEventType ZafNotebook::Event(const ZafEventStruct &event)
{
	ZafEventType ccode = LogicalEvent(event);

	// Check for zinc events.
	switch (ccode)
	{
	case S_COMPUTE_SIZE:
		{
			ZafWindow::Event(event);

			// Compute the default tab sizes.
			if (!tabHeight)
				tabHeight = display->cellHeight;
		}
		break;

	case N_SIZE:
		{
			ZafWindowObject *object;

			// Adjust the page sizes.
			for (object = First(); object; object = object->Next())
			{
				object->Event(S_COMPUTE_SIZE);

				ZafRegionStruct newRegion;
				newRegion.left = 0;
				newRegion.top = 0;
				newRegion.right = clientRegion.right - clientRegion.left;
				newRegion.bottom = clientRegion.bottom - clientRegion.top;
#if defined(ZAF_GRAPHICS_MODE)
				newRegion -= ZAF_BORDER_SIZE;
#endif
				newRegion.coordinateType = clientRegion.coordinateType;
				object->SetRegion(newRegion);
				object->OSSize();
			}
		}
		break;

	case L_BEGIN_SELECT:
	case L_CONTINUE_SELECT:
	case L_END_SELECT:
	case L_VIEW:
		{
			if (editMode || windowManager->dragObject)
			{
				ZafWindowObject::Event(event);
				break;
			}

			// Only mouse events not over the client should get here.
			ZafPositionStruct mousePosition = event.position;
			if (tabControl && tabControl->zafRegion.Overlap(mousePosition))
			{
				// If the tab control is being overlapped for the first time
				// let it set the mouse cursor without making it the
				// window manager's mouseObject (that would mess up
				// help tips if the notebook had one).
				if (ccode == L_VIEW && mouseObject != tabControl)
				{
					ZafEventStruct mouseEvent = event;
					mouseEvent.type = N_MOUSE_ENTER;
					tabControl->Event(mouseEvent);
					mouseObject = tabControl;
				}
				else
					ccode = tabControl->Event(event);
			}
			else
			{
				// If nothing is overlapped, reset the mouse cursor.
				if (ccode == L_VIEW)
				{
					mouseObject = ZAF_NULLP(ZafWindowObject);
					eventManager->SetDeviceState(E_MOUSE, DM_VIEW);
				}
				else
					ccode = ZafWindow::Event(event);
			}
		}
		break;

	case N_MOUSE_ENTER:
		ZafWindow::Event(event);
		mouseObject = ZAF_NULLP(ZafWindowObject);
		break;

	case S_HOT_KEY:
		if (tabControl->Event(event) == S_UNKNOWN)
			ccode = current ? Current()->Event(event) : S_UNKNOWN;
		break;

	// ----- Movement -----
	case L_NEXT:
	case L_PREVIOUS:
		// Tab applies to current page only.  Shouldn't change pages.
		ccode = Current()->Event(event);
		break;

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

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

ZafRegionStruct ZafNotebook::MaxRegion(ZafWindowObject *object, ZafVtJustify vWeight, ZafHzJustify hWeight)
{
#if defined(ZAF_TEXT_MODE)
	SetBordered(false);
#endif

	// Check for existing OS region.
	ZafRegionStruct region = ZafWindow::MaxRegion(object, vWeight, hWeight);

#if defined(ZAF_GRAPHICS_MODE)
	// Getting client region.
	if (!object || object == this || !object->SupportObject())	// Tab control
	{
		// Reduce for shadow border.  Shadow border on top drawn by tab control.
		region.left += ZAF_BORDER_SIZE;
		region.right -= ZAF_BORDER_SIZE;
		region.bottom -= ZAF_BORDER_SIZE;
	}
#endif
	return (region);
}

bool ZafNotebook::PageDisabled(int page)
{
	ZafWindowObject *tab = tabControl->Get(page);
	return (tab->Disabled());
}

ZafEventType ZafNotebook::ScrollEvent(const ZafEventStruct &)
{
	// No processing is done at this level.
	return (S_ERROR);
}

ZafWindowObject *ZafNotebook::Subtract(ZafWindowObject *object)
{
	// Check for support objects.
	if (object->SupportObject() && (support.Index(object) != -1))
		return (ZafWindow::Subtract(object));

	// Save some page information, then remove the object.
	bool saveUpdate = AutomaticUpdate();
	automaticUpdate = false;
	ZafWindow *page = DynamicPtrCast(object, ZafWindow);
	int index = Index(page);
	object = ZafWindow::Subtract(object);

	// Re-add the title to the page.
	if (page && index >= 0)
	{
		ZafNotebookTab *title = DynamicPtrCast(tabControl->Get(index), ZafNotebookTab);
		if (title)
		{
			tabControl->Subtract(title);
			ZafTitle *nTitle = new ZafTitle(title->Text());
			nTitle->ZafButton::SetHotKey(title->HotKeyChar(), title->HotKeyIndex());
			page->ZafWindow::Add(nTitle);
			delete title;
		}
	}

	// Defer the rest to the base class.
	automaticUpdate = saveUpdate;
	return (object);
}

// ----- Attributes ---------------------------------------------------------

ZafIChar ZafNotebook::TabHotKeyChar(int page) const
{
	// Find the associated tab.
	ZafNotebookTab *tab = (ZafNotebookTab *)((page == -1) ? tabControl->Current() : tabControl->Get(page));
	return (tab ? tab->HotKeyChar() : '\0');
}

int ZafNotebook::TabHotKeyIndex(int page) const
{
	// Find the associated tab.
	ZafNotebookTab *tab = (ZafNotebookTab *)((page == -1) ? tabControl->Current() : tabControl->Get(page));
	return (tab ? tab->HotKeyIndex() : -1);
}

const ZafIChar *ZafNotebook::TabText(int page)
{
	// Get the text.
	ZafWindowObject *object = (ZafWindowObject *)((page == -1) ? tabControl->Current() : tabControl->Get(page));
	return (object ? object->Text() : ZAF_NULLP(ZafIChar));
}

bool ZafNotebook::SetAutomaticUpdate(bool setAutomaticUpdate)
{
	// Defer to the base class.
	return (ZafWindow::SetAutomaticUpdate(setAutomaticUpdate));
}

int ZafNotebook::SetCurrentPage(int index)
{
	if (CurrentPage() != index && index >= FirstPage() && index <= LastPage())
	{
		// Turn drawing off for non-current page.
		if (current)
			Current()->SetVisible(false);

		// Make page current.
		ZafWindowObject *page = DynamicPtrCast(Get(index), ZafWindowObject);
		if (Focus())
			page->SetFocus(true);
		else
			SetCurrent(page);
		page->SetVisible(true);

		// Update the tabs.
		tabControl->SetCurrentTab(index);

		// Send notification event.
		Event(N_CHANGE_PAGE);
	}

	// Return the current page.
	return (CurrentPage());
}

ZafIChar ZafNotebook::SetTabHotKey(int page, ZafIChar hotKeyChar, int index)
{
	// Find the associated tab.
	ZafNotebookTab *tab = (ZafNotebookTab *)((page == -1) ? tabControl->Current() : tabControl->Get(page));
	return (tab ? tab->SetHotKey(hotKeyChar, index) : '\0');
}

ZafError ZafNotebook::SetTabText(const ZafIChar *text, int page)
{
	// Set the text.
	ZafWindowObject *object = (ZafWindowObject *)((page == -1) ? tabControl->Current() : tabControl->Get(page));
	ZafError tError;
	if (object)
	{
		tError = object->SetText(text);
		ZafRegionStruct tRegion = object->zafRegion;
		tRegion.VtShift(-tabControl->firstVisible->zafRegion.top);
		tRegion.HzShift(-tabControl->firstVisible->zafRegion.left);
		tabControl->OSInvalidateRegion(&tRegion);
	}
	else
		tError = ZAF_ERROR_INVALID;
	return (tError);
}

bool ZafNotebook::SetPageDisabled(int page, bool setDisabled)
{
	ZafWindowObject *tab = tabControl->Get(page);
	return (tab ? tab->SetDisabled(setDisabled) : false);
}

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

void ZafNotebook::OSDestroy(void)
{
	// Destroy the notebook tabs.
	tabControl->Destroy();

	// Let the base class process.
	ZafWindow::OSDestroy();
}

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

void ZafNotebook::OSMapPalette(ZafPaletteStruct &palette, ZafPaletteType  type, ZafPaletteState state)
{
	ZafWindowObject::OSMapPalette(palette, type, state);
}

void ZafNotebook::OSRegisterObject(void)
{
	ZafWindowObject::OSRegisterObject();
}

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

ZafError ZafNotebook::OSUpdatePalettes(ZafPaletteType zafTypes, ZafPaletteType osTypes)
{
	return (ZafWindow::OSUpdatePalettes(zafTypes, osTypes));
}

// ----- ZafNotebookTab -------------------------------------------------------

ZafPaletteMap ZAF_FARDATA tabPaletteMap[] =
{
	{ ZAF_PM_LIGHT_SHADOW, ZAF_PM_ANY_STATE, { ZAF_LINE_SOLID, ZAF_PTN_SOLID_FILL, ZAF_CLR_LIGHTGRAY, ZAF_CLR_WHITE, ZAF_MONO_BLACK, ZAF_MONO_WHITE, ZAF_FNT_SMALL } },
	{ ZAF_PM_DARK_SHADOW, ZAF_PM_ANY_STATE, { ZAF_LINE_SOLID, ZAF_PTN_SOLID_FILL, ZAF_CLR_DARKGRAY, ZAF_CLR_BLACK, ZAF_MONO_BLACK, ZAF_MONO_WHITE, ZAF_FNT_SMALL } },
	{ ZAF_PM_NONE, 0, { 0, 0, 0, 0, 0, 0, 0, 0 } }
};

ZafNotebookTab::ZafNotebookTab(const ZafIChar *text) :
	ZafButton(0, 0, 0, 1, text)
{
	SetSelectOnDownClick(true);
	SetHzJustify(ZAF_HZ_CENTER);
	SetOSDraw(false);
	SetNoncurrent(true);
#if defined(ZAF_GRAPHICS_MODE)
	SetDepth(1);
#elif defined(ZAF_TEXT_MODE)
	SetDepth(0);
#endif
	SetUserPaletteData(new ZafPaletteData(tabPaletteMap, true));
}

ZafNotebookTab::ZafNotebookTab(const ZafNotebookTab &copy) :
	ZafButton(copy)
{
}

#if defined(ZAF_DEFAULT_DRAW)
ZafEventType ZafNotebookTab::Draw(const ZafEventStruct &event, ZafEventType ccode)
{
	// Compute the actual draw region.
	ZafRegionStruct drawRegion = BeginDraw();

	display->SetClipRegion(event.region);

	// Don't draw over the bottom shadow (the current page's top shadow).
	if (((ZafNotebookTabControl *)parent)->currentTab != this)
		drawRegion.bottom -= ZAF_BORDER_SIZE;
	DrawBackground(drawRegion, ccode);
	DrawShadow(drawRegion, 0, ccode);

	const ZafIChar *text = Text();
	display->SetPalette(LogicalPalette(ZAF_PM_TEXT, PaletteState()));
	display->Text(drawRegion, text, -1, ZAF_HZ_CENTER, vtJustify, hotKeyIndex);

	EndDraw();

	return (ccode);
}

ZafEventType ZafNotebookTab::DrawShadow(ZafRegionStruct &region, int,
	ZafEventType ccode)
{
	ZafPaletteStruct lightShadow = LogicalPalette(ZAF_PM_LIGHT_SHADOW, ZAF_PM_ANY_STATE);
	ZafPaletteStruct darkShadow = LogicalPalette(ZAF_PM_DARK_SHADOW, ZAF_PM_ANY_STATE);
	ZafPaletteStruct whiteShadow = lightShadow;
	ZafPaletteStruct blackShadow = darkShadow;

	// Copy the background colors to the foreground since display->Line() uses
	// the foreground color. The "shadow" palettes have different colors in
	// the foreground and background entries so they can generate two shades
	// of shadow.
	whiteShadow.colorForeground = whiteShadow.colorBackground;
	blackShadow.colorForeground = blackShadow.colorBackground;

	// Left shadow area.
	ZafCoordinate point[6];
	point[0] = region.left; point[1] = region.bottom;
	point[2] = region.left; point[3] = region.top + chisel;
	point[4] = region.left + chisel; point[5] = region.top;
	display->SetPalette(whiteShadow);
	display->Line(region.left, region.top + chisel, region.left, region.bottom);
	display->Line(region.left + 1, region.top + chisel - 1, region.left + 1, region.bottom);
	display->Line(region.left + chisel, region.top + 1, region.right - chisel, region.top + 1);

	// Top shadow area.
	display->Line(region.left + chisel, region.top, region.right - chisel, region.top);

	// Right shadow area.
	point[0] = region.right; point[1] = region.bottom;
	point[2] = region.right; point[3] = region.top + chisel;
	point[4] = region.right - chisel + 1; point[5] = region.top + 1;
	display->SetPalette(blackShadow);
	display->Polygon(3, point);

	point[0] = region.right - 1; point[1] = region.bottom;
	point[2] = region.right - 1; point[3] = region.top + chisel;
	point[4] = region.right - chisel + 1; point[5] = region.top + 2;
	display->SetPalette(darkShadow);
	display->Polygon(3, point);

	region.left++;
	region.top++;
	region.right -= ZAF_BORDER_SIZE;

	return (ccode);
}
#endif

ZafEventType ZafNotebookTab::Event(const ZafEventStruct &event)
{
	ZafEventType ccode = LogicalEvent(event);

	// Check for zinc events.
	switch (ccode)
	{
	// ----- Actions -----
	case L_SELECT:
		{
			// Change the page.
			ZafNotebook *parentNotebook = DynamicPtrCast(parent->parent, ZafNotebook);
			if (parentNotebook)
				parentNotebook->SetCurrentPage(ListIndex());
		}
		break;

	case S_COMPUTE_SIZE:
		// Calculate width according to text. ZafNotebookTabControl will
		// set height and position.
		ZafWindowObject::Event(event);
		display->SetFont(LogicalPalette(ZAF_PM_TEXT, ZAF_PM_ANY_STATE).font);
		zafRegion.left = 0;
		zafRegion.right = display->TextSize(Text()).Width() - 1 + 2 * display->cellWidth;
		break;

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

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

// ----- ZafNotebookTabControl ---------------------------------------------------

ZafNotebookTabControl::ZafNotebookTabControl(void) :
	ZafWindow(0, 0, 10, 1),
	currentTab(ZAF_NULLP(ZafWindowObject)), firstVisible(ZAF_NULLP(ZafWindowObject))
{
	SetMoveable(false);
	SetNoncurrent(true);
	SetRegionType(ZAF_AVAILABLE_REGION);
	SetSizeable(false);
	SetSupportObject(true);

	Add(new ZafNotebookScrollArrows);
}

ZafNotebookTabControl::ZafNotebookTabControl(const ZafNotebookTabControl &copy) :
	ZafWindow(copy)
{
	firstVisible = First();
	SetCurrentTab(firstVisible);
}

ZafNotebookTabControl::~ZafNotebookTabControl(void)
{
}

ZafWindowObject *ZafNotebookTabControl::Add(ZafWindowObject *object, ZafWindowObject *position)
{
	bool saveUpdate = automaticUpdate;
	SetAutomaticUpdate(false);
	ZafWindowObject *retVal = ZafWindow::Add(object, position);
	if (screenID)
		Event(N_SIZE);
	SetAutomaticUpdate(saveUpdate);

	return (retVal);
}

ZafPositionStruct ZafNotebookTabControl::ConvertToOSPosition(const ZafWindowObject *object,
	const ZafPositionStruct *zafPosition) const
{
	ZafPositionStruct osPosition = ZafWindow::ConvertToOSPosition(object, zafPosition);
	if (!object->SupportObject())
	{
		if (!firstVisible)
			;
		else if (firstVisible == currentTab)
			osPosition.column -= firstVisible->zafRegion.left;
		else
			osPosition.column -= firstVisible->zafRegion.left - ZafNotebookTab::chisel;
	}

	return (osPosition);
}

ZafPositionStruct ZafNotebookTabControl::ConvertToZafPosition(const ZafWindowObject *object,
	const ZafPositionStruct *osPosition) const
{
	ZafPositionStruct zafPosition = ZafWindow::ConvertToZafPosition(object, osPosition);
	if (!object->SupportObject())
	{
		if (!firstVisible)
			;
		else if (firstVisible == currentTab)
			zafPosition.column += firstVisible->zafRegion.left;
		else
			zafPosition.column += firstVisible->zafRegion.left - ZafNotebookTab::chisel;
	}

	return (zafPosition);
}

ZafEventType ZafNotebookTabControl::DispatchMouseEvent(const ZafEventStruct &event)
{
	return (ZafWindowObject::DispatchMouseEvent(event));
}

#if defined(ZAF_DEFAULT_DRAW)
ZafEventType ZafNotebookTabControl::Draw(const ZafEventStruct &event, ZafEventType ccode)
{
	// Draw the background.
	ZafWindow::Draw(event, ccode);

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

	// Draw the shadow at top of page (runs along bottom of tab control).
	ZafPaletteStruct lightShadow = LogicalPalette(ZAF_PM_LIGHT_SHADOW, ZAF_PM_ANY_STATE);
	ZafPaletteStruct whiteShadow = lightShadow;

	// Copy the background colors to the foreground since display->Line() uses
	// the foreground color. The "shadow" palettes have different colors in
	// the foreground and background entries so they can generate two shades
	// of shadow.
	whiteShadow.colorForeground = whiteShadow.colorBackground;
	display->SetPalette(whiteShadow);

	if (currentTab)
	{
		ZafRegionStruct curObjectRegion = currentTab->zafRegion;
		int scrollOffset = 0;
		if (firstVisible)
			scrollOffset = (firstVisible == currentTab ? firstVisible->zafRegion.left :
				firstVisible->zafRegion.left - ZafNotebookTab::chisel);

		curObjectRegion.HzShift(-scrollOffset);
		display->Line(drawRegion.left, drawRegion.bottom, curObjectRegion.left, drawRegion.bottom);
		display->Line(curObjectRegion.right - 2, drawRegion.bottom, drawRegion.right, drawRegion.bottom);

		display->SetPalette(lightShadow);
		display->Line(curObjectRegion.left + 1, drawRegion.bottom, curObjectRegion.right - 2, drawRegion.bottom);
	}
	else
		display->Line(drawRegion.left, drawRegion.bottom, drawRegion.right, drawRegion.bottom);

	EndDraw();

	return (ccode);
}
#endif

ZafEventType ZafNotebookTabControl::Event(const ZafEventStruct &event)
{
	ZafNotebook *parentNBook = DynamicPtrCast(parent, ZafNotebook);

 	// Switch on the event type.
	ZafEventType ccode = LogicalEvent(event);
	switch (ccode)
	{
	case S_COMPUTE_SIZE:
		ZafWindow::Event(event);
#if defined(ZAF_GRAPHICS_MODE)
		zafRegion.bottom = zafRegion.top + parentNBook->TabHeight() + 1;
#elif defined(ZAF_TEXT_MODE)
		zafRegion.bottom = zafRegion.top;
#endif
		break;

	case N_SIZE:
		{
			// Compute the default tab sizes.
			int tabHeight = parentNBook->TabHeight();
			int tabWidth = parentNBook->TabWidth();
			int totalTabWidth = 0;

			ZafWindowObject *object;
			for (object = First(); object; object = object->Next())
			{
				object->Event(S_COMPUTE_SIZE);

				ZafRegionStruct newRegion;
				int width = tabWidth ? tabWidth : object->zafRegion.Width();
#if !defined(ZAF_TEXT_MODE)
				newRegion.top = ZafNotebookTab::chisel;
				newRegion.bottom = tabHeight;
				if (object->Previous())
					newRegion.left = object->Previous()->zafRegion.right + 1 -
						(object->Previous() == currentTab ? ZafNotebookTab::chisel : 0);
				else
					newRegion.left = ZafNotebookTab::chisel;
				newRegion.right = newRegion.left + width - 1;
				if (object == currentTab)
				{
					newRegion.left -= ZafNotebookTab::chisel;
					newRegion.top -= ZafNotebookTab::chisel;
					newRegion.right += ZafNotebookTab::chisel;
				}
#else
				newRegion.top = 0;
				newRegion.bottom = newRegion.top + tabHeight - 1;
				newRegion.left = object->Previous() ?
				object->Previous()->zafRegion.right + 1 : 0;
				newRegion.right = newRegion.left + width - 1;
#endif
				newRegion.coordinateType = object->zafRegion.coordinateType;
				object->SetRegion(newRegion);
				object->OSSize();
				totalTabWidth += object->zafRegion.Width();
			}

			// Don't show the tab scrollers if not needed.
			ZafWindowObject *scrollArrows = (ZafWindowObject *)support.First();
			bool showScrollArrows = (totalTabWidth >= zafRegion.Width()) ? true : false;
			if (!showScrollArrows && firstVisible != First())
			{
				OSInvalidateRegion(0);
				firstVisible = First();
			}
			scrollArrows->SetVisible(showScrollArrows);
		}
		break;

	case L_BEGIN_SELECT:
	case L_CONTINUE_SELECT:
	case L_END_SELECT:
		{
		// If the mouse is over the client region, let the current page process.
		if (clientRegion.Overlap(event.position))
		{
			// Shift region so we can display what is in view.
			ZafPositionStruct scrolledMousePosition = event.position;
			if (firstVisible)
				scrolledMousePosition.column += firstVisible == currentTab ?
					firstVisible->zafRegion.left : firstVisible->zafRegion.left - ZafNotebookTab::chisel;

			ZafWindowObject *object;
			for (object = Last();
				object && !object->zafRegion.Overlap(scrolledMousePosition);
				object = object->Previous())
					; // Do nothing.

			// object points to overlapped object.
			if (object)
				object->Event(event);
		}
		else if (SupportFirst())
			SupportFirst()->Event(event);
		}
		break;

	case S_HSCROLL:
		{
			bool redraw = false;

			if (event.scroll.delta < 0)
			{
				if (firstVisible && firstVisible->Previous())
				{
					firstVisible = firstVisible->Previous();
					redraw = true;
				}
			}
			else
			{
				if (firstVisible && firstVisible->Next())
				{
					firstVisible = firstVisible->Next();
					redraw = true;
				}
			}

			if (redraw)
				OSInvalidateRegion(&clientRegion, false, false);
//				Event(ZafEventStruct(S_REDISPLAY_REGION, 0, clientRegion));
		}
		break;

	default:
		ccode = ZafWindow::Event(event);
		break;
	}

	return (ccode);
}

void ZafNotebookTabControl::GetClip(ZafWindowObject *object, ZafRegionStruct &clip,
	ZafRegionStruct &viewPort)
{
	ZafWindow::GetClip(object, clip, viewPort);
#if defined(ZAF_GRAPHICS_MODE)
	if (object != this && !object->SupportObject() && firstVisible)
	{
		int hzShift = - (firstVisible == currentTab ? firstVisible->zafRegion.left :
			firstVisible->zafRegion.left - ZafNotebookTab::chisel);
		viewPort.HzShift(hzShift);
	}
#endif
}

ZafRegionStruct ZafNotebookTabControl::MaxRegion(ZafWindowObject *object, ZafVtJustify vWeight, ZafHzJustify hWeight)
{
	// Check for existing OS region.
	ZafRegionStruct region = ZafWindow::MaxRegion(object, vWeight, hWeight);
#if defined(ZAF_GRAPHICS_MODE)
	// Getting client region.  Save room for shadow along top of notebook pages.
	if (!object || object == this || !object->SupportObject())	// scroll arrows
		region.bottom--;
#endif

	return (region);
}

int ZafNotebookTabControl::SetCurrentTab(int setCurrentTab)
{
	// Set new tab current.
	ZafWindowObject *oldCurrent = currentTab;
	currentTab = DynamicPtrCast(Get(setCurrentTab), ZafWindowObject);

	if (!parent || !parent->screenID)
	{
		firstVisible = currentTab;
		return (setCurrentTab);
	}

	// Find scroll offset to draw proper area of tab control.
	int scrollOffset = firstVisible->zafRegion.left;

	if (currentTab->zafRegion.left < firstVisible->zafRegion.left)
	{
		// New current tab is off left edge.
		firstVisible = currentTab;
	}
	else if (currentTab->zafRegion.right - scrollOffset > clientRegion.right)
	{
		// New current tab is off right edge.
		while (currentTab->zafRegion.right - scrollOffset > clientRegion.right &&
			firstVisible != Last())
		{
			firstVisible = firstVisible->Next();
			if (!firstVisible)
				firstVisible = Last();
			scrollOffset = firstVisible->zafRegion.left;
		}
	}

	// Prevent tab control from updating until we're done switching tabs.
	SetAutomaticUpdate(false);

	// Update previously current tab's region.
	if (oldCurrent)
	{
		ZafRegionStruct newRegion = oldCurrent->zafRegion;
		newRegion.left += ZafNotebookTab::chisel;
		newRegion.top += ZafNotebookTab::chisel;
		newRegion.right -= ZafNotebookTab::chisel;
		oldCurrent->SetRegion(newRegion);
		oldCurrent->OSSize();
	}

	// Update the new tab's region.
	ZafRegionStruct newRegion = currentTab->zafRegion;
	newRegion.left -= ZafNotebookTab::chisel;
	newRegion.top -= ZafNotebookTab::chisel;
	newRegion.right += ZafNotebookTab::chisel;
	currentTab->SetRegion(newRegion);
	currentTab->OSSize();

	// Draw the tabs.
	SetAutomaticUpdate(true);

	// Return the current tab number.
	return (setCurrentTab);
}

bool ZafNotebookTabControl::SetSupportObject(bool setSupportObject)
{
	// Make sure the attribute has changed.
	if (supportObject != setSupportObject)
		supportObject = setSupportObject;

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

ZafWindowObject *ZafNotebookTabControl::Subtract(ZafWindowObject *object)
{
	bool saveUpdate = automaticUpdate;
	SetAutomaticUpdate(false);
	ZafWindowObject *retVal = ZafWindow::Subtract(object);
	if (object == currentTab)
		currentTab = ZAF_NULLP(ZafWindowObject);
	if (screenID)
		Event(N_SIZE);
	SetAutomaticUpdate(saveUpdate);

	return (retVal);
}

void ZafNotebookTabControl::OSUpdateRegions(ZafRegionList *regionList)
{
	if (AutomaticUpdate() && Visible() && screenID)
	{
		ZafEventStruct event(S_REDISPLAY_REGION);
		event.region = regionList->First()->region;

		ZafRegionStruct osRegion;
		if (parent && windowManager->Index(this) == -1)
			osRegion = parent->ConvertToOSRegion(this, 0);
		else
			osRegion = zafRegion;

		ZafRegionElement *damageElement;
		ZafRegionElement *nextElement;
		ZafWindowObject *object;

		// Globalize regionList
		for (damageElement = regionList->First(); damageElement;
			damageElement = damageElement->Next())
		{
			event.region.left = ZafMin(event.region.left, damageElement->region.left);
			event.region.top = ZafMin(event.region.top, damageElement->region.top);
			event.region.right = ZafMax(event.region.right, damageElement->region.right);
			event.region.bottom = ZafMax(event.region.bottom, damageElement->region.bottom);
			damageElement->region.VtShift(osRegion.top);
			damageElement->region.HzShift(osRegion.left);
		}

		display->SetDrawRegions(regionList, false);
#if defined(ZAF_GRAPHICS_MODE)
		DrawFrame(event, S_REDISPLAY_REGION);
		event.region.HzShift(-clientRegion.left);
		if (event.region.left < 0)
			event.region.left = 0;
		event.region.VtShift(-clientRegion.top);
		if (event.region.top < 0)
			event.region.top = 0;
#endif
		Draw(event, S_REDISPLAY_REGION);
		display->SetDrawRegions(0);

		for (damageElement = regionList->First(); damageElement;
			damageElement = damageElement->Next())
		{
			damageElement->region.VtShift(-osRegion.top);
			damageElement->region.HzShift(-osRegion.left);
		}

		// Draw the scrollArrows, if present.
		object = SupportFirst();
		if (object)
		{
			for (damageElement = regionList->First(); damageElement;
				damageElement = damageElement->Next())
			{
				damageElement->region.VtShift(-object->zafRegion.top);
				damageElement->region.HzShift(-object->zafRegion.left);
			}
			
			object->OSUpdateRegions(regionList);

			for (damageElement = regionList->First(); damageElement;
				damageElement = damageElement->Next())
			{
				damageElement->region.VtShift(object->zafRegion.top);
				damageElement->region.HzShift(object->zafRegion.left);
			}
		}

#if defined(ZAF_GRAPHICS_MODE)
		// Don't try to draw children if the clientRegion region is "flat."
		if (clientRegion.right <= clientRegion.left || clientRegion.bottom <= clientRegion.top)
			return;
#endif

#if defined(ZAF_GRAPHICS_MODE)
		for (damageElement = regionList->First(); damageElement;
			damageElement = damageElement->Next())
		{
			damageElement->region.HzShift(-clientRegion.left);
			damageElement->region.VtShift(-clientRegion.top);
			if (firstVisible)
				damageElement->region.HzShift(firstVisible == currentTab ?
					firstVisible->zafRegion.left : firstVisible->zafRegion.left -
					ZafNotebookTab::chisel);
		}
#endif
		// Draw current tab last, since it overlaps adjacent tabs.
		object = currentTab;
		if (object)
			object = object->Next() ? object->Next() : First();

		ZafRegionList childList;
		ZafRegionStruct childRegion;

		while (object)
		{
			childList.Destroy();
			for (damageElement = regionList->First();
				damageElement; damageElement = nextElement)
			{
				nextElement = damageElement->Next();
				if (object->zafRegion.Overlap(damageElement->region, childRegion))
				{
					childRegion.HzShift(-object->zafRegion.left);
					childRegion.VtShift(-object->zafRegion.top);
					childList.Allocate(0, childRegion);
				}
			}
			if (childList.First())
				object->OSUpdateRegions(&childList);

			// If just drew current tab, we're done, so break out of loop.
			if (object == currentTab)
				break;
			else
				object = object->Next() ? object->Next() : First();
		}
	}
}



// ----- ZafNotebookScrollArrows ---------------------------------------------------

#define ZAF_SCROLL_BUTTON_INFO
#include "img_def.cpp"

ZafNotebookScrollArrows::ZafNotebookScrollArrows(void) :
	ZafButton(0, 0, 0, 0, ZafLanguageManager::blankString),
	middle(0), scrollState(ZAF_SCROLL_NONE)
{
	SetBordered(false);
	SetHzJustify(ZAF_HZ_CENTER);
	SetSupportObject(true);
	SetAllowToggling(false);
	SetAutoSize(false);
	SetNoncurrent(true);
	SetRegionType(ZAF_AVAILABLE_REGION);
	ImageAllocate();
}

ZafNotebookScrollArrows::ZafNotebookScrollArrows(const ZafNotebookScrollArrows &copy) :
	ZafButton(copy),
	middle(0), scrollState(ZAF_SCROLL_NONE)
{
	ImageAllocate();
}

ZafNotebookScrollArrows::~ZafNotebookScrollArrows(void)
{
	ImageFree();
}

#if defined(ZAF_DEFAULT_DRAW)
ZafEventType ZafNotebookScrollArrows::Draw(const ZafEventStruct &event,
	ZafEventType ccode)
{
	// Compute the actual draw region.
	ZafPaletteState state = PaletteState();
	ZafRegionStruct drawRegion = BeginDraw();

	display->SetClipRegion(event.region);

	// Draw shadow for top of page (runs along bottom of scroll arrows).
	ZafPaletteStruct whiteShadow = LogicalPalette(ZAF_PM_LIGHT_SHADOW, 0);
	ZafPaletteStruct blackShadow = LogicalPalette(ZAF_PM_DARK_SHADOW, 0);

	// Copy the background colors to the foreground since display->Line() uses
	// the foreground color. The "shadow" palettes have different colors in
	// the foreground and background entries so they can generate two shades
	// of shadow.
	whiteShadow.colorForeground = whiteShadow.colorBackground;
	blackShadow.colorForeground = blackShadow.colorBackground;

	drawRegion.bottom--;

	display->SetPalette(whiteShadow);
	display->Line(drawRegion.left, drawRegion.bottom, drawRegion.right - 1, drawRegion.bottom);
	display->SetPalette(blackShadow);
	display->Pixel(drawRegion.right, drawRegion.bottom, blackShadow.colorForeground);

	drawRegion.bottom--;

	// Draw "two" buttons.
	ZafPaletteStruct textPalette = LogicalPalette(ZAF_PM_BACKGROUND, state);
	ZafRegionStruct draw = drawRegion;

	// Draw right arrow.
	draw.left = middle + 1;
	DrawShadow(draw, scrollState == ZAF_SCROLL_RIGHT ? -1 : depth, ccode);
	display->SetPalette(textPalette);
	display->Rectangle(draw, 0, true);
	draw = drawRegion;
	draw.left = middle + 1;
	int width = draw.Width();
	int height = draw.Height();
	int bitmapWidth = scrollRightArrowBitmap->Width();
	int bitmapHeight = scrollRightArrowBitmap->Height();
	draw.left += width / 2 - bitmapWidth / 2;
	draw.top += height / 2 - bitmapHeight / 2;
	display->Bitmap(draw.left, draw.top, *scrollRightArrowBitmap);

	// Draw left arrow.
	draw = drawRegion;
	draw.right = middle;
	DrawShadow(draw, scrollState == ZAF_SCROLL_LEFT ? -1 : depth, ccode);
	display->SetPalette(textPalette);
	display->Rectangle(draw, 0, true);
	draw = drawRegion;
	draw.right = middle;
	width = draw.Width();
	height = draw.Height();
	bitmapWidth = scrollLeftArrowBitmap->Width();
	bitmapHeight = scrollLeftArrowBitmap->Height();
	draw.left += width / 2 - bitmapWidth / 2;
	draw.top += height / 2 - bitmapHeight / 2;
	display->Bitmap(draw.left, draw.top, *scrollLeftArrowBitmap);

	EndDraw();

	return (ccode);
}

ZafEventType ZafNotebookScrollArrows::DrawShadow(ZafRegionStruct &region,
	int depth, ZafEventType ccode)
{
	if (depth < 0)
		return (ZafButton::DrawShadow(region, depth, ccode));

	ZafPaletteStruct lightShadow = LogicalPalette(ZAF_PM_LIGHT_SHADOW, 0);
	ZafPaletteStruct darkShadow = LogicalPalette(ZAF_PM_DARK_SHADOW, 0);
	ZafPaletteStruct whiteShadow = lightShadow;
	ZafPaletteStruct blackShadow = darkShadow;

	// Copy the background colors to the foreground since display->Line() uses
	// the foreground color. The "shadow" palettes have different colors in
	// the foreground and background entries so they can generate two shades
	// of shadow.
	whiteShadow.colorForeground = whiteShadow.colorBackground;
	blackShadow.colorForeground = blackShadow.colorBackground;

	// Draw the shadow pattern.
	display->SetPalette(lightShadow);
	display->Line(region.left, region.top, region.right - 1, region.top);
	display->Line(region.left, region.top, region.left, region.bottom - 1);

	display->SetPalette(whiteShadow);
	display->Line(region.left + 1, region.top + 1, region.right - 1, region.top + 1);
	display->Line(region.left + 1, region.top + 1, region.left + 1, region.bottom - 1);

	display->SetPalette(blackShadow);
	display->Line(region.left, region.bottom, region.right, region.bottom);
	display->Line(region.right, region.top, region.right, region.bottom);

	display->SetPalette(darkShadow);
	display->Line(region.left + 1, region.bottom - 1, region.right - 1, region.bottom - 1);
	display->Line(region.right - 1, region.top + 1, region.right - 1, region.bottom - 1);

	region -= ZAF_BORDER_SIZE;

	return (ccode);
}
#endif

ZafEventType ZafNotebookScrollArrows::Event(const ZafEventStruct &event)
{
 	// Switch on the event type.
	ZafEventType ccode = LogicalEvent(event);
	switch (ccode)
	{
	case S_COMPUTE_SIZE:
		// Suppress, since the tab control already set the size.
		ZafWindowObject::Event(event);
#if defined(ZAF_GRAPHICS_MODE)
		zafRegion.left = zafRegion.right - 20;
#elif defined(ZAF_TEXT_MODE)
		zafRegion.left = zafRegion.right - 1;
#endif
		middle = zafRegion.Width() / 2;
		break;

	case L_BEGIN_SELECT:
	case L_CONTINUE_SELECT:
		{
			ZafPositionStruct mousePosition = event.position;

			ZafScrollState saveState = scrollState;

			int right = zafRegion.right - zafRegion.left;
			int bottom = zafRegion.bottom - zafRegion.top;

			if (mousePosition.line < 0 || mousePosition.line > bottom ||
				mousePosition.column < 0 || mousePosition.column > right)
				scrollState = ZAF_SCROLL_NONE;
			else if (mousePosition.column <= middle)
				scrollState = ZAF_SCROLL_LEFT;
			else
				scrollState = ZAF_SCROLL_RIGHT;

			if (saveState != scrollState)
				Event(S_REDISPLAY);

			ZafScrollStruct scroll;
			if (scrollState == ZAF_SCROLL_LEFT)
			{
				scroll.delta = -1;
				parent->Event(ZafEventStruct(S_HSCROLL, 0, scroll));
			}
			else if (scrollState == ZAF_SCROLL_RIGHT)
			{
				scroll.delta = 1;
				parent->Event(ZafEventStruct(S_HSCROLL, 0, scroll));
			}

			if (ccode == L_BEGIN_SELECT)
			{
				ZafTimer *repeatTimer = new ZafTimer(D_ON, InitialDelay());
				repeatTimer->Add(this, N_INITIAL_DELAY);
				repeatTimer->SetStringID(ZAF_ITEXT("ZafButtonRepeat"));
				eventManager->Add(repeatTimer);
				windowManager->CaptureMouse(this);
			}
		}
		break;

	case L_END_SELECT:
		{
			ZafScrollState saveState = scrollState;
			if (windowManager->MouseEventRoute() == this)
				windowManager->CaptureMouse(ZAF_NULLP(ZafWindowObject));

			ZafDevice *repeatTimer = eventManager->GetObject(ZAF_ITEXT("ZafButtonRepeat"));
			if (repeatTimer)
			{
				eventManager->Subtract(repeatTimer);
				delete repeatTimer;
			}
			scrollState = ZAF_SCROLL_NONE;
			if (saveState != scrollState)
				Event(S_REDISPLAY);
		}
		break;

	case N_INITIAL_DELAY:
	case N_REPEAT_DELAY:
		{
			ZafScrollStruct scroll;
			if (scrollState == ZAF_SCROLL_LEFT)
			{
				scroll.delta = -1;
				parent->Event(ZafEventStruct(S_HSCROLL, 0, scroll));
			}
			else if (scrollState == ZAF_SCROLL_RIGHT)
			{
				scroll.delta = 1;
				parent->Event(ZafEventStruct(S_HSCROLL, 0, scroll));
			}
			if (ccode == N_INITIAL_DELAY)
			{
				ZafTimer *repeatTimer = DynamicPtrCast(eventManager->GetObject(ZAF_ITEXT("ZafButtonRepeat")),
					ZafTimer);
				if (repeatTimer)
				{
					repeatTimer->SetNotifyMessage(N_REPEAT_DELAY);
					repeatTimer->SetInterval(RepeatDelay());
				}
			}
		}
		break;

	default:
		ccode = ZafButton::Event(event);
		break;
	}

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

#if !defined(ZAF_TEXT_MODE)
void ZafNotebookScrollArrows::ImageAllocate(void)
{
	// scrollLeftArrowBitmap
	if (!scrollLeftArrowBitmap)
	{
		ZafI18nData::AddStaticModule(ImageFree);
		scrollLeftArrowBitmap = new ZafBitmapData(_scrollLeftArrow_ZafBitmap);
		scrollLeftArrowBitmap->staticHandle = true;
		scrollLeftArrowBitmap->SetDestroyable(false);

		scrollRightArrowBitmap = new ZafBitmapData(_scrollRightArrow_ZafBitmap);
		scrollRightArrowBitmap->staticHandle = true;
		scrollRightArrowBitmap->SetDestroyable(false);
	}
}

void ZafNotebookScrollArrows::ImageFree(bool globalRequest)
{
	// Destroy the static images.
	if (globalRequest && scrollLeftArrowBitmap)
	{
		scrollLeftArrowBitmap->staticHandle = false;
		delete scrollLeftArrowBitmap;
		scrollLeftArrowBitmap = ZAF_NULLP(ZafBitmapData);
		scrollRightArrowBitmap->staticHandle = false;
		delete scrollRightArrowBitmap;
		scrollRightArrowBitmap = ZAF_NULLP(ZafBitmapData);
	}
}
#else
void ZafNotebookScrollArrows::ImageAllocate(void) {}
void ZafNotebookScrollArrows::ImageFree(bool) {}
#endif

bool ZafNotebookScrollArrows::SetSupportObject(bool setSupportObject)
{
	// Make sure the attribute has changed.
	if (supportObject != setSupportObject)
		supportObject = setSupportObject;

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

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

void ZafNotebookTab::OSInvalidateRegion(ZafRegionStruct *region, bool immediateRedraw, bool)
{
	if (AutomaticUpdate() && Visible() && screenID)
	{
		ZafRegionStruct tRegion = region ? *region : zafRegion;
		tRegion.HzShift(zafRegion.left);
		tRegion.VtShift(zafRegion.top);
		parent->OSInvalidateRegion(&tRegion, immediateRedraw, !SupportObject());
	}
}

