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

#include <w_app.hpp>
#include <z_appevt.hpp>
#include <z_help.hpp>
#include <z_mouse2.hpp>
#include <z_win.hpp>
#include <z_keymap.hpp>

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

ZafWindowManager::~ZafWindowManager(void)
{
//??? Why isn't this in a z_ file?
	// Remove all objects from the window manager.
	while (first)
	{
		ZafWindow *window = DynamicPtrCast(First(), ZafWindow);
		ZafList::Subtract(window);
		if (window->Destroyable())
			delete window;
	}

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

ZafWindowObject *ZafWindowManager::Add(ZafWindowObject *window, ZafWindowObject *position)
{
	// Make sure Add is allowed.
	if (window && !window->screenID)
	{
		ZafWindow *firstWindow = DynamicPtrCast(First(), ZafWindow),
			*newWindow = DynamicPtrCast(window, ZafWindow);

		// Remove any temporary objects if we have a non-temporary window.
		if (!newWindow->Temporary())
		{
			while (firstWindow && firstWindow->Temporary())
			{
				ZafWindowManager::Subtract(firstWindow);
				if (firstWindow->Destroyable())
					delete firstWindow;
				firstWindow = DynamicPtrCast(First(), ZafWindow);
			}
		}

		// Add the window to the list. (Default to first in list, 
		// or in other words the top-most window.)
		ZafList::Add(window, position ? position : First());

		if (GetCapture())
			SendMessage(GetCapture(), WM_CANCELMODE, 0, 0);

		// Create the window with the OS.
		window->Event(S_CREATE);
	}

	return (window);
}

ZafEventType ZafWindowManager::ApplicationEvent(const ZafEventStruct &event)
{
	// Check for zinc events.
	ZafEventType ccode = event.type;
	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) && event.text)
			delete []event.text;
		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:
		if (event.text)
		{
			ZafWindowObject *object = First();
			while (object)
			{
				ZafWindowObject *nextObj = object->Next();
				if (object->StringID() && strcmp(object->StringID(), event.text) == 0)
				{
					ZafEventStruct closeEvent(S_CLOSE);
					closeEvent.windowObject = object;
					Event(closeEvent);
				}
				object = nextObj;
			}

			delete []event.text;
		}
		break;

	case A_RESET_I18N:
		// Reset the i18n data.
		if (event.text)
		{
			ZafI18nData::ResetI18n(event.text);
			delete []event.text;
		}

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

	case A_MINIMIZE_WINDOWS:
		{
		for (ZafWindowObject *object = First(); object; object = object->Next())
		{
			ZafWindow *window = DynamicPtrCast(object, ZafWindow);
			if (window)
				window->SetMinimized(true);
		}
		}
		break;

	case A_RESTORE_WINDOWS:
		{
		for (ZafWindowObject *object = First(); object; object = object->Next())
		{
			ZafWindow *window = DynamicPtrCast(object, ZafWindow);
			if (window)
				window->SetMinimized(false);
		}
		}
		break;

	case A_HELP_CONTEXT:
		if (event.text)
		{
			if (zafHelpSystem)
				zafHelpSystem->DisplayHelp(event.text);
			delete []event.text;
		}
		break;

	// ----- Layout requests -----
//??? MDI
/* START BLOCK COMMENT
**		case A_CASCADE_WINDOWS:
**			// Cascade the application windows.
**			if (First())
**			{
**				ZafEventStruct aEvent(S_SIZE);
**				aEvent.region.left = aEvent.region.top = 0;
**				for (ZafWindowObject *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;
END BLOCK COMMENT */

//??? MDI
/* START BLOCK COMMENT
**		case A_TILE_WINDOWS:
**			// Tile the application windows.
**			if (first)
**			{
**				// 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 count = Count();
**				int width = 0, height = 0;
**				for (int rows = 1; rows <= count && width <= height; rows++)
**				{
**					width = display->columns / ((count + rows - 1) / rows);
**					height = display->lines / rows;
**				}
**	
**				// Resize the children.
**				ZafEventStruct sizeEvent(S_SIZE);
**				sizeEvent.region.left = sizeEvent.region.top = 0;
**				for (ZafWindowObject *object = Last(); object; object = object->Previous())
**				{
**					// Check for off-screen position.
**					if (sizeEvent.region.left + width > display->columns)
**					{
**						sizeEvent.region.left = 0;
**						sizeEvent.region.top += height;
**					}
**	
**					// Reposition the object.
**					sizeEvent.region.right = sizeEvent.region.left + width - 1;
**					sizeEvent.region.bottom = sizeEvent.region.top + height - 1;
**					object->Event(sizeEvent);
**	
**					// Determine the new object position.
**					sizeEvent.region.left += width;
**				}
**			}
**			break;
END BLOCK COMMENT */

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

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

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;

	if (ccode == E_OSEVENT)
		return (ZafWindowManager::OSEvent(event));

	// Process ZAF events.
	switch (ccode)
	{
	case S_CLOSE:
	case S_CLOSE_TEMPORARY:
		{
		// Get a pointer to the window that should be closed.
		ZafWindow *closeWindow;
		ZafWindowObject *object = event.windowObject;
		if (object)
			// The window to be closed was provided with the event.
//??? An S_CLOSE_WINDOW or similar event that makes use of
//??? event.windowObject is needed.
			closeWindow = DynamicPtrCast(object->RootObject(), ZafWindow);
		else
		{
			// Get the first window.
			closeWindow = DynamicPtrCast(First(), ZafWindow);

			// The first non-temporary window should be closed.
			if (ccode == S_CLOSE)
				while (closeWindow && closeWindow->Temporary())
					closeWindow = DynamicPtrCast(closeWindow->Next(), ZafWindow);
		}

		// Make sure it is ok to close the window.
		if (closeWindow && !closeWindow->Locked() &&
			!(ccode == S_CLOSE_TEMPORARY && !closeWindow->Temporary()) &&
			!(screenID == closeWindow->screenID && Event(ZafEventStruct(N_EXIT)) != 0) &&
			closeWindow->Event(ZafEventStruct(N_CLOSE)) == 0)
		{
			if (closeWindow->screenID == screenID)
				PostQuitMessage(0);

			// Close the window and all temporary windows in front of it.
			do
			{
				// Get the previous window.
				ZafWindowObject *prevWindow = closeWindow->Previous();

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

				// Assign the previous window.
				closeWindow = DynamicPtrCast(prevWindow, ZafWindow);
			} while (closeWindow && closeWindow->Temporary());
		}

		// See if there are any windows left.
		if (!first)
			ccode = S_NO_OBJECT;
		}
		break;

	case S_EXIT:
		PostQuitMessage(0);
		ccode = 0;
		break;

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

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

	case L_NEXT_WINDOW:
		{
		// Find the first non-temporary window.
		ZafWindow *firstWindow = DynamicPtrCast(First(), ZafWindow);
		while (firstWindow && firstWindow->Temporary())
			firstWindow = DynamicPtrCast(firstWindow->Next(), ZafWindow);
		}
		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;

	// ----- Base class messages -----
	case S_ADD_OBJECT:
	case S_SUBTRACT_OBJECT:
		ccode = ZafWindow::Event(event);
		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:
		ApplicationEvent(event);
		break;

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

	default:
		// Let first (top-most) window process the event.
		ccode = First() ? First()->Event(event) : S_NO_OBJECT;
		break;
	}

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

ZafRegionStruct ZafWindowManager::MaxRegion(ZafWindowObject *, ZafVtJustify, ZafHzJustify)
{
	// Return the entire region of the display.
//???Regions?
//	ZafRegionStruct displayRegion = { 0, 0, display->columns - 1, display->lines - 1 };
	ZafRegionStruct displayRegion;
	displayRegion.coordinateType = ZAF_PIXEL;
	displayRegion.left = displayRegion.top = 0;
	displayRegion.right = display->columns - 1;
	displayRegion.bottom = display->lines - 1;
	return (displayRegion);
}

// ----- OS Specific Functions ----------------------------------------------
ZafEventType ZafWindowManager::OSEvent(const ZafEventStruct &event)
{
	ZafEventType ccode = event.type;

	UINT message = event.osEvent.message;
	switch (message)
	{
	case WM_TIMER:
		if (event.osEvent.wParam == ZafMSWindowsApp::mouseTimerID)
		{
			// Test if an N_MOUSE_LEAVE event needs to be sent.
			// (If the mouse moves off of the application, this
			//  is the only way to notify the object.)
			if (mouseObject)
			{
				// See if the mouse is capured.
				HWND hwndCapture = GetCapture();
				if (hwndCapture)
				{
					if (hwndCapture == mouseObject->screenID ||
						IsChild(mouseObject->screenID, hwndCapture))
						break;
				}
				else
				{
					POINT mousePoint;
					GetCursorPos(&mousePoint);

					HWND hwndMouse = WindowFromPoint(mousePoint);
					if (hwndMouse == mouseObject->screenID ||
						IsChild(mouseObject->screenID, hwndMouse))
						break;
				}

				// Send the N_MOUSE_LEAVE event.
				mouseObject->Event(ZafEventStruct(N_MOUSE_LEAVE));
				mouseObject = ZAF_NULLP(ZafWindowObject);
			}

			// Free the mouse timer.
			KillTimer(NULL, ZafMSWindowsApp::mouseTimerID);
			ZafMSWindowsApp::mouseTimerID = 0;
		}
		else
			ccode = DispatchMessage(&event.osEvent);
		break;

	case WM_QUERYENDSESSION:
	case WM_QUIT:
		// See if it is OK to exit the application.
		ccode = (Event(ZafEventStruct(N_EXIT)) == 0) ? S_EXIT : 0;

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

	case WM_KEYDOWN:
	case WM_KEYUP:
		if (dragObject)
		{
			if (event.osEvent.wParam == VK_ESCAPE)
			{
				// Cancel the drag operation.
				dragObject = ZAF_NULLP(ZafWindowObject);
				eventManager->SetDeviceImage(E_MOUSE, DM_VIEW);
			}
			else
			{
				// Generate a WM_MOUSEMOVE message.  This will update
				// the cursor image if the drag operation (move/copy/link)
				// is change by the keypress.
				POINT point;
				GetCursorPos(&point);
				SetCursorPos(point.x, point.y);
			}
			ccode = 0;
			break;
		}
		else
		{
			if (First()->IsA(ID_ZAF_MDI_WINDOW))
			{
				MSG msg = event.osEvent;
				if (TranslateMDISysAccel(First()->screenID, &msg))
					return (0);
			}
			else if (event.osEvent.wParam == VK_ESCAPE)
			{
				if (message == WM_KEYUP && DynamicPtrCast(First(), ZafWindow)->Temporary())
				{
					Event(S_CLOSE_TEMPORARY);
					return (0);
				}

				return (First()->Event(event));
			}

			// Message translation is deferred for Zinc objects because
			// the programmer may intercept and throw away messages. When this
			// happens, translation should not occur.
			// If the user throws away a WM_KEYDOWN message, for example,
			// a WM_CHAR message should not be generated.

			// Only translate messages not destined for Zinc objects.
			// This prevents the generation of WM_CHAR messages when the
			// user intercepts and throws away key-down messages.
			if (IsWindow(event.osEvent.hwnd) &&
				!ZafMSWindowsApp::ObjectFromHandle(event.osEvent.hwnd))
				TranslateMessage(&event.osEvent);
			ccode = DispatchMessage(&event.osEvent);
		}
		break;

	case WM_LBUTTONDOWN:
	case WM_NCLBUTTONDOWN:
		if (ZafMSWindowsApp::dropDownCombo)
		{
#if defined(ZAF_WIN32)
			POINTS points = MAKEPOINTS(event.osEvent.lParam);
			POINT point;
			point.x = points.x;
			point.y = points.y;
#else
			POINT point = MAKEPOINT(event.osEvent.lParam);
#endif
	 		if (message == WM_LBUTTONDOWN)
				ClientToScreen(event.osEvent.hwnd, &point);

			HWND mouseWin = WindowFromPoint(point);
			if (mouseWin != ZafMSWindowsApp::dropDownCombo && mouseWin != GetTopWindow(NULL))
				SendMessage(ZafMSWindowsApp::dropDownCombo, CB_SHOWDROPDOWN, (WPARAM)false, (LPARAM)0);
		}

		ccode = DispatchMessage(&event.osEvent);
		break;

	default:

//???	TranslateMessage(&event.osEvent);

		// Dispatch the Windows message.
		ccode = DispatchMessage(&event.osEvent);
		break;
	}

	return (ccode);
}

ZafWindowObject *ZafWindowManager::Subtract(ZafWindowObject *object)
{
	// Subtract owned windows.
	ZafWindowObject *window = First();
	while (window)
	{
		ZafWindowObject *nextWindow = window->Next();
		ZafWindow *ownedWindow = DynamicPtrCast(window, ZafWindow);
		if (ownedWindow && ownedWindow->Owner() == object)
		{
			Subtract(ownedWindow);
			if (ownedWindow->Destroyable())
				delete ownedWindow;

			// Start with first window again in case "z-order" changed. 
			nextWindow = First();
		}

		window = nextWindow;
	}

	// Re-enable other windows if modal.
	if (DynamicPtrCast(object, ZafWindow)->Modal())
	{
		ZafWindow *window = DynamicPtrCast(object->Next(), ZafWindow);
		while (window)
		{
			EnableWindow(window->OSScreenID(ZAF_FRAMEID), (BOOL)!window->Disabled());
			if (window->Modal())
				break;
			window = DynamicPtrCast(window->Next(), ZafWindow);
		}
	}

	// Subtract the window.
	ZafWindow::Subtract(object);

	// 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);

	if (First())
	{
		// Reset the focus.
		if (!First()->Focus())
			First()->SetFocus(true);
	}
	else
		// Give control loop a chance to return S_NO_OBJECT.
		eventManager->Put(ZafEventStruct(S_CONTINUE));

	return (object);
}

// ----- Stubs --------------------------------------------------------------

ZafPositionStruct ZafWindowManager::ConvertToOSPosition(const ZafWindowObject *object,
	const ZafPositionStruct *zafRegion) const
{
	return ZafWindowObject::ConvertToOSPosition(object, zafRegion);
}

ZafPositionStruct ZafWindowManager::ConvertToZafPosition(const ZafWindowObject *object,
	const ZafPositionStruct *osRegion) const
{
	return ZafWindowObject::ConvertToZafPosition(object, osRegion);
}

ZafRegionStruct ZafWindowManager::ConvertToOSRegion(const ZafWindowObject *object,
	const ZafRegionStruct *region) const
{
	return ZafWindowObject::ConvertToOSRegion(object, region);
}

ZafRegionStruct ZafWindowManager::ConvertToZafRegion(const ZafWindowObject *object,
	const ZafRegionStruct *region) const
{
	return ZafWindowObject::ConvertToZafRegion(object, region);
}

void ZafWindowManager::OSMapPalette(ZafPaletteStruct &palette, ZafPaletteType, ZafPaletteState)
{
	if (palette.colorForeground == ZAF_CLR_DEFAULT && palette.osPalette.colorForeground == 0xFFFFFFFFL)
		palette.osPalette.colorForeground = GetSysColor(COLOR_WINDOWTEXT);
	if (palette.colorBackground == ZAF_CLR_DEFAULT && palette.osPalette.colorBackground == 0xFFFFFFFFL)
#if defined(ZAF_MSWINDOWS_3D)
		palette.osPalette.colorBackground = GetSysColor(COLOR_3DFACE);
#else
		palette.osPalette.colorBackground = GetSysColor(COLOR_WINDOW);
#endif
}



