/*	WindowProc.cpp
 *
 *		Window procedures for my application core
 */

/*  YAAF - Yet another application framework
 *  Copyright (C) 1997, 1998 William Edward Woody
 *  
 *  This library is free software; you can redistribute it
 *  and/or modify it under the terms of the GNU Library
 *  General Public License as published by the Free Software
 *  Foundation; either version 2 of the License, or any
 *  later version.
 *  
 *  This library is distributed in the hope that it will be
 *  useful, but WITHOUT ANY WARRANTY; without even the implied
 *  warranty of MERCHANTABIILITY or FITNESS FOR A PARTICULAR
 *  PURPOSE. See the GNU Library General Public License for
 *  more details.
 *  
 *  You should have received a copy of the GNU Library General
 *  Public License along with this library; if not, write to the
 *  Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 *  Boston, MA 02111-1307, USA.
 *  
 *  To contact the author, either e-mail me at woody@pandawave.com, 
 *	or write to
 *  
 *          William Edward Woody
 *          In Phase Consulting
 *          1545 Ard Eevin Avenue
 *          Glendale, CA 91202
 *
 *	Or visit us on the web at http://www.pandawave.com
 */

#include <XConfig.h>			// must be first to optimize compile on MSVC++
#include <XDataUtil.h>
#include <XError.h>
#include <XWindow.h>
#include <XApplication.h>
#include <XMessages.h>
#include <XMenu.h>
#include <XDraw.h>

/************************************************************************/
/*																		*/
/*	Globals																*/
/*																		*/
/************************************************************************/

static XGView *GCaptureView;

/************************************************************************/
/*																		*/
/*	Keyboard support													*/
/*																		*/
/************************************************************************/

/*	GetExtendedKeyCode
 *
 *		This returns the extended key code for the KeyCommand function.
 *	If the key event is not an extended key code key, this returns -1
 */

short XGAppCore::GetExtendedKeyCode(WPARAM wp, LPARAM)
{
	switch (wp) {
		case VK_UP:
			return KKeyUpCursor;
		case VK_DOWN:
			return KKeyDownCursor;
		case VK_LEFT:
			return KKeyLeftCursor;
		case VK_RIGHT:
			return KKeyRightCursor;
		case VK_INSERT:
			return KKeyInsert;
		case VK_DELETE:
			return KKeyDelete;
		case VK_HOME:
			return KKeyHome;
		case VK_END:
			return KKeyEnd;
		case VK_PRIOR:
			return KKeyPageUp;
		case VK_NEXT:
			return KKeyPageDown;
		case VK_ESCAPE:
			return KKeyEscape;
		case VK_HELP:
			return KKeyHelp;
		case VK_NUMLOCK:
			return KKeyNumLock;
		case VK_F1:
			return KKeyF1;
		case VK_F2:
			return KKeyF2;
		case VK_F3:
			return KKeyF3;
		case VK_F4:
			return KKeyF4;
		case VK_F5:
			return KKeyF5;
		case VK_F6:
			return KKeyF6;
		case VK_F7:
			return KKeyF7;
		case VK_F8:
			return KKeyF8;
		case VK_F9:
			return KKeyF9;
		case VK_F10:
			return KKeyF10;
		case VK_F11:
			return KKeyF11;
		case VK_F12:
			return KKeyF12;
		case VK_F13:
			return KKeyF13;
		case VK_F14:
			return KKeyF14;
		case VK_F15:
			return KKeyF15;
		default:
			return -1;
	}
}

/************************************************************************/
/*																		*/
/*	Common Callback Support												*/
/*																		*/
/************************************************************************/

/*	XGAppCore::GetStateFlags
 *
 *		Get the keyboard state and return the flags
 */

short XGAppCore::GetStateFlags()
{
	short mods;
	
	mods = 0;
	if (::GetKeyState(VK_SHIFT) & (~1)) 	mods |= KKeyShift;
	if (::GetKeyState(VK_CONTROL) & (~1)) 	mods |= KKeyControl;
	if (::GetKeyState(VK_MENU) & (~1)) 		mods |= KKeyMenu;
	if (::GetKeyState(VK_LBUTTON) & (~1)) 	mods |= KKeyButton;
	if (::GetKeyState(VK_RBUTTON) & (~1))	mods |= KKeyRButton;
	if (::GetKeyState(VK_MBUTTON) & (~1))	mods |= KKeyMButton;
	
	gLastMods = mods;
	return mods;
}


/*	XGAppCore::ProcKeyboardCommon
 *
 *		Process keyboard events. It doesn't matter who has the Windows
 *	Focus, as I'm going to route these through my own event structure
 */

long XGAppCore::ProcKeyboardCommon(UINT msg, WPARAM wp, LPARAM lp)
{
	short mods;
	short c;
	
	switch (msg) {
		case WM_KEYDOWN:
			mods = GetStateFlags();
			c = GetExtendedKeyCode(wp,lp);
			if (c != -1) {
				mods |= KSpecialKey;
				XGAppCore::GetApplication()->
						GetCurFocus()->
						SendDispatch(KXGKeyEvent,MAKEDWORD(mods,c),NULL);
				return 0;			// always marked 'handled.'
			}
			return -1;
		case WM_CHAR:
			mods = GetStateFlags();
			return XGAppCore::GetApplication()->
					GetCurFocus()->
					SendDispatch(KXGKeyEvent,MAKEDWORD(mods,wp),NULL);
		case WM_SYSCHAR:
			mods = GetStateFlags();
			return XGAppCore::GetApplication()->
					GetCurFocus()->
					SendDispatch(KXGMenuKey,MAKEDWORD(mods,wp),NULL);
		default:
			return -1;
	}
}

/*	XGAppCore::ProcXGWindowCommon
 *
 *		Handle the common stuff for XGWindow applications
 */

long XGAppCore::ProcXGWindowCommon(XGWindow *tw, UINT msg, WPARAM wp, LPARAM lp)
{
	MINMAXINFO *mm;
	Point pt;
	static short gMods;
	PAINTSTRUCT ps;
	HWND control;
	
	switch (msg) {
		/*	WM_DESTROY
		 *
		 *		Destruction. There are two ways a window can
		 *	be destroyed--either through the destructor of the
		 *	XGWindow pointer, or through calling the WM_DESTROY
		 *	message. This handles both cases by assuming the
		 *	destruction happens in the XGWindow destructor.
		 *
		 *		By doing it this way, we don't care how in what
		 *	order things are
		 */
		
		case WM_DESTROY:
			if (tw) {
				/*
				 *	If this is non-NULL, we're still associated with
				 *	the window. (_GWindow is only set during
				 *	construction.)
				 */
				
				tw->fWindow = NULL;
				delete tw;
			}
			return 0;
		
		/*	WM_GETMINMAXINFO
		 *
		 *		Get the min/max info for this thing. The construction
		 *	order of my window allows me to handle this call even
		 *	though WM_CREATE hasn't been called yet...
		 */
		
		case WM_GETMINMAXINFO:
			mm = (MINMAXINFO *)lp;
			if (tw) {
				pt = tw->MinWindowSize();
				XGWindow::ContentToSize(&pt);
				if (pt.h > 0) mm->ptMinTrackSize.x = pt.h;
				if (pt.v > 0) mm->ptMinTrackSize.y = pt.v;
				pt = tw->MaxWindowSize();
				XGWindow::ContentToSize(&pt);
				if (pt.h > 0) mm->ptMaxTrackSize.x = pt.h;
				if (pt.v > 0) mm->ptMaxTrackSize.y = pt.v;
				pt = tw->ZoomWindowSize();
				XGWindow::ContentToSize(&pt);
				if (pt.h > 0) mm->ptMaxSize.x = pt.h;
				if (pt.v > 0) mm->ptMaxSize.y = pt.v;
			} else {
				mm->ptMinTrackSize.x = 150;
				mm->ptMinTrackSize.y = 100;
			}
			return -1;
			
		/*	WM_SIZE
		 *
		 *		Handle the reisze
		 */
		
		case WM_SIZE:
			if (!::IsIconic(tw->GetHWND())) {
				if ((tw != _GWindow) && tw && tw->fWindow) {
					tw->DoSizeWindow();
				}
			}
			return -1;				// -1 indicates passing message through
			
		/*	WM_CTLCOLORBTN
		 *
		 *		This returns the face color, making sure that the
		 *	background of a button matches the background of a dialog box
		 */
		
		case WM_CTLCOLORBTN:
			::SetBkColor((HDC)wp,::GetSysColor(COLOR_BTNFACE));
			return (long)::GetSysColorBrush(COLOR_BTNFACE);

		/*	WM_NCACTIVATE
		 *
		 *		Handle activation/deactivation
		 */
		
		case WM_NCACTIVATE:
			if (!wp) {
				if (XGPopWindow::GetPopCount()) {
					return FALSE;
				}
			}
			return -1;

		/*	WM_LBUTTONDOWN et al
		 *
		 *		Mouse click processing
		 */
		
		case WM_LBUTTONDBLCLK:
		case WM_RBUTTONDBLCLK:
		case WM_MBUTTONDBLCLK:
		case WM_LBUTTONDOWN:
		case WM_RBUTTONDOWN:
		case WM_MBUTTONDOWN:
			if (tw->GetHWND() == GetCapture()) return 0;
			
			gMods = GetStateFlags();				
			if ((msg == WM_LBUTTONDBLCLK) ||
					(msg == WM_RBUTTONDBLCLK) ||
					(msg == WM_MBUTTONDBLCLK))
				gMods |= KDoubleClick;
			
			pt.h = LOWORD(lp);
			pt.v = HIWORD(lp);
			if (tw) {
				if (NULL != (GCaptureView = tw->DoMouseDown(pt,gMods))) {
					::SetCapture(tw->GetHWND());
				}
			}
			return 0;
			
		/*	WM_MOUSEMOVE:
		 *
		 *		Handle mouse move events
		 */
		 
		case WM_MOUSEMOVE:
			if (tw->GetHWND() == ::GetCapture()) {
				gMods |= KCapturedMouse;
			} else {
				gMods = GetStateFlags();
				GCaptureView = NULL;
			}
			
			if (tw) {
				pt.h = LOWORD(lp);
				pt.v = HIWORD(lp);
				tw->DoMouseMove(pt,gMods,GCaptureView);
			}
			return 0;
		
		/*	WM_LBUTTONUP et al
		 *
		 *		The mouse up event
		 */
		
		case WM_LBUTTONUP:
		case WM_MBUTTONUP:
		case WM_RBUTTONUP:
			if (tw->GetHWND() == ::GetCapture()) {
				gMods |= KCapturedMouse;
				pt.h = LOWORD(lp);
				pt.v = HIWORD(lp);
				tw->DoMouseUp(pt,gMods,GCaptureView);
				::ReleaseCapture();
			}
			return 0;
			
		case WM_ERASEBKGND:
			return TRUE;				// don't erase background...
		
		/*	WM_PAINT
		 *
		 *		Handle display update
		 */
		
		case WM_PAINT:
			if (::IsIconic(tw->GetHWND())) {
				return -1;
			} else {
				/*
				 *	Draw the contents
				 */
				
				::BeginPaint(tw->GetHWND(),&ps);
				if (tw) {
					XGDraw::UpdateRect(&ps.rcPaint);
					tw->DoDraw(ps.rcPaint);
					XGDraw::UpdateRect();
				}
				::EndPaint(tw->GetHWND(),&ps);
			}
			return 0;
			
		/*	WM_COMMAND
		 *
		 *		Menu processing
		 */
		
		case WM_COMMAND:
			control = (HWND)lp;
			if (control) {
				/*
				 *	This is a control message. Simply package the
				 *	message and punt it up to the owner view
				 */
				
				XGView *v;
				
				v = (XGView *)::GetProp(control,_GControlClass);
				if (v) {
					XGSWInternalRecord i;
					
					i.w = tw->GetHWND();
					i.msg = msg;
					i.wp = wp;
					i.lp = lp;
					return v->DoDispatch(KXGWInternal,0,(void *)&i);
				}
			} else {
				/*
				 *	Menu dispatch
				 */
				 
				XGFocus *f;
				XGMenuBar *m;
				
				f = XGAppCore::GetApplication()->GetCurFocus();
				m = XGAppCore::GetApplication()->GetCurMenu();
				if (!m->MenuCommand(f,LOWORD(wp))) return -1;
			}
			return 0;
		
		/*	WM_HSCROLL, WM_VSCROLL
		 *
		 *		Handle scroll bar manipulation
		 */
		
		case WM_HSCROLL:
		case WM_VSCROLL:
			control = (HWND)lp;
			if (control) {
				XGView *v;
				
				v = (XGView *)::GetProp(control,_GControlClass);
				if (v) {
					XGSWInternalRecord i;
					
					i.w = tw->GetHWND();
					i.msg = msg;
					i.wp = wp;
					i.lp = lp;
					return v->DoDispatch(KXGWInternal,0,(void *)&i);
				}
			}
			return 0;

		default:
			return -1;
	}
}


/************************************************************************/
/*																		*/
/*	SDI framework callbacks												*/
/*																		*/
/************************************************************************/

/*	_GSingleWindowProc
 *
 *		The Microsoft Windows callback procedure for single window apps
 */

LRESULT CALLBACK _GSingleWindowProc(HWND w, UINT msg, WPARAM wp, LPARAM lp)
{
	XGWindow *tw;
	long retVal;
	
	try {
		/*
		 *	First, try to get the XGWindow pointer for this thing.
		 */
		
		tw = XGWindow::GetXGWindow(w);
		if (tw == NULL) {
			tw = _GWindow;
			if (tw == NULL) return ::DefWindowProc(w,msg,wp,lp);
		}
		XGEventOwner owner(tw);
		
		/*
		 *	Now dispatch the message. If a return value is given,
		 *	return immediately
		 */
		
		if (-1 != (retVal = XGAppCore::ProcXGWindowCommon(tw,msg,wp,lp))) return retVal;
//		if (-1 != (retVal = XGAppCore::ProcKeyboardCommon(msg,wp,lp))) return retVal;
		
		/*
		 *	Now dispatch messages specific to me
		 */
		
		switch (msg) {
			/*	WM_CREATE
			 *
			 *		Construction. This associates my HWND with my
			 *	XGWindow pointer
			 */
			
			case WM_CREATE:
				::SetProp(w,_GWindowClass,(HANDLE)tw);
				tw->fWindow = w;
				return 0;

			/*	WM_CLOSE
			 *
			 *		Handle closing my window
			 */
			
			case WM_CLOSE:
				if (tw) tw->CloseWindow();
				return 0;
				
			/*	WM_QUERYENDSESSION
			 *
			 *		Handle shutting down the system
			 */
			
			case WM_QUERYENDSESSION:
				if (tw) {
					if (tw->CloseWindow()) return TRUE;
					return FALSE;
				} else return TRUE;
				
			/*	WM_ACTIVATE
			 *
			 *		Handle window activation/deactivation
			 */
			
			case WM_ACTIVATE:
				if (LOWORD(wp) != WA_INACTIVE) {
					if (tw && tw->fWindow) tw->DoActivate(true);
				} else {
					if (tw && tw->fWindow) tw->DoActivate(false);
				}
				return 0;

			default:
				break;
			
			/*	WM_ERASEBKGND
			 *
			 *		Erase the background. This erases the background
			 *	correctly
			 */
			
			case WM_ERASEBKGND:
				if (::IsIconic(tw->GetHWND())) {
					return ::DefWindowProc(tw->GetHWND(),WM_ICONERASEBKGND,wp,lp);
				} else {
					return ::DefWindowProc(tw->GetHWND(),msg,wp,lp);
				}
		}
		
		
		/*
		 *	Now handle default processing
		 */
		
		return ::DefWindowProc(w,msg,wp,lp);
	}
	
	catch (XGError &e) {
		e.DisplayError();
	}
	return 0;
}

/************************************************************************/
/*																		*/
/*	MDI framework callbacks												*/
/*																		*/
/************************************************************************/

/*	_GMainMDIWindowProc
 *
 *		This handles the frame window
 */

LRESULT CALLBACK _GMainMDIWindowProc(HWND w, UINT msg, WPARAM wp, LPARAM lp)
{
	CLIENTCREATESTRUCT ccs;
	RECT r;
//	long retVal;
	short item;
	long ret;
	
	try {
		/*
		 *	Try keyboard processing
		 */
		
//		if (-1 != (retVal = XGAppCore::ProcKeyboardCommon(msg,wp,lp))) return retVal;
		
		/*
		 *	Try the rest
		 */
		
		switch (msg) {
			/*
			 *	WM_CREATE construction
			 */
			
			case WM_CREATE:
				ccs.hWindowMenu = NULL;
				ccs.idFirstChild = 10000;			// high number
				_GMDIWindow = ::CreateWindow("MDIClient","",
										     WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
										     0,0,0,0,w,NULL,_GInstance,(LPSTR)&ccs);
				if (_GMDIWindow == NULL) return -1;	// failure
				::ShowWindow(_GMDIWindow,SW_SHOW);
				return 0;
			
			/*
			 *	WM_DESTROY destruction--kill app the hard way.
			 */
			
			case WM_DESTROY:
				_GMainWindow = NULL;
				::PostQuitMessage(0);
				return 0;
				
			/*
			 *	WM_CLOSE: close main application event
			 */
			
			case WM_CLOSE:
				XGAppCore::GetApplication()->
					GetCurFocus()->
					SendDispatch(KXGQuitApplication,0,NULL);
				return 0;
				
			/*	WM_QUERYENDSESSION:
			 *
			 *		Close session
			 */
			
			case WM_QUERYENDSESSION:
				ret = XGAppCore::GetApplication()->
					GetCurFocus()->
					SendDispatch(KXGQuitApplication,0,NULL);
				if (ret == 0) return TRUE;		// success
				else return FALSE;				// failure
			
			/*
			 *	WM_SIZE: Resize event
			 */
			
			case WM_SIZE:
				::GetClientRect(w,&r);
				::MoveWindow(_GMDIWindow,r.left,r.top,
							 r.right-r.left,r.bottom-r.top,TRUE);
				return 0;
			
			/*
			 *	WM_COMMAND: Process menu command
			 */
			
			case WM_COMMAND:
				item = LOWORD(wp);
				if (-1 != XGAppCore::GetApplication() ->
							GetCurFocus()->
							SendDispatch(KXGDoMenuCommand,item,NULL)) return 0;
				break;
		
			default:
				break;
		}
		
		return DefFrameProc(w,_GMDIWindow,msg,wp,lp);
	}
	
	catch (XGError &e) {
		e.DisplayError();
	}
	return 0;
}

/*	_GChildWindowProc
 *
 *		The Microsoft Windows callback procedure for single window apps
 */

LRESULT CALLBACK _GChildWindowProc(HWND w, UINT msg, WPARAM wp, LPARAM lp)
{
	XGWindow *tw;
	long retVal;
	
	try {
		/*
		 *	First, try to get the XGWindow pointer for this thing.
		 */
		
		tw = XGWindow::GetXGWindow(w);
		if (tw == NULL) {
			tw = _GWindow;
			if (tw == NULL) return ::DefMDIChildProc(w,msg,wp,lp);
		}
		
		/*
		 *	Now dispatch the message. If a return value is given,
		 *	return immediately
		 */
		
		if (-1 != (retVal = XGAppCore::ProcXGWindowCommon(tw,msg,wp,lp))) return retVal;
//		if (-1 != (retVal = XGAppCore::ProcKeyboardCommon(msg,wp,lp))) return retVal;
		
		/*
		 *	Now dispatch messages specific to me
		 */
		
		switch (msg) {
			/*	WM_CREATE
			 *
			 *		Construction. This associates my HWND with my
			 *	XGWindow pointer
			 */
			
			case WM_CREATE:
				::SetProp(w,_GWindowClass,(HANDLE)tw);
				tw->fWindow = w;
				return 0;

			/*	WM_CLOSE
			 *
			 *		Handle closing my window
			 */
			
			case WM_CLOSE:
				if (tw) tw->CloseWindow();
				return 0;
			
			/*	WM_MDIACTIVATE
			 *
			 *		Handle window activation/deactivation
			 */
			
			case WM_MDIACTIVATE:
				if (w == (HWND)lp) {
					if (tw && tw->fWindow) tw->DoActivate(true);
				} else {
					if (tw && tw->fWindow) tw->DoActivate(false);
				}
				return 0;

			/*	WM_ERASEBKGND
			 *
			 *		Erase the background. This erases the background
			 *	correctly
			 */
			
			case WM_ERASEBKGND:
				if (::IsIconic(tw->GetHWND())) {
					return ::DefMDIChildProc(tw->GetHWND(),WM_ICONERASEBKGND,wp,lp);
				} else {
					return ::DefMDIChildProc(tw->GetHWND(),msg,wp,lp);
				}

			default:
				break;
		}
		
		
		/*
		 *	Now handle default processing
		 */
		
		return ::DefMDIChildProc(w,msg,wp,lp);
	}
	
	catch (XGError &e) {
		e.DisplayError();
	}
	return 0;
}
