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

#include <z_win.hpp>
#include <z_help.hpp>
#include <z_dlgwin.hpp>
#include <z_error.hpp>
#include "m_win.hpp"

static Cursor modalCursor = 0;

// ----- ZafWindowManager ---------------------------------------------------

ZafWindowManager::~ZafWindowManager(void)
{
	// Remove all objects from the window manager.
	screenID = 0;
	current = ZAF_NULLP(ZafWindowObject);
	ZafWindow *window = DynamicPtrCast(first, ZafWindow);
	while (window)
	{
		ZafList::Subtract(window);
		window->Event(S_DEINITIALIZE);
		if (window->Destroyable())
			delete window;
		window = DynamicPtrCast(first, ZafWindow);
	}

	// Clean up the static variables.
	if (modalCursor)
	{
		XFreeCursor(((ZafScreenDisplay *)display)->xDisplay, modalCursor);
		modalCursor = 0;
	}

	// Clear the static variables.
	ZafWindowObject::display = ZAF_NULLP(ZafDisplay);
	ZafWindowObject::eventManager = ZAF_NULLP(ZafEventManager);
	ZafWindowObject::windowManager = ZAF_NULLP(ZafWindowManager);
}

ZafWindowObject *ZafWindowManager::Add(ZafWindowObject *object, ZafWindowObject *)
{
	// Check for a null or current object.
	if (!object)
		return (ZAF_NULLP(ZafWindowObject));
	else if (object == First())
		return (object);

	// Add the object to the back of the list.  Motif will automatically
	// call the NotifyFocus() function when the window gains the focus.
	if (ZafList::Index(object) == -1)
		ZafList::Add(object);
	else
	{
		ZafList::Subtract(object);
		ZafList::Add(object);
	}
	object->parent = ZAF_NULLP(ZafWindowObject);

    // Check for a modal window.
    ZafWindow *window = (ZafWindow *)object;
	if (modalWindow && !window->Modal())
		window->SetModal(true);

	// S_CREATE will cause the window to be realized(SetVisible()) and set the focus.
	if (!window->screenID)
		window->Event(S_CREATE);

	if (window->Modal())
	{
		// Set the modal window.
		if (modalWindow)
			modalList.Add(modalWindow);

		modalWindow = window;

		// Check for a modal cursor.
		if (!modalCursor)
			modalCursor = XCreateFontCursor(((ZafScreenDisplay *)display)->xDisplay, XC_dot);

		// Reset the base cursor on all other windows.
		for (ZafWindowObject *temp = First(); temp; temp = temp->Next())
			if (temp != modalWindow && temp->Visible())
				XDefineCursor(((ZafScreenDisplay *)display)->xDisplay, XtWindow(temp->OSScreenID(ZAF_SHELLID)), modalCursor);
	}

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

ZafEventType ZafWindowManager::ApplicationEvent(const ZafEventStruct &event)
{
	// Check for zinc events.
	ZafEventType ccode = event.type;
	ZafIChar *name = event.text;
	switch (ccode)
	{
	// ----- Document requests -----
	case A_OPEN_DOCUMENT:
	case A_PRINT_DOCUMENT:
		// If these are not processed, we must delete event.text.
		ccode = First() ? First()->Event(event) : S_NO_OBJECT;
		if ((ccode == S_UNKNOWN || ccode == S_NO_OBJECT) && name)
			delete []name;
		break;

	// ----- Window requests -----
	case A_OPEN_WINDOW:
		if (event.text)
		{
			ZafIChar *windowName = strrchr(event.text, ZAF_DIRECTORY_SEPARATOR);
			if (windowName)
				windowName++;
			else
				windowName = event.text;
			ZafWindowObject *window = GetObject(windowName);
			if (window && !window->parent)
				window->SetFocus(true);
#if defined(USE_ZAF_PERSISTENCE)
			else if (zafObjectPersistence)
			{
				ZafFileSystem *fileSystem = zafObjectPersistence->CurrentFileSystem();
				ZafIChar *fileName = strrchr(event.text, ZAF_DIRECTORY_SEPARATOR);
				if (fileName)
				{
					*(fileName++) = 0;
					fileSystem->ChDir(event.text);
					ZafFileInfoStruct fileInfo;
					if (fileSystem->FindFirst(fileInfo, fileName) == 0 &&
						fileInfo.stringID)
					{
						ZafObjectConstructor constructor = zafObjectPersistence->GetObjectConstructor((ZafClassID)0, fileInfo.stringID);
						if (constructor)
						{
							zafObjectPersistence->PushLevel(fileInfo.stringID, fileInfo.numberID, ZAF_PERSIST_DIRECTORY);
							Add((ZafWindowObject *)(constructor)(fileInfo.name, *zafObjectPersistence));
							zafObjectPersistence->PopLevel();
						}
					}
				}
			}
#endif
			delete []event.text;
		}
		break;

	case A_CLOSE_WINDOW:
		// Remove the window from the window manager.
		if (name)
		{
			ZafWindowObject *object = First();
			while (object)
			{
				ZafWindowObject *nextObject = object->Next();
				if (object->StringID() && strcmp(object->StringID(), name) == 0)
				{
					ZafEventStruct closeEvent(S_CLOSE);
					closeEvent.windowObject = object;
					ccode = Event(closeEvent);
				}
				object = nextObject;
			}

			delete []name;
		}
		break;

	// ----- Language/Locale requests -----
	case A_RESET_I18N:
		// Reset the i18n data.
		if (name)
		{
			ZafI18nData::ResetI18n(name);
			delete []name;
		}

		// Notify all objects of the i18n change.
		BroadcastEvent(N_RESET_I18N, -1);
		break;

	// ----- Layout requests -----
	case A_MINIMIZE_WINDOWS:
		BroadcastEvent(S_MINIMIZE);
		break;

	case A_RESTORE_WINDOWS:
		BroadcastEvent(S_RESTORE);
		break;

	case A_HELP_CONTEXT:
		// Add the help context.
		if (name && zafHelpSystem)
			zafHelpSystem->DisplayHelp(name);
		// Delete the message text.
		if (name)
			delete []name;
		break;

	// ----- Errors -----
	default:
		ccode = S_ERROR;
	}

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

ZafPositionStruct ZafWindowManager::ConvertToOSPosition(const ZafWindowObject *,
	const ZafPositionStruct *zafPosition) const
{
	ZafPositionStruct osPosition;
	if (zafPosition)
	{
		osPosition.column = zafPosition->column;
		osPosition.line = zafPosition->line;
	}
	else
		osPosition.column = osPosition.line = 0;
	return (osPosition);
}

ZafRegionStruct ZafWindowManager::ConvertToOSRegion(const ZafWindowObject *object,
	const ZafRegionStruct *region) const
{
	// Default to the base window class.
	ZafRegionStruct newRegion = ZafWindowObject::ConvertToOSRegion(object, region);

	// Check for magic Motif placement numbers.
	if (newRegion.left == 0)
		newRegion.HzShift(1);
	if (newRegion.top == 0)
		newRegion.VtShift(1);

	// Return the region.
	return (newRegion);
}

ZafPositionStruct ZafWindowManager::ConvertToZafPosition(const ZafWindowObject *,
	const ZafPositionStruct *osPosition) const
{
	ZafPositionStruct zafPosition;
	if (osPosition)
	{
		zafPosition.column = osPosition->column;
		zafPosition.line = osPosition->line;
	}
	else
		zafPosition.column = zafPosition.line = 0;
	return (zafPosition);
}

ZafRegionStruct ZafWindowManager::ConvertToZafRegion(const ZafWindowObject *object,
	const ZafRegionStruct *region) const
{
	// Default to the base window class.
	ZafRegionStruct newRegion = ZafWindowObject::ConvertToZafRegion(object, region);
	return (newRegion);
}

ZafEventType ZafWindowManager::Event(const ZafEventStruct &event)
{
	// Check for event routing.
	if (event.route)
		return (event.route->Event(event));

	ZafEventType ccode = S_UNKNOWN;
	if (event.InputType() == E_MOUSE && MouseEventRoute())
		ccode = MouseEventRoute()->Event(event);
	else if (DefaultEventRoute())
		ccode = DefaultEventRoute()->Event(event);

	if (ccode != S_UNKNOWN)
		return (ccode);
	else
		ccode = event.type;

	// Check for zinc events.
	ZafWindow *window = (ZafWindow *)first;
	switch (ccode)
	{
	// ----- Position messages -----
	case S_MINIMIZE:
	case S_MAXIMIZE:
	case S_RESTORE:
		ccode = window ? window->Event(event) : S_NO_OBJECT;
		break;

	case S_CLOSE:
	case S_CLOSE_TEMPORARY:
		{ // Begin S_CLOSE case.
		ZafWindow *closeWindow;
		ZafWindowObject *object = event.windowObject;
		if (object)
			closeWindow = DynamicPtrCast(object->RootObject(), ZafWindow);
		else
		{
			closeWindow = DynamicPtrCast(First(), ZafWindow);
			if (ccode == S_CLOSE)
				while(closeWindow && closeWindow->Temporary())
				closeWindow = DynamicPtrCast(closeWindow->Next(), ZafWindow);
		}

		// Check for any modal windows.
		if ((modalWindow && closeWindow != modalWindow) || window->Locked() ||
			closeWindow->Event(ZafEventStruct(N_CLOSE)) != 0)
			return (S_ERROR);

		// Make sure it's ok to close the window.
		if (closeWindow && !closeWindow->Locked() &&
			!(ccode == S_CLOSE_TEMPORARY && !closeWindow->Temporary()))
		{
			// Check to see if there is an exit function.
			if ((screenID == closeWindow->screenID &&
				(ccode = Event(ZafEventStruct(S_EXIT))) == S_EXIT) ||
				ccode == S_ERROR)
				break;

			// There wasn't an exitFunction so continue to close the window.
			// Close the window and all temporary windows in front of it.
			if (first)
			{
				do
				{
					// Get the previous window.
					ZafWindowObject *prevWindow = closeWindow->Previous();

					// Subtract the window.
					Subtract(closeWindow);
					if (closeWindow->Destroyable())
						delete closeWindow;

					// Assign the previous window.
					closeWindow = DynamicPtrCast(prevWindow, ZafWindow);
				}	while(closeWindow && closeWindow->Temporary());
			}
			
			if (!first)
				ccode = S_NO_OBJECT;
		}
		} // End S_CLOSE case.
		break;

	// ----- Activation messages -----
	case S_REDISPLAY:
		// Redisplay all windows.
		BroadcastEvent(event);
		break;

	case S_SET_FOCUS:
		if (event.windowObject)
			event.windowObject->SetFocus(true);
		break;

	// ----- Application events -----
	case A_OPEN_DOCUMENT:
	case A_PRINT_DOCUMENT:
	case A_OPEN_WINDOW:
	case A_CLOSE_WINDOW:
	case A_RESET_I18N:
	case A_MINIMIZE_WINDOWS:
	case A_RESTORE_WINDOWS:
		ccode = ApplicationEvent(event);
		break;

	case N_RESET_I18N:
		BroadcastEvent(event, -1);
		break;

	// ----- Current object messages -----
	case L_ALT_KEY:
	case S_CONTINUE:
		ccode = window ? window->Event(event) : S_NO_OBJECT;
		break;

	// ----- Base class messages -----
	case S_ADD_OBJECT:
		ccode = ZafWindow::Event(event);
		break;

	case S_SUBTRACT_OBJECT:
		ccode = ZafWindow::Event(event);
		if (!First())
			ccode = S_NO_OBJECT;
		break;

	// ----- Exit messages -----
	case S_EXIT:
	case L_EXIT:
		// Check for an exit function.
		ccode = Event(N_EXIT) ? S_ERROR : S_EXIT;

		// Clean up open windows.
		if (ccode == S_EXIT)
		{
			while (First())
			{
				ZafWindow *window = DynamicPtrCast(First(), ZafWindow);
				Subtract(window);
				if (window && window->Destroyable())
					delete window;
			}
		}
		break;

	case N_EXIT:
		// See if it is OK to exit.
		ccode = exitFunction ? (*exitFunction)(display, eventManager, this) : 0;

		// Clear the exit function pointer if exiting, so that the exit
		// function is not called again for the last window subtracted.
		if (ccode == 0)
		  	SetExitFunction(ZAF_NULLF(ZafExitFunction));
		break;

	case L_NEXT_WINDOW:
		// Find the first non-temporary window.
		while (window && window->Temporary())
			window = DynamicPtrCast(window->Next(), ZafWindow);

		// If first window is not modal, bring last window to the front.
		if (!window->Modal())
			Last()->SetFocus(true);
		break;

	// ----- Unknown or error messages -----
	case E_MOTIF:
		if (modalWindow && window != modalWindow &&
			(event.InputType() == E_MOUSE || event.InputType() == E_KEY))
			break;
		else if (window && event.InputType() == E_KEY)
		{
			ccode = window->Event(event);
			if (ccode != S_HOT_KEY)
				XtDispatchEvent((XEvent *)&event.osEvent);
		}
		else
			XtDispatchEvent((XEvent *)&event.osEvent);
		break;

	default:
		ccode = window ? window->Event(event) : S_NO_OBJECT;
		break;
	}

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

ZafRegionStruct ZafWindowManager::MaxRegion(ZafWindowObject *, ZafVtJustify , ZafHzJustify )
{
	ZafRegionStruct region;
	region.left = 0;
	region.top = 0;
	region.right = display->columns - 1;
	region.bottom = display->lines - 1;
	region.coordinateType = ZAF_PIXEL;
	return (region);
}

void ZafWindowManager::OSMapPalette(ZafPaletteStruct &, ZafPaletteType , ZafPaletteState )
{
	// No action is performed at this level.
}

ZafWindowObject *ZafWindowManager::Subtract(ZafWindowObject *object)
{
	// Make sure there is an object to subtract.
	if (!object)
		return (ZAF_NULLP(ZafWindowObject));

	// Check for global entries.
	if (dragObject && dragObject->RootObject() == object)
		dragObject = ZAF_NULLP(ZafWindowObject);
	if (mouseObject && mouseObject->RootObject() == object)
		mouseObject = ZAF_NULLP(ZafWindowObject);
	if (helpObject && helpObject->RootObject() == object)
		helpObject = ZAF_NULLP(ZafWindowObject);

	// Check for a modal window.
	if (modalWindow && modalWindow == object)
	{
		// Remove the window from the modal list.	
		modalList.Subtract(modalWindow);

		// If additional modal windows are in the list reset the 
		// cursor and modal window.
		if (modalList.Last())
		{	
			modalWindow = DynamicPtrCast(modalList.Last()->modalObject,
				ZafWindow);
			XUndefineCursor(((ZafScreenDisplay *)display)->xDisplay, 
				XtWindow(modalWindow->OSScreenID(ZAF_SHELLID)));
		}
        else
		{	
 	       	// Reset the window cursors.
			for (ZafWindowObject *temp = First(); temp; temp = temp->Next())
				if (temp != modalWindow && temp->Visible())
					XUndefineCursor(((ZafScreenDisplay *)display)->xDisplay, 
						XtWindow(temp->OSScreenID(ZAF_SHELLID)));
			modalWindow = ZAF_NULLP(ZafWindow);
		}
	}

	// Remove the object from the list.
	ZafWindowObject *nextObject = object->Next();
	if (ZafList::Index(object) != -1)
	{
		nextObject = object->Next();
		ZafList::Subtract(object);
		modalList.Subtract(object);	
	}
	else
		nextObject = ZAF_NULLP(ZafWindowObject);

	// Destroy the associated motif widget (if any).
	if (object->screenID)
	{
		// Sequence is important here.  The object must be de-initialized
		// before it is destroyed so the object's destroy() callback
		// does not delete the object twice.
		Widget shell = object->OSScreenID(ZAF_SHELLID);
		object->Event(S_DEINITIALIZE);

		XtDestroyWidget(shell);

		// Let the OS finish updating before we move on.
//		ZafProcessXtEvents();
	}

	// Return a pointer to the next object.
	return (nextObject);
}

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

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

// ----- ZafModalList -------------------------------------------------------

ZafWindowManager::ZafModalList::ZafModalElement::ZafModalElement(ZafWindowObject *tModalObject) : 
	modalObject(tModalObject)
{
}

ZafWindowManager::ZafModalList::ZafModalElement::~ZafModalElement(void)
{
}

ZafWindowObject *ZafWindowManager::ZafModalList::Add(ZafWindowObject *object, ZafWindowObject *position)
{
	// Check for a null object.
	if (!object)
		return (ZAF_NULLP(ZafWindowObject));

	ZafModalElement *element;
	
	// If the object is already in the list return.
	for (element = (ZafModalElement *)First(); element; 
		element = (ZafModalElement *)element->Next())
		if (element->modalObject == object)
			return (ZAF_NULLP(ZafWindowObject));

	// Add a new notify element.
	element = new ZafModalElement(object);
	for (ZafModalElement *temp = First(); temp; temp = temp->Next())
		if (temp->modalObject == position)
		{
			ZafList::Add(element, temp);    // position insert.
			return (object);
		}
		ZafList::Add(element);             // compareFunction insert.
		return (object);
}

ZafWindowObject *ZafWindowManager::ZafModalList::Subtract(ZafWindowObject *object)
{
	// Make sure there is an object to subtract.
	if (!object)
		return (ZAF_NULLP(ZafWindowObject));

	// Remove the object from the list.
	for (ZafModalElement *temp = First(); temp; temp = temp->Next())
		if (temp->modalObject == object)
		{
			ZafWindowObject *nextObject = temp->Next() ?
				temp->Next()->modalObject : ZAF_NULLP(ZafWindowObject);
			ZafList::Subtract(temp);
			delete temp;
			return (nextObject);
		}

	// Object is not in the list.
	return (ZAF_NULLP(ZafWindowObject));
}

