/*	XGAppCore_xwin.cpp
 *
 *		X specific implementation of various XGAppCore methods
 */

/*  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 <XApplication.h>
#include <XDraw.h>
#include <XWindow.h>
#include <XMessages.h>
#include <XMenu.h>
#include <XError.h>
#include <XModifiers.h>
#include <XDataUtil.h>

#include <time.h>
#include <sys/times.h>

/************************************************************************/
/*																		*/
/*	Miscellaneous Globals												*/
/*																		*/
/************************************************************************/

extern int					_GArgC;
extern char					**_GArgV;

static XGView				*GCapture;
Atom						_GWMProtocols;
Atom						_GDelWindowAtom;
extern Display				*_GDisplay;
Window						_GEventWindow;			// for cursor util

short						GMenuCommand;			// menu command

/************************************************************************/
/*																		*/
/*	Support																*/
/*																		*/
/************************************************************************/

/*	GetDisplayArgument
 *
 *		Scan the argc/argv list for the -display command argument
 */

char *GetDisplayArgument()
{
	int i;
	
	for (i = 1; i < _GArgC-1; i++) {
		if (!strcmp(_GArgV[i],"-display")) {
			return _GArgV[i+1];
		}
	}
	return NULL;
}

/************************************************************************/
/*																		*/
/*	OS Init/Shutdown													*/
/*																		*/
/************************************************************************/

#pragma mark Initialization and Shutdown

/*	XGAppCore::InitOS
 *
 *		Initialize the OS. I don't need to do very much here...
 */

void XGAppCore::InitOS()
{
	/*
	 *	Initialize my connection to the X server through Xlib
	 */
	 
	_GDisplay = XOpenDisplay(GetDisplayArgument());
	if (_GDisplay == NULL) {
		/*
		 *	Server not up -- punt immediately, no questions asked.
		 */
		
		printf("Unable to connect to X server\n");
		throw XGAbort();			// Throw no-warning abort error
	}
	
#if DEBUG
	XSynchronize(_GDisplay,false);	// debugging
#endif

	/*
	 *	Create my atoms
	 */
	
	_GDelWindowAtom = XInternAtom(_GDisplay,"WM_DELETE_WINDOW",False);
	_GWMProtocols = XInternAtom(_GDisplay,"WM_PROTOCOLS",False);
}

/*	XGAppCore::KillOS
 *
 *		Kill the OS. I don't need to do much here, either...
 */

void XGAppCore::KillOS()
{
	/*
	 *	Shut down my connection
	 */
	
	if (_GDisplay) XCloseDisplay(_GDisplay);
}

/*	XGAppCore::GetAppArgs
 *
 *		Called right before the event pump is run, this parses
 *	the arguments on the command line and passes them to my app
 */

void XGAppCore::GetAppArgs()
{
	int i;
	int count;
	char *n;
	
	/*
	 *	How many files specified?
	 */
	
	count = 0;
	for (i = 1; i < _GArgC; i++) {
		n = _GArgV[i];
		if (*n == '-') i++;
		else count++;
	}
	
	/*
	 *	Send messages around my system
	 */
	
	if (count == 0) {
		GetCurFocus()->SendDispatch(KXGOpenApplication,0,NULL);
	} else {
		for (i = 1; i < _GArgC; i++) {
			n = _GArgV[i];
			if (*n == '-') i++;
			else {
				GetCurFocus()->SendDispatch(KXGOpenDocument,
											0,
											(void *)_GArgV[i]);
			}
		}
	}
	return NULL;
}

/************************************************************************/
/*																		*/
/*	Window Order Support												*/
/*																		*/
/************************************************************************/

/*	GetParentWindow
 *
 *		Given a pointer to a window, this either returns the parent of the
 *	window or None if the parent is the root
 */

Window XGAppCore::GetParentWindow(Window w)
{
	unsigned int nchildren;
	Window *children;
	Window parent;
	Window root;

	::XQueryTree(_GDisplay,
				 w,
				 &root,
				 &parent,
				 &children,
				 &nchildren);

	::XFree((void *)children);
	if (parent == DefaultRootWindow(_GDisplay)) return None;
	else return parent;
}	

/*	ArrangeOrder
 *
 *		Arrange the order of the windows associated with my application.
 *	This reaches out and makes sure my current modal dialog is in front of
 *	my modeless dialogs and document windows. This is frigheningly 
 *	antisocial, but there it is.
 */

void XGAppCore::ArrangeOrder(void)
{
	unsigned int nchildren;
	Window *children;
	Window parent;
	Window root;
	int i;
	XGWindow *wptr;
	unsigned int flag;

	if (fModalWindow == NULL) return;	/* Nothing to arrange... */
		
	/*
	 *	Get the children of the root window
	 */
	
	::XQueryTree(_GDisplay,
				 DefaultRootWindow(_GDisplay),
				 &root,
				 &parent,
				 &children,
				 &nchildren);
	
	/*
	 *	Scan the list for a modal window
	 */
	
	flag = 0;
	for (i = nchildren-1; i >= 0; i--) {
		wptr = XGWindow::GetXGWindowByParent(children[i]);
		if (wptr == NULL) continue;
		if (wptr == fModalWindow) {
			break;
		}
		flag = 1;
	}
	
	/*
	 *	Now see if we've a problem
	 */
	
	if ((i >= 0) && flag) {
		/*
		 *	We have a modal window not on top
		 */
		
		::XRaiseWindow(_GDisplay,children[i]);
	}
	
	::XFree((void *)children);
}

/*	GetSpecialKey
 *
 *		This determines if the keyboard event is a special key. If it is,
 *	this returns the special key code (YAAF key code)
 */

short XGAppCore::GetSpecialKey(KeySym sym)
{
	switch (sym) {
		case XK_KP_Up:
		case XK_Up:
			return KKeyUpCursor;
		case XK_KP_Down:
		case XK_Down:
			return KKeyDownCursor;
		case XK_KP_Left:
		case XK_Left:
			return KKeyLeftCursor;
		case XK_KP_Right:
		case XK_Right:
			return KKeyRightCursor;
		case XK_KP_Insert:
			return KKeyInsert;
		case XK_KP_Delete:
			return KKeyDelete;
		case XK_KP_Home:
			return KKeyHome;
		case XK_KP_End:
			return KKeyEnd;
		case XK_KP_Page_Up:
		case XK_Page_Up:
			return KKeyPageUp;
		case XK_KP_Page_Down:
		case XK_Page_Down:
			return KKeyPageDown;
		case XK_Escape:
			return KKeyEscape;
//		case XK_:
//			return KKeyHelp;
//		case XK_KP_:
///			return KKeyNumLock;
		case XK_Clear:
			return KKeyClear;
		case XK_F1:
			return KKeyF1;
		case XK_F2:
			return KKeyF2;
		case XK_F3:
			return KKeyF3;
		case XK_F4:
			return KKeyF4;
		case XK_F5:
			return KKeyF5;
		case XK_F6:
			return KKeyF6;
		case XK_F7:
			return KKeyF7;
		case XK_F8:
			return KKeyF8;
		case XK_F9:
			return KKeyF9;
		case XK_F10:
			return KKeyF10;
		case XK_F11:
			return KKeyF11;
		case XK_F12:
			return KKeyF12;
		case XK_F13:
			return KKeyF13;
		case XK_F14:
			return KKeyF14;
		case XK_F15:
			return KKeyF15;
		default:
			return 0;
	}
}

/*	DeriveFlags
 *
 *		Given an X-windows modifier flags, this converts to the YAAF
 *	modifier flags
 */

short XGAppCore::DeriveFlags(long f)
{
	short i;
	
	i = 0;
	if (f & ShiftMask)   i |= KKeyShift;
	if (f & LockMask)    i |= KKeyShift;		// treat caps lock like shift
	if (f & ControlMask) i |= KKeyControl;
	if (f & Mod1Mask)    i |= KKeyMod1;
	if (f & Mod2Mask)    i |= KKeyMod2;
	if (f & Mod3Mask)    i |= KKeyMod3;
	if (f & Mod4Mask)    i |= KKeyMod4;
	if (f & Mod5Mask)    i |= KKeyMod5;
	if (f & Button1Mask) i |= KKeyButton;
	if (f & Button2Mask) i |= KKeyButton2;
	if (f & Button3Mask) i |= KKeyButton3;
	if (f & Button4Mask) i |= KKeyButton4;
	if (f & Button5Mask) i |= KKeyButton5;
	gLastMods = i;
	
	return i;
}

/************************************************************************/
/*																		*/
/*	Event Processing													*/
/*																		*/
/************************************************************************/

/*	XGAppCore::DoEvent
 *
 *		Do one pass through my event loop.
 */

void XGAppCore::DoEvent()
{
	XGWindow *wptr;
	int i,len;
	short flags;
	short key;
	char buffer[16];
	KeySym sym;
	XGFocus *f;
	Window parent;
	XEvent event;
	static bool gDblClick = false;
	static clock_t gClickTime;
	static Point gClickWhere;
	clock_t eventTime;
	struct tms buf;
	short derFlags;

	try {
		GMenuCommand = -1;
		
		/*
		 *	Poll Xlib for the next event. I don't want to hang
		 *	on XNextEvent, as I also want to process periodic
		 *	events.
		 */

		if (::XPending(_GDisplay)) {
			::XNextEvent(_GDisplay,&event);
		} else {
			clock_t texpire;
			bool flag;
			
			/*
			 *	No events pending. I don't want to idle more than
			 *	1/4th of a second, so basically spin and call the
			 *	XEventsQueued to read the input stream from the
			 *	server. If I get something, bump out early.
			 */
			
			flag = false;
			texpire = times(&buf) + CLK_TCK/4;
			while (texpire > times(&buf)) {
				if (::XEventsQueued(_GDisplay,QueuedAfterReading)) {
					flag = true;
					break;
				}
				sched_yield();				// yield to other processes
			}
			if (flag) {
				/*
				 *	We received an event, so process it.
				 */
				
				::XNextEvent(_GDisplay,&event);
			} else {
				/*
				 *	No event; just return
				 */
				
				ArrangeOrder();		// make sure windows correct
				return;
			}
		}
		eventTime = times(&buf);

		if (1 == GetCurFocus()->SendDispatch(KXGAppEvent,0,(void *)&event)) {
			ArrangeOrder();
			return;
		}

		/*
		 *	Record the owner of this event; this becomes my
		 *	instantaneous frontmost window
		 */
		
		_GEventWindow = event.xany.window;
		fEventOwner = XGWindow::GetXGWindow(event.xany.window);
		f = GetCurFocus();

		/*
		 *	We've got an Xlib style event. parse
		 */
	
		switch (event.type) {
			case Expose:
				/*
				 *	This is an update/draw event. Find the view or the
				 *	window owning this thing, and dispatch appropriately
				 */
			
				wptr = XGWindow::GetXGWindow(event.xexpose.window);
				if (wptr) {
					Rect r;
					
					r.left = event.xexpose.x;
					r.right = r.left + event.xexpose.width;
					r.top = event.xexpose.y;
					r.bottom = r.top + event.xexpose.height;
					
					XGDraw::UpdateRect(&r);
					wptr->DoDraw(r);
					XGDraw::UpdateRect();
				}
				break;
			
			case ClientMessage:
				/*
				 *	My main window is being shut down. Set my quit flag.
				 */
			
				if (event.xclient.message_type == _GWMProtocols) {
					if (fEventOwner) {
						if ((fModalWindow == NULL) || (fModalWindow == fEventOwner)) {
							fEventOwner->CloseWindow();
						}
					}
				}
				break;
			
			case ConfigureNotify:
				/*
				 *	The window is being resized. If this is my main window,
				 *	pass the resize down to the window processing loop
				 */
			
				wptr = XGWindow::GetXGWindow(event.xconfigure.window);
				if (wptr) {
					// Pass resize to resize my children views
					wptr->DoSizeWindow();
				}
				break;
			
			case KeyPress:
				/*
				 *	Key press messages all run through my standard
				 *	dispatch system for current focus
				 */
			
				len = ::XLookupString(&(event.xkey),buffer,sizeof(buffer),&sym,NULL);
				key = GetSpecialKey(sym);
				if (key) {
					/*
					 *	Special key press
					 */
				
					flags = DeriveFlags(event.xkey.state);
					flags |= KSpecialKey;
					if (flags & KKeyMod2) {
						f->SendDispatch(KXGMenuKey,MAKEDWORD(flags,key),NULL);
					} else {
						f->SendDispatch(KXGKeyEvent,MAKEDWORD(flags,key),NULL);
					}
				} else {
					/*
					 *	Normal key
					 */
				
					flags = DeriveFlags(event.xkey.state);
					for (i = 0; i < len; i++) {
						if (flags & KKeyMod1) {
							// Dispatch menu command
							GetCurMenu()->MenuKey(f,buffer[i]);
						} else {
							key = (unsigned char)buffer[i];
							if (key == 0x7F) key = 0x08;	// translation
							f->SendDispatch(KXGKeyEvent,MAKEDWORD(flags,key),NULL);
						}
					}
				}
				break;
			
			case ButtonPress:
				Point pt;
				
				wptr = XGWindow::GetXGWindow(event.xbutton.window);
				if (fModalWindow && (fModalWindow != wptr)) break;
				pt.h = event.xbutton.x;
				pt.v = event.xbutton.y;
				
				/* Double click detection */
				gDblClick = false;
				if (gClickTime > eventTime) {
					if ((gClickWhere.h - 4 < pt.h) &&
							(gClickWhere.h + 4 > pt.h) &&
							(gClickWhere.v - 4 < pt.v) &&
							(gClickWhere.v + 4 > pt.v))
						gDblClick = true;
				}
				gClickTime = eventTime + CLK_TCK/4;
				gClickWhere = pt;

				derFlags = DeriveFlags(event.xbutton.state);
				if (gDblClick) derFlags |= KDoubleClick;
				GCapture = wptr->DoMouseDown(pt,derFlags);
				
				if (GCapture) {
					// Grab the pointer
					i = ::XGrabPointer(_GDisplay,
										event.xbutton.window,
										False,
								   		ButtonReleaseMask |
								   			ButtonPressMask |
								   			PointerMotionMask,
								  		GrabModeAsync,
								  		GrabModeAsync,
							 			None,
										None,
										CurrentTime);
				}	
				break;
								
			case ButtonRelease:
				/*
				 *	Button up event--pass on down only.
				 */
			
				if (!GCapture) break;
				
				/* Ungrab this first. */
				::XUngrabPointer(_GDisplay,CurrentTime);
				::XFlush(_GDisplay);
				
				wptr = XGWindow::GetXGWindow(event.xbutton.window);
				if (wptr) {
					// Do mouse up
					pt.h = event.xbutton.x;
					pt.v = event.xbutton.y;
					flags = DeriveFlags(event.xbutton.state);
					if (GCapture) flags |= KCapturedMouse;
					if (gDblClick) flags |= KDoubleClick;
					wptr->DoMouseUp(pt,flags,GCapture);
				}
				GCapture = NULL;
				break;
				
			case FocusIn:
				wptr = XGWindow::GetXGWindow(event.xfocus.window);
				if (wptr) {
					wptr->fActive = true;
					wptr->DoActivate(true);
				}
				break;
			case FocusOut:
				wptr = XGWindow::GetXGWindow(event.xfocus.window);
				if (wptr) {
					wptr->fActive = false;
					wptr->DoActivate(false);
				}
				break;
		
			case EnterNotify:
				/*
				 *	Enter notify generates motion flag
				 */
			
				wptr = XGWindow::GetXGWindow(event.xcrossing.subwindow);
				if (wptr) {
					pt.h = event.xcrossing.x;
					pt.v = event.xcrossing.y;
					flags = DeriveFlags(event.xcrossing.state);
					if (GCapture) flags |= KCapturedMouse;
					if (gDblClick) flags |= KDoubleClick;
					wptr->DoMouseMove(pt,flags,GCapture);
				}
				break;
			
			case LeaveNotify:
				/*
				 *	Leave notify generates disable event
				 */
			
				break;

			case MotionNotify:
				/*
				 *	Motion notify generates mouse moved events
				 */

				wptr = XGWindow::GetXGWindow(event.xmotion.window);
				if (fModalWindow && (fModalWindow != wptr)) break;
				if (wptr) {
					pt.h = event.xmotion.x;
					pt.v = event.xmotion.y;
					flags = DeriveFlags(event.xcrossing.state);
					if (GCapture) flags |= KCapturedMouse;
					if (gDblClick) flags |= KDoubleClick;
					wptr->DoMouseMove(pt,flags,GCapture);
				}
				break;
				
			case ReparentNotify:
				/*
				 *	Sent when the window I just made is wrappered
				 *	into a parent window. This sets the parent
				 *	window attribute.
				 *
				 *	I scan until I find the window that is the child
				 *	of the root window of my screen which owns my own
				 *	window. This is the thing I have to manipulate to
				 *	arrange the order of my windows...
				 */
				
				wptr = XGWindow::GetXGWindow(event.xreparent.window);
				if (wptr) {
					for (;;) {
						parent = GetParentWindow(event.xreparent.parent);
						if (parent == None) break;
						event.xreparent.parent = parent;
					}
					wptr->fWMWindow = event.xreparent.parent;
				}
				break;
			default:
				break;
		}
		
		/*
		 *	If I get here, there is a menu command to perform
		 */
		
		if (GMenuCommand > 0) {
			if (wptr) {
				wptr->SendDispatch(KXGDoMenuCommand,GMenuCommand,NULL);
			}
			GMenuCommand = -1;
		}
	}
	catch (XGError &x) {
		x.DisplayError();
	}
	/* Handle gcc bug with exception handling */
	catch (XGPostError &x) {
		x.DisplayError();
	}
	catch (XGAbort &x) {
		x.DisplayError();
	}
	
	ArrangeOrder();					/* Make sure order of windows is correct */
	fEventOwner = NULL;
}

/*	XGAppCore::CancelCheck
 *
 *		Check to see if the cancel button is pressed. This doesn't
 *	do a thing right now...
 */

bool XGAppCore::CancelCheck()
{
	return false;
}
