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

#include <time.h>
#include "i_spin.hpp"
#include <z_app.hpp>
#define USE_RAW_KEYS
#include <z_keymap.hpp>
#include <z_lang.hpp>
#include <z_mouse2.hpp>	// For DM_VIEW.
#include <z_timer.hpp>

// ----- ZafSpinControl ------------------------------------------------------

ZafEventType ZafSpinControl::DispatchMouseEvent(const ZafEventStruct &event)
{
	ZafEventType ccode = LogicalEvent(event);
	if ((ccode == L_BEGIN_SELECT || ccode == L_END_SELECT || ccode == L_CONTINUE_SELECT) &&
		clientRegion.Overlap(event.position))
		return (First()->DispatchMouseEvent(event));
	return (ZafWindowObject::DispatchMouseEvent(event));
}

ZafWindowObject *ZafSpinControl::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_TEXT_MODE)
	position.coordinateType = ZAF_PIXEL;
#else
	position.coordinateType = ZAF_CELL;
#endif
	// Convert mouse position to the coordinates of this object.
	ZafPositionStruct mousePosition = ConvertToZafPosition(this, &position);
	ZafWindowObject *mouseObject = 0;

	if (clientRegion.Overlap(mousePosition))
		mouseObject = (First()->FindMouseObject(event));

	if (!mouseObject)
		mouseObject = ZafWindowObject::FindMouseObject(event);

	return (mouseObject);
}

#if defined(ZAF_DEFAULT_DRAW)
ZafEventType ZafSpinControl::Draw(const ZafEventStruct &event, ZafEventType ccode)
{
	return (ZafWindow::Draw(event, ccode));
}
#endif

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

	// Check for zinc events.
	switch (ccode)
	{
	// ----- Create messages -----
	case S_INITIALIZE:
		if (support.First())
			SupportFirst()->SetSystemObject(SystemObject());
		First()->SetRegionType(ZAF_AVAILABLE_REGION);
		First()->SetBordered(false);
		ccode = ZafWindow::Event(event);
		break;

	case S_DECREMENT:
	case L_DOWN:
		{
		ZafString *string = DynamicPtrCast(First(), ZafString);
		if (string)
			string->Decrement(delta, WrappedData());

		(this->*memberUserFunction)(event, L_SELECT);
		}
		break;

	case S_INCREMENT:
	case L_UP:
		{
		ZafString *string = DynamicPtrCast(First(), ZafString);
		if (string)
			string->Increment(delta, WrappedData());

		(this->*memberUserFunction)(event, L_SELECT);
		}
		break;

	case L_VIEW:
	case L_CONTINUE_SELECT:
//	case L_CONTINUE_COPY_DRAG:
	case L_END_SELECT:
//	case L_END_COPY_DRAG:
		if (editMode || windowManager->dragObject)
			ZafWindowObject::Event(event);
		else
		{
			ZafPositionStruct mousePosition = event.position;
			ZafRegionStruct windowClient = ClientRegion();
			if (windowClient.Overlap(mousePosition))
			{
				// If the mouse is over the client region, convert to the client
				// region's coordinates.
				mousePosition.column -= windowClient.left;
				mousePosition.line -= windowClient.top;
				// Cast away the constness so we can have event.position relative
				// to the client region.
				((ZafEventStruct &)event).position = mousePosition;

				// The spin string is the only possible client object.
				ZafWindowObject *object = First();
				if (object && object->Visible() && object->zafRegion.Overlap(mousePosition))
				{
					// If the string 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 spin control had one).
					if (ccode == L_VIEW && mouseObject != object)
					{
						ZafEventStruct mouseEvent = event;
						mouseEvent.type = N_MOUSE_ENTER;
						object->Event(mouseEvent);
						mouseObject = object;
					}
					else
						ccode = object->Event(event);
				}
			}
			else if (SupportFirst()->zafRegion.Overlap(mousePosition))
			{
				// If the spin arrow 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 spin control had one).
				ZafWindowObject *object = SupportFirst();
		 		if (ccode == L_VIEW && mouseObject != object)
				{
					ZafEventStruct mouseEvent = event;
					mouseEvent.type = N_MOUSE_ENTER;
					object->Event(mouseEvent);
					mouseObject = object;
				}
				else
					ccode = object->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 L_DOUBLE_CLICK:
	case L_BEGIN_SELECT:
		if (Draggable())
			windowManager->dragObject = First();

//		if (!viewOnly && OSDraw())
		if (OSDraw())
		{
			ZafPositionStruct mousePosition = event.position;
			ZafRegionStruct windowClient = ClientRegion();
			if (windowClient.Overlap(mousePosition))
			{
				// If the mouse is over the client region, convert to the client
				// region's coordinates.
				mousePosition.column -= windowClient.left;
				mousePosition.line -= windowClient.top;
				// Cast away the constness so we can have event.position relative
				// to the client region.
				((ZafEventStruct &)event).position = mousePosition;

				// The spin string is the only possible client object.
				ZafWindowObject *object = First();
				if (object && object->Visible() && object->zafRegion.Overlap(mousePosition))
					return (object->DispatchMouseEvent(event));
			}
			else if (SupportFirst()->zafRegion.Overlap(mousePosition))
				return (SupportFirst()->DispatchMouseEvent(event));
			else
				return (ZafWindow::Event(event));
		}
		break;

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

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

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

bool ZafSpinControl::SetAcceptDrop(bool setAcceptDrop)
{
	return (ZafWindowObject::SetAcceptDrop(setAcceptDrop));
}

bool ZafSpinControl::SetDisabled(bool setDisabled)
{
	// Defer to the base class.
	return (ZafWindow::SetDisabled(setDisabled));
}

bool ZafSpinControl::SetVisible(bool tVisible)
{
	return (ZafWindow::SetVisible(tVisible));
}

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

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

void ZafSpinControl::OSMapPalette(ZafPaletteStruct &palette, ZafPaletteType type, ZafPaletteState state)
{
	// Get default colors from the operating system.
/* START BLOCK COMMENT
**		if (type == ZAF_PM_BACKGROUND)
**		{
**			if (palette.colorForeground == ZAF_CLR_DEFAULT && palette.osPalette.colorForeground == 0xFFFFFFFFL)
**				palette.osPalette.colorForeground = GetSysColor(COLOR_WINDOW);
**			if (palette.colorBackground == ZAF_CLR_DEFAULT && palette.osPalette.colorBackground == 0xFFFFFFFFL)
**				palette.osPalette.colorBackground = GetSysColor(COLOR_WINDOW);
**		}
**		else
END BLOCK COMMENT */
		ZafWindow::OSMapPalette(palette, type, state);
}

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

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

void ZafSpinControl::OSSize(void)
{
	ZafWindow::OSSize();
}

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

// ----- ZafSpinButton ---------------------------------------------------

#define ZAF_SPIN_BUTTON_INFO
#include "img_def.cpp"

ZafClassID ZafSpinButton::classID = ID_ZAF_SPIN_BUTTON;
ZafClassNameChar ZAF_FARDATA ZafSpinButton::className[] = ZAF_ITEXT("ZafSpinButton");

char ZAF_FARDATA ZafSpinButton::spinUpArrowName[] = "spinUpArrow";
char ZAF_FARDATA ZafSpinButton::spinDownArrowName[] = "spinDownArrow";

ZafBitmapData *ZafSpinButton::spinUpArrowBitmap = ZAF_NULLP(ZafBitmapData);
ZafBitmapData *ZafSpinButton::spinDownArrowBitmap = ZAF_NULLP(ZafBitmapData);

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

ZafSpinButton::ZafSpinButton(const ZafSpinButton &copy) :
	ZafButton(copy), middle(0), spinState(copy.spinState)
{
}

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

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

	display->SetClipRegion(event.region);

	ZafPaletteStruct textPalette = LogicalPalette(ZAF_PM_TEXT, state);

	ZafRegionStruct draw = drawRegion;

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

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

	EndDraw();

	return (ccode);
}

ZafEventType ZafSpinButton::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 ZafSpinButton::Event(const ZafEventStruct &event)
{
 	// Switch on the event type.
	ZafEventType ccode = LogicalEvent(event);
	switch (ccode)
	{
	case S_COMPUTE_SIZE:
		ZafWindowObject::Event(event);
#if !defined(ZAF_TEXT_MODE)
		zafRegion.left = zafRegion.right - display->cellHeight / 2;
		middle = zafRegion.Height() / 2;
#else
		zafRegion.top = zafRegion.bottom = 0;
		zafRegion.left = zafRegion.right - 1;
		middle = 1;
#endif
		break;

	case L_DOUBLE_CLICK:
	case L_BEGIN_SELECT:
	case L_CONTINUE_SELECT:
		{
			ZafSpinState saveState = spinState;
#if !defined(ZAF_TEXT_MODE)
			int bottom = zafRegion.Height() - 1;
			int right = zafRegion.Width() - 1;
			if (event.position.line < 0 || event.position.line > bottom ||
				event.position.column < 0 || event.position.column > right)
				spinState = ZAF_SPIN_NONE;
			else if (event.position.line <= middle)
				spinState = ZAF_SPIN_UP;
			else
				spinState = ZAF_SPIN_DOWN;
#else
			if (event.position.column < 0 || event.position.column > 1)
				spinState = ZAF_SPIN_NONE;
			else if (event.position.column == middle)
				spinState = ZAF_SPIN_UP;
			else
				spinState = ZAF_SPIN_DOWN;
#endif
			if (saveState != spinState)
				Event(S_REDISPLAY);

			if (spinState == ZAF_SPIN_UP)
				parent->Event(S_INCREMENT);
			else if (spinState == ZAF_SPIN_DOWN)
				parent->Event(S_DECREMENT);

			if (ccode == L_BEGIN_SELECT || ccode == L_DOUBLE_CLICK)
			{
				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:
		{
			ZafSpinState saveState = spinState;
			if (windowManager->MouseEventRoute() == this)
				windowManager->CaptureMouse(ZAF_NULLP(ZafWindowObject));

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

	case N_INITIAL_DELAY:
	case N_REPEAT_DELAY:
		{
			if (spinState == ZAF_SPIN_UP)
				parent->Event(S_INCREMENT);
			else if (spinState == ZAF_SPIN_DOWN)
				parent->Event(S_DECREMENT);
			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);
}

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

		spinDownArrowBitmap = new ZafBitmapData(_spinDownArrow_ZafBitmap);
		spinDownArrowBitmap->staticHandle = true;
		spinDownArrowBitmap->SetDestroyable(false);
	}
#endif
}

void ZafSpinButton::ImageFree(bool globalRequest)
{
#if !defined(ZAF_TEXT_MODE)
	// Destroy the static images.
	if (globalRequest && spinUpArrowBitmap)
	{
		spinUpArrowBitmap->staticHandle = false;
		delete spinUpArrowBitmap;
		spinUpArrowBitmap = ZAF_NULLP(ZafBitmapData);
		spinDownArrowBitmap->staticHandle = false;
		delete spinDownArrowBitmap;
		spinDownArrowBitmap = ZAF_NULLP(ZafBitmapData);
	}
#endif
}

bool ZafSpinButton::IsA(ZafClassID compareID) const
{
	return ((compareID == classID) ? true : ZafButton::IsA(compareID));
}

bool ZafSpinButton::IsA(ZafClassName compareName) const
{
	return (!strcmp(compareName, className) ? true : ZafButton::IsA(compareName));
}

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

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


