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

#include <z_mdiwin.hpp>
#include <z_mouse2.hpp>
#include <z_border.hpp>
#include <z_icon1.hpp>
#include "m_win.hpp"

// ----- MtfMDIWindow -------------------------------------------------------

char mdiFrameTranslations[] =
	"<Btn1Down>:	ZafMDIBeginSelect()\n\
	 <Motion>:		ZafMDIView()";

char mdiClientTranslations[] =
	"<Enter>:		ZafMDIEnter()\n\
	<Motion>:		ZafMDIView()";

void ZafMDIView(Widget widget, XEvent *xEvent, String *, Cardinal *);

void ZafMDIBeginSelect(Widget widget, XEvent *xEvent, String *, Cardinal *)
{
	// Check for a valid object.
	ZafWindowObject *zafObject = ZAF_NULLP(ZafWindowObject);
	XtVaGetValues(widget, XmNuserData, &zafObject, 0);
	if (zafObject->NumberID() == ZAF_NUMID_TITLE ||
		zafObject->NumberID() == ZAF_NUMID_BORDER)
		zafObject = zafObject->RootObject();

	// Determine the object coordinates.
	Widget frame = zafObject->OSScreenID(ZAF_FRAMEID);

	ZafEventStruct event(L_BEGIN_SELECT, xEvent);
	event.position.Assign(xEvent->xbutton.x, xEvent->xbutton.y, ZAF_PIXEL);
	while (widget && widget != frame)
	{
		event.position.column += widget->core.x;
		event.position.line += widget->core.y;
		widget = XtParent(widget);
	}

	zafObject->SetFocus(true);
	zafObject->Modify(event);
}

void ZafMDIEnter(Widget widget, XEvent *xEvent, String *param, Cardinal *nparams)
{
	// Chain to the base class.
	ZafMDIView(widget, xEvent, param, nparams);
	XtCallActionProc(widget, "ManagerEnter", xEvent, param, *nparams);
	if (xEvent->xcrossing.detail != NotifyNonlinearVirtual)
	{
		ZafEventStruct event(N_MOUSE_ENTER, xEvent);
		ZafWindowObject *zafObject = ZAF_NULLP(ZafWindowObject);
		XtVaGetValues(widget, XmNuserData, &zafObject, 0);
		zafObject->Event(event);
	}
}

void ZafMDIView(Widget widget, XEvent *xEvent, String *, Cardinal *)
{
	// Define the cursor images.
	static struct
	{
		ZafDeviceImage image;
		ZafRawCode operation;
	} cursor[] =
	{
		{ DM_MOVE, ZAF_SIZE_LEFT | ZAF_SIZE_TOP | ZAF_SIZE_RIGHT | ZAF_SIZE_BOTTOM },
		{ DM_LEFT_SIDE, ZAF_SIZE_LEFT },
		{ DM_RIGHT_SIDE, ZAF_SIZE_RIGHT },
		{ DM_TOP_SIDE, ZAF_SIZE_TOP },
		{ DM_BOTTOM_SIDE, ZAF_SIZE_BOTTOM },
		{ DM_TOP_LEFT_CORNER, ZAF_SIZE_TOP | ZAF_SIZE_LEFT },
		{ DM_TOP_RIGHT_CORNER, ZAF_SIZE_TOP | ZAF_SIZE_RIGHT },
		{ DM_BOTTOM_LEFT_CORNER, ZAF_SIZE_BOTTOM | ZAF_SIZE_LEFT },
		{ DM_BOTTOM_RIGHT_CORNER, ZAF_SIZE_BOTTOM | ZAF_SIZE_RIGHT },
		{ DM_VIEW, 0 }
	};

	// Get the associated object.
	ZafWindowObject *zafObject;
	Dimension width, height;
	XtVaGetValues(widget,
		XmNuserData, &zafObject,
		XmNwidth, &width,
		XmNheight, &height,
		0);

	// Determine the type of object being selected.
	ZafEventStruct mouseEvent;
	switch (zafObject->NumberID())
	{
	case ZAF_NUMID_MAXIMIZE:
	case ZAF_NUMID_MINIMIZE:
	case ZAF_NUMID_SYSTEM:
		mouseEvent.type = DM_VIEW;
		mouseEvent.screenID = zafObject->parent->OSScreenID(ZAF_FRAMEID);
		zafEventManager->Event(mouseEvent, E_MOUSE);
		return;

	case ZAF_NUMID_TITLE:
		mouseEvent.type = DM_MOVE;
		mouseEvent.screenID = zafObject->OSScreenID(ZAF_FRAMEID);
		zafEventManager->Event(mouseEvent, E_MOUSE);
		return;

	default:
		// Turn off cursor when in child or client area.
		if (zafObject->screenID == widget)
		{
			mouseEvent.type = DM_VIEW;
			mouseEvent.screenID = zafObject->OSScreenID(ZAF_FRAMEID);
			zafEventManager->Event(mouseEvent, E_MOUSE);
			return;
		}
		break;
	}

	// Get the size options.
	const int SIZE_MARGIN = ZAF_BORDER_SIZE + ZAF_SHADOW_SIZE;
	ZafRawCode sizeOperation = 0;
	if (xEvent->xcrossing.x <= SIZE_MARGIN)
		sizeOperation |= ZAF_SIZE_LEFT;
	else if (xEvent->xcrossing.x >= width - SIZE_MARGIN)
		sizeOperation |= ZAF_SIZE_RIGHT;
	if (xEvent->xcrossing.y <= SIZE_MARGIN)
		sizeOperation |= ZAF_SIZE_TOP;
	else if (xEvent->xcrossing.y >= height - SIZE_MARGIN)
		sizeOperation |= ZAF_SIZE_BOTTOM;

	// Find the matching object.
	for (int i = 0; cursor[i].image; i++)
		if (sizeOperation == cursor[i].operation)
		{
			mouseEvent.type = cursor[i].image;
			mouseEvent.screenID = zafObject->OSScreenID(ZAF_FRAMEID);
			zafEventManager->Event(mouseEvent, E_MOUSE);
			return;
		}
}

// ----- ZafMDIWindow -------------------------------------------------------

ZafWindowObject *ZafMDIWindow::Add(ZafWindowObject *object, ZafWindowObject *position)
{
	// Make sure add is allowed.
	if (object->parent)
		return (object);

	// Check for special objects.
	if (mdiType == ZAF_MDI_CHILD && object->SupportObject())
	{
		switch (object->NumberID())
		{
		case ZAF_NUMID_MAXIMIZE:
		case ZAF_NUMID_MINIMIZE:
		case ZAF_NUMID_SYSTEM:
		case ZAF_NUMID_TITLE:
		case ZAF_NUMID_BORDER:
			object->SetSystemObject(true);
			break;
		}
	}

	// Defer to the base class.
	return (ZafWindow::Add(object, position));
}

ZafPositionStruct ZafMDIWindow::ConvertToOSPosition(const ZafWindowObject *object,
	const ZafPositionStruct *zafPosition) const
{
	return (ZafWindow::ConvertToOSPosition(object, zafPosition));
}

ZafPositionStruct ZafMDIWindow::ConvertToZafPosition(const ZafWindowObject *object,
	const ZafPositionStruct *osPosition) const
{
	return (ZafWindow::ConvertToZafPosition(object, osPosition));
}

#if defined(ZAF_DEFAULT_DRAW)
ZafEventType ZafMDIWindow::Draw(const ZafEventStruct &event, ZafEventType ccode)
{
	// Check for invalid conditions.
	if (!screenID || mdiType == ZAF_MDI_PARENT)
		return (ccode);

	// Draw the border, all other fields are automatic.
	for (ZafWindowObject *object = SupportFirst(); object; object = object->Next())
		if (object->NumberID() == ZAF_NUMID_BORDER && !maximized)
			object->Draw(event, ccode);
		else if (object->NumberID() == ZAF_NUMID_MIN_ICON && minimized)
			object->Draw(event, ccode);
		else if (event.type != E_MOTIF)
			object->Redisplay();

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

ZafEventType ZafMDIWindow::Event(const ZafEventStruct &event)
{
	// Check for zinc events.
	ZafEventType ccode = event.type;
	if (event.InputType() == E_KEY)
		ccode = LogicalEvent(event);
	ZafWindowObject *object = First();

	if (ccode == S_INITIALIZE)
	{
		// Force the correct mdiType (for persistence and programmer mistakes).
		if (parent && mdiType == ZAF_MDI_PARENT)
			mdiType = ZAF_MDI_CHILD;
		else if (!parent && mdiType == ZAF_MDI_CHILD)
			mdiType = ZAF_MDI_PARENT;
	}

	if (mdiType == ZAF_MDI_PARENT)
	{
		switch (ccode)
		{
		case S_MDI_CASCADE_WINDOWS:
			// Cascade the child windows.
			if (object)
			{
				ZafEventStruct aEvent(S_SIZE);
				aEvent.region.left = aEvent.region.top = 0;
				for (object = Last(); object; object = object->Previous())
				{
					// Move the window.
					aEvent.region.right = aEvent.region.left + object->zafRegion.Width();
					aEvent.region.bottom = aEvent.region.top + object->zafRegion.Height();
					object->Event(aEvent);

					// Update the region.
					aEvent.region.left += display->cellHeight;
					aEvent.region.top += display->cellHeight;
				}
			}
			break;

		case S_MDI_CLOSE:
			{
			ZafWindow *closeWindow;
			ZafWindowObject *object = event.windowObject;
			if (object && object->parent == this)
				// The window to be closed was provided with the event.
				closeWindow = DynamicPtrCast(object, ZafWindow);
			else
			{
				if (Current()->IsA(ID_ZAF_ICON))
					Current()->Event(S_MDI_MINIMIZE);
				// Get the current Window.
				closeWindow = DynamicPtrCast(Current(), ZafWindow);
			}

			// Make sure it is ok to close the window.
			if (closeWindow && !closeWindow->Locked() &&
 			 (closeWindow->Event(ZafEventStruct(N_CLOSE)) == 0))
			{
				Subtract(closeWindow);
				if (closeWindow->Destroyable())
					delete closeWindow;
			}
			}
			break;

		case S_MDI_TILE_WINDOWS:
			// Tile the child windows.
			if (object)
			{
				// Determine the proper size of each child.  Algorithm keeps
				// computing until the window ratio is 1-to-1 (100% as wide as 
				// it is tall).
				int tCount = Count();
				int width = 0, height = 0;
				for (int rows = 1; rows <= tCount && width <= height; rows++)
				{
					width = clientRegion.Width() / ((tCount + rows - 1) / rows);
					height = clientRegion.Height() / rows;
				}

				// Resize the children.
				ZafEventStruct event(S_SIZE);
				event.region.left = event.region.top = 0;
				for (object = Last(); object; object = object->Previous())
				{
					// Check for off-screen position.
					if (event.region.left + width > clientRegion.Width())
					{
						event.region.left = 0;
						event.region.top += height;
					}

					// Reposition the object.
					event.region.right = event.region.left + width - 1;
					event.region.bottom = event.region.top + height - 1;
					object->Event(event);

					// Determine the new object position.
					event.region.left += width;
				}
			}
			break;

		case S_MDI_MAXIMIZE:
		case S_MDI_MINIMIZE:
		case S_MDI_RESTORE:
			object = Current();
			if (object)
				ccode = object->Event(event);
			break;

		case L_MDI_NEXT_WINDOW:
			{
			unsigned int nchildren;
			Window origin, *children;
			XCirculateSubwindowsDown(XtDisplay(screenID), XtWindow(screenID));
			if (XQueryTree(XtDisplay(screenID), XtWindow(screenID), &origin, &origin, &children, &nchildren))
			{
				XtVaGetValues(XtWindowToWidget(XtDisplay(screenID), children[nchildren-1]), XmNuserData, &object, 0);
				object->SetFocus(true);
				XFree(children);
			}
			}
			break;

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

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

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

		// Set/reset the cursor for special sub-objects.
		for (object = SupportFirst(); object; object = object->Next())
			switch (object->NumberID())
			{
			case ZAF_NUMID_MAXIMIZE:
			case ZAF_NUMID_MINIMIZE:
			case ZAF_NUMID_SYSTEM:
				{
				XtTranslations translationTable = XtParseTranslationTable(mdiClientTranslations);
				XtOverrideTranslations(object->screenID, translationTable);
				}
				break;

			case ZAF_NUMID_TITLE:
				{
				XtTranslations translationTable = XtParseTranslationTable(mdiFrameTranslations);
				XtOverrideTranslations(object->screenID, translationTable);
				}
				break;
			}
		break;

	case S_COMPUTE_SIZE:
		if (maximized)
			zafRegion.Assign(0, 0, parent->screenID->core.width, parent->screenID->core.height);
		ccode = ZafWindow::Event(event);
		break;

	// ----- Position & Size messages -----
	case S_MDI_MINIMIZE:
		{
		ZafIcon *minIcon = MinimizeIcon();
		if (minIcon)
		{
			minimized = !minimized;
			Widget widget = OSScreenID(ZAF_FRAMEID);
			if (minimized)
			{
				XtUnmanageChild(widget);
				minIcon->Event(event);
				widget = minIcon->screenID;
				// Set the minIcon's region.
				ZafWindow *parentWindow = DynamicPtrCast(parent, ZafWindow);
				// Check for overlapping minimized windows.
				ZafRegionStruct boundRegion;
				boundRegion = parentWindow->ClientRegion();
				int width = minIcon->zafRegion.Width();
				int height = minIcon->zafRegion.Height();
				int left = minIcon->zafRegion.left;
				int top = minIcon->zafRegion.top;
				ZafRegionStruct minRegion;
				minRegion.Assign(left, top, width, height);
				// If we overlapped a minicon we need to go through list of windows
				// again to be sure that we didn't move over an icon earlier in the
				// list.
				bool overlapped = true;
				while(overlapped)
				{
					// Assume we won't overlap an Icon.
					overlapped = false;
					ZafWindow *window = DynamicPtrCast(parentWindow->Last(), ZafWindow);
					while (window)
					{
						if (window->Minimized() && window != this && window->MinimizeIcon() &&
						 minRegion.Overlap(window->MinimizeIcon()->zafRegion))
						{
							left = window->MinimizeIcon()->zafRegion.right + 2 * Display()->cellWidth;
							// We have overlapped an Icon go through the list again.
							overlapped = true;						
							if (left + width > boundRegion.right)
							{
								top += (height + Display()->cellHeight);
								minRegion.top = top;
								minRegion.bottom = top + height;
								left = 0;
							}
							minRegion.left = left;
							minRegion.right = left + width;
						}
						window = DynamicPtrCast(window->Previous(), ZafWindow);
					}
				}
				minIcon->SetRegion(minRegion);
				minIcon->Event(S_COMPUTE_SIZE);
				minIcon->OSSize();
				
				XtManageChild(widget);
				XRaiseWindow(XtDisplay(widget), XtWindow(widget));
				minIcon->SetFocus(true);
			}
			else
			{
 				XtManageChild(widget);
				XRaiseWindow(XtDisplay(widget), XtWindow(widget));
				SetFocus(true);
			}
		}
		}
		break;

	case S_MDI_MAXIMIZE:
		{
		ZafEventStruct sEvent(S_SIZE, 0, zafRegion);
		maximized = !maximized;
		if (maximized)
		{
			restoreRegion = zafRegion;
			sEvent.region.Assign(0, 0, parent->screenID->core.width, parent->screenID->core.height);
		}
		else
			sEvent.region = restoreRegion;
		ZafWindowObject::Event(sEvent);
		Draw(S_REDISPLAY, S_REDISPLAY);
		}
		break;

	case S_MDI_RESTORE:
		if (minimized)
			Event(S_MDI_MINIMIZE);
		else if (maximized)
			Event(S_MDI_MAXIMIZE);
		break;

	case S_CURRENT:
	case S_NON_CURRENT:
		// The frame area needs to be redisplayed when the object focus
		// changes.  Redisplay the frame area by assigning frameID to 
		// screenID and then by calling Redisplay().
		ccode = ZafWindow::Event(event);
		Draw(S_REDISPLAY, S_REDISPLAY);
		break;

	case L_NEXT:
		if (Current())
		{
			for (object = Current()->Next(); object; object = object->Next())
				if (!object->Disabled() && !object->Noncurrent())
				{
					object->SetFocus(true);
					return (ccode);
				}
			for (object = First(); object; object = object->Next())
				if (!object->Disabled() && !object->Noncurrent())
				{
					object->SetFocus(true);
					return (ccode);
				}
		}
		break;

	case L_PREVIOUS:
		if (Current())
		{
			for (object = Current()->Previous(); object; object = object->Previous())
				if (!object->Disabled() && !object->Noncurrent())
				{
					object->SetFocus(true);
					return (ccode);
				}
			for (object = Last(); object; object = object->Previous())
				if (!object->Disabled() && !object->Noncurrent())
				{
					object->SetFocus(true);
					return (ccode);
				}
		}
		break;

	// ----- Base class messages -----
	default:
		ccode = ZafWindow::Event(event);
		break;
	}

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

void ZafMDIWindow::GetClip(ZafWindowObject *object, ZafRegionStruct &clip,
	ZafRegionStruct &virtualRegion)
{
	ZafWindow::GetClip(object, clip, virtualRegion);
}

ZafEventType ZafMDIWindow::MoveEvent(const ZafEventStruct &event)
{
	// Check for move events.
	ZafEventType ccode = event.type;
	return (ccode);
}

ZafWindowObject *ZafMDIWindow::NotifyFocus(ZafWindowObject *focusObject, bool setFocus)
{
	// Defer to the base class.
	ZafWindowObject *object = Current();
	ZafWindowObject *invalidObject = ZafWindow::NotifyFocus(focusObject, setFocus);
	focusObject = Current();

	// Check for mdi-child re-ordering.
	if (mdiType == ZAF_MDI_PARENT && focusObject && object != focusObject)
	{
		// Check for mdi-child re-positioning.
		Widget widget = focusObject->OSScreenID(ZAF_FRAMEID);
		XRaiseWindow(XtDisplay(widget), XtWindow(widget));
	}

	// Return the invalid object.
	return (invalidObject);
}

ZafEventType ZafMDIWindow::ScrollEvent(const ZafEventStruct &event)
{
	return (ZafWindow::ScrollEvent(event));
}

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

bool ZafMDIWindow::SetMaximized(bool setMaximized)
{
	// Defer to the base class.
	return (ZafWindow::SetMaximized(setMaximized));
}

bool ZafMDIWindow::SetMinimized(bool setMinimized)
{
	// Defer to the base class.
	return (ZafWindow::SetMinimized(setMinimized));
}

bool ZafMDIWindow::SetVisible(bool setVisible)
{
	// Defer to the base class.
	return (ZafWindowObject::SetVisible(setVisible));
}

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

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

void ZafMDIWindow::OSMapPalette(ZafPaletteStruct &palette, ZafPaletteType type, ZafPaletteState state)
{
	// Defer to the base class.
	ZafWindow::OSMapPalette(palette, type, state);
}

void ZafMDIWindow::OSRegisterObject(void)
{
	// Defer to the base window class.
	ZafWindow::OSRegisterObject();

	// Add edit mode operations.
	static bool initialized = false;
	static XtActionsRec edit_actions[] =
	{
		{ "ZafMDIEnter", ZafMDIEnter },
		{ "ZafMDIBeginSelect", 	ZafMDIBeginSelect },
		{ "ZafMDIView", 		ZafMDIView }
	};
	if (!initialized)
	{
		initialized = true;
		XtAppAddActions(((ZafScreenDisplay *)zafDisplay)->xAppContext, edit_actions, XtNumber(edit_actions));
	}

	// Register MDI child move translations.
	if (mdiType == ZAF_MDI_CHILD)
	{
		// If we don't have a minicon create one.
		if (!MinimizeIcon())
		{
			const ZafIChar *text = Text() ? Text() : ZAF_NULLP(ZafIChar);
			ZafIcon *minIcon = new ZafIcon(0, 0, text, ZAF_NULLP(ZafIconData), ZAF_MINIMIZE_ICON);
			minIcon->SetIconImage(ZAF_APPLICATION_ICON);
			Add(minIcon);
		}

		XtTranslations frameTranslations = XtParseTranslationTable(mdiFrameTranslations);
		XtOverrideTranslations(XtParent(screenID), frameTranslations);

		XtTranslations clientTranslation = XtParseTranslationTable(mdiClientTranslations);
		XtOverrideTranslations(screenID, clientTranslation);
	}
}

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

