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

/**********************************************************************

  YAAF is Copyright 1997-1999 by William Woody.

  Redistribution and use in source and binary forms, with or without 
  modification, are permitted provided that the following conditions 
  are met:

  Redistributions of source code must retain the above copyright notice, 
  this list of conditions and the following disclaimer.

  Neither the name of the copyright holder or other contributors may
  be used to promote products derived from this software without
  specific written prior permission.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 
  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 
  ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 
  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
  SOFTWARE.
  
  Contact:   woody@alumni.caltech.edu
  Web page:  http://www.pandawave.com/yaaf/

**********************************************************************/

#include <XConfig.h>			// must be first to optimize compile on MSVC++

#if OPT_WINOS

#include <XDataUtil.h>
#include <XError.h>
#include <XWindow.h>
#include <XApplication.h>
#include <XMessages.h>
#include <XMenu.h>
#include <XDraw.h>
#include <XInline.h>
#include <XFile.h>

#include <ctype.h>

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

XGView *XGAppCore::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;
				XGDispatch::SendDispatch(KXGKeyEvent,MAKEDWORD(mods,c),NULL);
				return 0;			// always marked 'handled.'
			} else {
				if (wp == VK_TAB) {
					XGDispatch::SendDispatch(KXGKeyEvent,MAKEDWORD(mods,c),NULL);
					return 0;
				}
			}
			return -1;
		case WM_CHAR:
			mods = GetStateFlags();
			return XGDispatch::SendDispatch(KXGKeyEvent,MAKEDWORD(mods,wp),NULL);
		case WM_SYSCHAR:
			mods = GetStateFlags();
			return XGDispatch::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;
	XPoint 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:
			XGInline::Close();
			if (!::IsIconic(tw->GetHWND())) {
				if ((tw != _GWindow) && tw && tw->fWindow) {
					tw->ProcessSizeWindow();
				}
			}
			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 != (XGAppCore::gCaptureView = tw->ProcessMouseDown(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();
				XGAppCore::gCaptureView = NULL;
			}
			
			if (tw) {
				pt.h = LOWORD(lp);
				pt.v = HIWORD(lp);
				tw->ProcessMouseMove(pt,gMods,XGAppCore::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);
				::ReleaseCapture();
				if (tw->ProcessMouseUp(pt,gMods,XGAppCore::gCaptureView)) {
					// get capture back
					::SetCapture(tw->GetHWND());
				}
			}
			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->ProcessDraw(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
				 */
				 
				XGMenuBar *m;
				
				m = XGAppCore::GetApplication()->GetCurMenu();
				if (!m->MenuCommand(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;
	}
}


/************************************************************************/
/*																		*/
/*	DDE Processing														*/
/*																		*/
/************************************************************************/

/*	Compare
 *
 *		Case insensitive compare
 */

static bool Compare(char *a, char *b)
{
	while ((*a) && (toupper(*a) == toupper(*b))) a++, b++;
	if (*a == *b) return true;
	return false;
}

/*	ExecuteDDECommand
 *
 *		Execute one DDE command. Understands 'open', 'print', and 'exit'
 */

static void ExecuteDDECommand(char *cmd, int argc, char **argv)
{
	XGFileSpecifier file;
	
	if (Compare(cmd,"open")) {
		/*
		 *	Open file. Expect one command
		 */
		
		if (argc >= 1) {
			/*
			 *	Open this file
			 */
			
			file = argv[0];
			XGDispatch::SendDispatch(KXGDoOpenDocument,0,(void *)&file);
		}
	} else if (Compare(cmd,"print")) {
		/*
		 *	Print file. Expect one command
		 */
		
		if (argc >= 1) {
			/*
			 *	Print this file
			 */
			
			XGDispatch::SendDispatch(KXGDoPrintDocument,0,(void *)&file);
		}
	} else if (Compare(cmd,"exit")) {
		/*
		 *	Exit application. This only asks to quit.
		 */
		
		XGDispatch::SendDispatch(KXGQuitApplication,0,NULL);
	}
}

/*	ParseDDECommand
 *
 *		Parse a single DDE command 'open(%1)'
 */

static char *ParseDDECommand(char *command)
{
	char buffer[256];
	int d;
	char *c;
	char *argv[20];
	int argc;
	
	/*
	 *	Get just the command string. That is the part up
	 *	to the ']', '(' or eol
	 */
	
	c = command;
	d = 0;
	while ((*c != ']') && (*c != '(') && (*c != 0)) {
		if (d < 255) buffer[d++] = *c;
		c++;
	}
	if (d == 0) return c;
	
	buffer[d] = 0;
	
	/*
	 *	Now get the parameters. We can handle up to 20
	 */
	
	argc = 0;
	if (*c == '(') {
		/*
		 *	Parse arguments.
		 */
		
		c++;
		while ((*c != ')') && (*c != ']') && (*c != 0)) {
			d = 0;
			// quoted string or bare argument?
			if (c[d] == '\"') {
				c++;
				while (c[d] != 0) {
					if (c[d] == '\"') {
						if (c[d+1] == '\"') {
							d += 2;
						} else break;
					} else d++;
				}
				
				if (argc < 20) {
					/*
					 *	Copy string, handling quoted quotes
					 */
					
					char *e;
					int f;
					
					argv[argc] = (char *)malloc(d+1);
					e = argv[argc];
					for (f = 0; f < d; f++) {
						if (c[f] == '\"') f++;
						*e++ = c[f];
					}
					*e = 0;
					argc++;
				}
				
				while ((c[d] != ',') && (c[d] != ')') && 
						(c[d] != ']') && (c[d] != 0)) 
					d++;
				c += d;
			} else {
				// bare argument.
				while ((c[d] != ',') && (c[d] != ')') && 
						(c[d] != ']') && (c[d] != 0)) 
					d++;
				
				if (argc < 20) {
					char *e;
					int f;
					
					argv[argc] = (char *)malloc(d+1);
					e = argv[argc];
					for (f = 0; f < d; f++) {
						*e++ = c[f];
					}
					*e = 0;
					argc++;
				}
				
				c += d;
			}
		}
	}
	
	/*
	 *	Execute this thing
	 */
	
	ExecuteDDECommand(buffer,argc,argv);
	
	/*
	 *	And release memory
	 */
	
	for (d = 0; d < argc; d++) {
		free((void *)argv[d]);
	}
	return c;
}

/*	ExecuteDDE
 *
 *		Exeucte the DDE command list
 */

static void ExecuteDDE(char *command)
{
	char *c;

	c = command;
	while (*c) {
		while ((*c) && (*c != '[')) {
			if (*c == '\"') {
				c++;
				while ((*c) && (*c != '\"')) c++;
			} else {
				c++;
			}
		}
		
		/*
		 *	At start of command. Parse just the command
		 */
		
		c = ParseDDECommand(++c);
	}
}

char _GDDEAppName[256];
char _GDDETopicName[256];

/*	ProcessShellDDE
 *
 *		Processes shell DDE calls. Normally after receving an
 *	'DDE_INITIATE', I would create a separate window to manage that
 *	connection. However, as I only expect commands from the shell,
 *	I'm punting this bit of functionality for simplicity's sake.
 */

static long ProcessShellDDE(HWND w, UINT msg, WPARAM wp, LPARAM lp)
{
	char *command;
	ATOM app,top,a;
	WPARAM hi,lo;
	unsigned short ack;
	char buffer[256];
	
	switch (msg) {
		case WM_DDE_INITIATE:
			/*
			 *	Initiate DDE command
			 */
			
			a = (ATOM)LOWORD(lp);
			if (a == NULL) return 0;
			::GlobalGetAtomName(a,buffer,sizeof(buffer));
			if (::strcmp(_GDDEAppName,buffer)) return 0;
			
			a = (ATOM)HIWORD(lp);
			if (a != NULL) {
				::GlobalGetAtomName(a,buffer,sizeof(buffer));
				if (::strcmp(_GDDETopicName,buffer)) return 0;
			}
			
			/*
			 *	Respond with ACK of only conversation I do
			 */
			
			app = ::GlobalAddAtom(_GDDEAppName);
			top = ::GlobalAddAtom(_GDDETopicName);
			::SendMessage((HWND)wp,WM_DDE_ACK,(WPARAM)w,MAKELPARAM(app,top));
			::GlobalDeleteAtom(app);
			::GlobalDeleteAtom(top);
			
			return 0;
		
		case WM_DDE_TERMINATE:
			/*
			 *	Terminate DDE connection
			 */
			
			::PostMessage((HWND)wp,WM_DDE_TERMINATE,(WPARAM)w,0);
			return 0;
		
		case WM_DDE_EXECUTE:
			/*
			 *	Extract command string
			 */
			
			command = (char *)GlobalLock((HANDLE)lp);
			ExecuteDDE(command);
			::GlobalUnlock((HANDLE)lp);
			
			ack = 1;
			lo = *(WPARAM *)&ack;
			lp = PackDDElParam(WM_DDE_ACK,lo,lp);
			PostMessage((HWND)wp,WM_DDE_ACK,(LPARAM)w,lp);
			return 0;
		
		case WM_DDE_UNADVISE:
		case WM_DDE_ADVISE:
		case WM_DDE_DATA:
		case WM_DDE_POKE:
		case WM_DDE_REQUEST:
			/*
			 *	Don't do 'em, don't ack 'em!
			 */
			
			UnpackDDElParam(msg,lp,&lo,&hi);
			if ((msg == WM_DDE_ADVISE) || (msg == WM_DDE_DATA) || 
					(msg == WM_DDE_POKE)) {
				::GlobalFree((HANDLE)lo);
			}
			ack = 0;
			lo = *(WPARAM *)(&ack);
			ReuseDDElParam(lp,msg,WM_DDE_ACK,lo,hi);
			PostMessage((HWND)wp,WM_DDE_ACK,(WPARAM)w,lp);
			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 = ProcessShellDDE(w,msg,wp,lp))) return retVal;
		if (-1 != (retVal = XGAppCore::ProcXGWindowCommon(tw,msg,wp,lp))) return retVal;
//		if (-1 != (retVal = XGAppCore::ProcKeyboardCommon(msg,wp,lp))) return retVal;
		
		/*
		 *	Check for the activate message
		 */
		
		if ((_GUpActive) && (_GUpActive == msg)) {
			::SetWindowPos(w,HWND_TOP,0,0,0,0,SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
			return 0;
		}
		
		/*
		 *	Now dispatch messages specific to me
		 */
		
		switch (msg) {
			/*	WM_MOVING, WM_MOVE, WM_SIZING, WM_SIZE
			 *
			 *		These events cause any inline editing to be closed.
			 */
			
			case WM_MOVING:
			case WM_MOVE:
			case WM_SIZING:
			case WM_SIZE:
				XGInline::Close();
				break;

			/*	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->ProcessActivate(true);
				} else {
					if (tw && tw->fWindow) tw->ProcessActivate(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;
}

/************************************************************************/
/*																		*/
/*	Toolbar callback													*/
/*																		*/
/************************************************************************/

/*	_GToolBarWindowProc
 *
 *		This handles the toolbar window by rerouting stuff to the
 *	view. This is a pretty stripped down version of this thing, and is
 *	a bit of a kludge as this depends on the toolbar view being structured
 *	as a single view.
 */

LRESULT CALLBACK _GToolBarWindowProc(HWND w, UINT msg, WPARAM wp, LPARAM lp)
{
	PAINTSTRUCT ps;
	static short gMods;
	XPoint pt;
	XRect r,t;
	
	try {
		switch (msg) {
			case WM_CREATE:
				_GToolView->SetRootWindow(w);
				::GetClientRect(w,&r);
				::OffsetRect(&r,-r.left,-r.top);
				if (r.right < 4096) r.right = 4096;	// kludge to eliminate flicker
				_GToolView->SetLocation(r);
				return 0;
			case WM_DESTROY:
				delete _GToolView;
				_GToolWindow = NULL;
				return 0;
				
			/*
			 *	Redirect commands
			 */
			
			/*	WM_MOVING, WM_MOVE, WM_SIZING, WM_SIZE
			 *
			 *		These events cause any inline editing to be closed.
			 */
			
			case WM_MOVING:
			case WM_MOVE:
			case WM_SIZING:
				XGInline::Close();
				break;


			case WM_PAINT:
				::BeginPaint(w,&ps);
				XGDraw::UpdateRect(&ps.rcPaint);
				_GToolView->ProcessDraw(ps.rcPaint);
				XGDraw::UpdateRect();
				::EndPaint(w,&ps);
				return 0;
				
			case WM_LBUTTONDOWN:
			case WM_RBUTTONDOWN:
			case WM_MBUTTONDOWN:
			case WM_LBUTTONDBLCLK:
			case WM_RBUTTONDBLCLK:
			case WM_MBUTTONDBLCLK:
				if (GetCapture() != NULL) return 0;
				
				gMods = XGAppCore::GetStateFlags();				
				if ((msg == WM_LBUTTONDBLCLK) ||
						(msg == WM_RBUTTONDBLCLK) ||
						(msg == WM_MBUTTONDBLCLK))
					gMods |= KDoubleClick;
				
				pt.h = LOWORD(lp);
				pt.v = HIWORD(lp);

				XGWindow::ProcessMouseEnter(pt,gMods,_GToolView);
				if (_GToolView->DoMouseDown(pt,gMods)) {
					XGAppCore::gCaptureView = _GToolView;
					::SetCapture(w);
				}
				return 0;
			
			case WM_MOUSEMOVE:
				if (w == ::GetCapture()) {
					gMods |= KCapturedMouse;
				} else {
					gMods = XGAppCore::GetStateFlags();
					XGAppCore::gCaptureView = NULL;
				}
				
				if (_GToolView) {
					pt.h = LOWORD(lp);
					pt.v = HIWORD(lp);
					
					XGWindow::ProcessMouseEnter(pt,gMods,_GToolView);
					if (XGAppCore::gCaptureView) XGAppCore::gCaptureView->DoMouseMove(pt,gMods);
					else _GToolView->DoMouseMove(pt,gMods);
				}
				return 0;
			
			case WM_LBUTTONUP:
			case WM_MBUTTONUP:
			case WM_RBUTTONUP:
				if (w == ::GetCapture()) {
					gMods |= KCapturedMouse;
					pt.h = LOWORD(lp);
					pt.v = HIWORD(lp);
					
					/*
					 *	We don't have an XGWindow to wrap this in, so I
					 *	have to emulate the stuff in XGWindow::ProcessMouseUp
					 */
					
					::ReleaseCapture();
					XGWindow::gHoldCapture = false;
					if (XGAppCore::gCaptureView) XGAppCore::gCaptureView->DoMouseUp(pt,gMods);
					else _GToolView->DoMouseUp(pt,gMods);
					if (XGWindow::gHoldCapture) {
						::SetCapture(w);
					}
				}
				return 0;
				
			case WM_SIZE:
				XGInline::Close();

				::GetClientRect(w,&r);
				::OffsetRect(&r,-r.left,-r.top);
				if (r.right < 4096) r.right = 4096;	// kludge to eliminate flicker
				t = _GToolView->GetContentRect();
				if (!::EqualRect(&t,&r)) _GToolView->SetLocation(r);
				return 0;
				
			default:
				return ::DefWindowProc(w,msg,wp,lp);
		}
	}
	catch (...) {
	}
	
	return -1;
}


/************************************************************************/
/*																		*/
/*	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 = ProcessShellDDE(w,msg,wp,lp))) return retVal;
//		if (-1 != (retVal = XGAppCore::ProcKeyboardCommon(msg,wp,lp))) return retVal;
		
		/*
		 *	Check for the activate message
		 */
		
		if ((_GUpActive) && (_GUpActive == msg)) {
			::SetWindowPos(w,HWND_TOP,0,0,0,0,SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
			return 0;
		}

		/*
		 *	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 | MDIS_ALLCHILDSTYLES,
										     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:
				XGDispatch::SendDispatch(KXGQuitApplication,0,NULL);
				return 0;
				
			/*	WM_QUERYENDSESSION:
			 *
			 *		Close session
			 */
			
			case WM_QUERYENDSESSION:
				ret = XGDispatch::SendDispatch(KXGQuitApplication,0,NULL);
				if (ret == 0) return TRUE;		// success
				else return FALSE;				// failure
			
			
			/*	WM_MOVING, WM_MOVE, WM_SIZING, WM_SIZE
			 *
			 *		These events cause any inline editing to be closed.
			 */
			
			case WM_MOVING:
			case WM_MOVE:
			case WM_SIZING:
				XGInline::Close();
				break;

			/*
			 *	WM_SIZE: Resize event
			 */
			
			case WM_SIZE:
				XGInline::Close();
				::GetClientRect(w,&r);
				if (_GToolWindow) {
					::MoveWindow(_GToolWindow,r.left,r.top,r.right-r.left,27,TRUE);
					r.top += 27;
				}
				::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 != XGDispatch::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 MDI child 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_NCCREATE
			 *
			 *		Non-client construction; this sets the attributes
			 *	appropriately for toolbar windows
			 */
			
			case WM_NCCREATE:
				if ((tw->fWindowKind & KMaskType) == KWinFloating) {
					::SetWindowLong(w,GWL_EXSTYLE,WS_EX_TOOLWINDOW | WS_EX_TOPMOST);
				}
				break;

			/*	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->ProcessActivate(true);
				} else {
					if (tw && tw->fWindow) tw->ProcessActivate(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);
				}

			/*	WM_MOVING, WM_MOVE, WM_SIZING, WM_SIZE
			 *
			 *		These events cause any inline editing to be closed.
			 */
			
			case WM_MOVING:
			case WM_MOVE:
			case WM_SIZING:
			case WM_SIZE:
				XGInline::Close();
				break;

			/* this is the older implementation of drag and drop on
			 * windows.  It only supports files, so the new implementation
			 * use OLE-COM.
			case WM_DROPFILES:
				XGBroadcast::SendBroadcast('drop', wp, tw->GetHWND());
				DragFinish((HANDLE)wp);
				return(0);
			 */

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

#endif
