/******************************************************************************
 JXWindow.cc

	If we ever allow a constructor that takes a JXDisplay*, we have to
	rewrite JXGetCurrFontMgr.

	BASE CLASS = JXContainer

	Copyright © 1996 by John Lindal. All rights reserved.

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

#include <JXWindow.h>
#include <JXWindowDirector.h>
#include <JXMenuManager.h>
#include <JXDNDManager.h>
#include <JXHintManager.h>
#include <JXWidget.h>
#include <JXTextMenu.h>
#include <JXDisplay.h>
#include <JXGC.h>
#include <JXColormap.h>
#include <JXWindowPainter.h>
#include <JXImageMask.h>
#include <jXEventUtil.h>
#include <jXUtil.h>
#include <jXGlobals.h>

#include <X11/Xatom.h>
#include <jXKeysym.h>

#include <JString.h>
#include <jASCIIConstants.h>
#include <JMinMax.h>
#include <jStreamUtil.h>
#include <jStrStreamUtil.h>
#include <jMath.h>
#include <jTime.h>
#include <iostream.h>
#include <stdlib.h>
#include <ctype.h>
#include <limits.h>
#include <jAssert.h>

const JCharacter* kWMClassAtomName = "WM_CLASS";

// JXSelectionManager needs PropertyNotify

const unsigned int kEventMask =
	FocusChangeMask | KeyPressMask |
	ButtonPressMask | ButtonReleaseMask |
	PointerMotionMask | PointerMotionHintMask |
	EnterWindowMask | LeaveWindowMask |
	ExposureMask | StructureNotifyMask | PropertyChangeMask;

const unsigned int kPointerGrabMask =
	ButtonPressMask | ButtonReleaseMask |
	PointerMotionMask | PointerMotionHintMask |
	PropertyChangeMask;

const JFileVersion kCurrentGeometryDataVersion = 0;
const JCharacter kGeometryDataEndDelimiter     = '\1';

// JBroadcaster message types

const JCharacter* JXWindow::kIconified   = "Iconified::JXWindow";
const JCharacter* JXWindow::kDeiconified = "Deiconified::JXWindow";

/******************************************************************************
 Constructor

	colormap can be NULL.

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

JXWindow::JXWindow
	(
	JXWindowDirector*	aDirector,
	const JCoordinate	w,
	const JCoordinate	h,
	const JCharacter*	title,
	const JBoolean		ownsColormap,
	JXColormap*			colormap,
	const JBoolean		isMenu
	)
	:
	JXContainer((JXGetApplication())->GetCurrentDisplay(), this, NULL),
	itsDesktopLoc(0,0),
	itsBounds(0, 0, h, w),
	itsWMFrameLoc(0,0),
	itsIsDestructingFlag(kFalse),
	itsHasMinSizeFlag(kFalse),
	itsHasMaxSizeFlag(kFalse),
	itsIsMenuFlag(isMenu),
	itsFirstClick(kJXNoButton, 0, JPoint(-1,-1)),
	itsSecondClick(kJXNoButton, 0, JPoint(-1,-1))
{
	assert( aDirector != NULL );
	itsDirector = aDirector;

	itsTitle = new JString(title);
	assert( itsTitle != NULL );

	itsFocusWhenShowFlag    = kFalse;
	itsBufferPixmap         = None;
	itsBufferDrawingFlag    = isMenu;
	itsKeepBufferPixmapFlag = isMenu;
	itsUseBkgdPixmapFlag    = isMenu;
	itsCursorIndex          = kJXDefaultCursor;
	itsUpdateRegion         = XCreateRegion();

	itsShortcuts = new JArray<Shortcut>;
	assert( itsShortcuts != NULL );
	itsShortcuts->SetCompareFunction(CompareShortcuts);

	itsMouseContainer      = NULL;
	itsIsDraggingFlag      = kFalse;
	itsProcessDragFlag     = kFalse;
	itsCursorLeftFlag      = kFalse;
	itsCleanAfterBlockFlag = kFalse;

	itsButtonPressReceiver = this;
	itsPointerGrabbedFlag  = kFalse;
	itsBPRChangedFlag      = kFalse;

	itsFocusList = new JPtrArray<JXWidget>;
	assert( itsFocusList != NULL );

	itsFocusWidget = NULL;
	itsCurrHintMgr = NULL;

	itsPrevMouseContainer = NULL;
	itsClickCount         = 1;

	// get display for this window

	itsDisplay = (JXGetApplication())->GetCurrentDisplay();

	// get colormap for this window

	if (colormap != NULL)
		{
		assert( itsDisplay == colormap->GetDisplay() );

		itsColormap         = colormap;
		itsOwnsColormapFlag = ownsColormap;
		}
	else
		{
		itsColormap         = itsDisplay->GetColormap();
		itsOwnsColormapFlag = kFalse;
		}

	itsBackColor = itsColormap->GetDefaultBackColor();

	// create window

	const unsigned long valueMask =
		CWBackPixel | CWBorderPixel | CWColormap | CWCursor |
		CWSaveUnder | CWOverrideRedirect | CWEventMask;

	XSetWindowAttributes attr;
	attr.background_pixel  = itsColormap->GetXPixel(itsBackColor);
	attr.border_pixel      = itsColormap->GetXPixel(itsColormap->GetBlackColor());
	attr.colormap          = *itsColormap;
	attr.cursor            = itsDisplay->GetXCursorID(itsCursorIndex);
	attr.save_under        = itsIsMenuFlag;
	attr.override_redirect = itsIsMenuFlag;
	attr.event_mask        = kEventMask;

	itsXWindow =
		XCreateWindow(*itsDisplay, itsDisplay->GetRootWindow(), 0,0, w,h,
					  0, CopyFromParent, InputOutput, itsColormap->GetVisual(),
					  valueMask, &attr);

	// set window properties

	XTextProperty windowName;
	const int ok = XStringListToTextProperty((char**) &title, 1, &windowName);
	assert( ok );

	XSizeHints sizeHints;
	sizeHints.flags  = PSize;
	sizeHints.width  = w;
	sizeHints.height = h;

	XWMHints wmHints;
	wmHints.flags         = StateHint | InputHint;
	wmHints.initial_state = NormalState;
	wmHints.input         = True;

	JCharacter* argv = "jxapp";
	XSetWMProperties(*itsDisplay, itsXWindow, &windowName, &windowName,
					 &argv, 1, &sizeHints, &wmHints, NULL);

	XFree(windowName.value);

	// create JXDNDAware property for Drag-And-Drop

	(GetDNDManager())->EnableDND(itsXWindow);

	// we need to listen for map/unmap, iconify/deiconify, focus in/out

	itsIsMappedFlag    = kFalse;
	itsIsIconifiedFlag = kFalse;
	itsHasFocusFlag    = kFalse;

	// trap window manager's delete message

	itsCloseAction = kCloseDirector;

	Atom deleteWindowXAtom = itsDisplay->GetDeleteWindowXAtom();
	XSetWMProtocols(*itsDisplay, itsXWindow, &deleteWindowXAtom, 1);

	// create GC to use when drawing

	itsGC = new JXGC(itsDisplay, itsColormap, itsXWindow);
	assert( itsGC != NULL );

	// notify the display that we exist

	itsDisplay->WindowCreated(this, itsXWindow);
}

/******************************************************************************
 Destructor

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

JXWindow::~JXWindow()
{
	itsIsDestructingFlag = kTrue;

	DeleteEnclosedObjects();	// widgets talk to us when deleted

	itsDisplay->WindowDeleted(this);

	delete itsTitle;
	delete itsShortcuts;
	delete itsFocusList;

	if (itsBufferPixmap != None)
		{
		XFreePixmap(*itsDisplay, itsBufferPixmap);
		}

	XWMHints* wmHints = XGetWMHints(*itsDisplay, itsXWindow);
	if (wmHints != NULL)
		{
		if ((wmHints->flags & IconPixmapHint) != 0)
			{
			XFreePixmap(*itsDisplay, wmHints->icon_pixmap);
			}
		if ((wmHints->flags & IconMaskHint) != 0)
			{
			XFreePixmap(*itsDisplay, wmHints->icon_mask);
			}
		XFree(wmHints);
		}

	if (itsOwnsColormapFlag)
		{
		delete itsColormap;
		}

	delete itsGC;
	XDestroyRegion(itsUpdateRegion);
	XDestroyWindow(*itsDisplay, itsXWindow);
	itsDisplay->Flush();
}

/******************************************************************************
 SetTitle

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

void
JXWindow::SetTitle
	(
	const JCharacter* title
	)
{
	*itsTitle = title;

	XTextProperty windowName;
	const int ok = XStringListToTextProperty((char**) &title, 1, &windowName);
	assert( ok );

	XSetWMName(*itsDisplay, itsXWindow, &windowName);
	XSetWMIconName(*itsDisplay, itsXWindow, &windowName);

	XFree(windowName.value);
}

/******************************************************************************
 SetTransientFor

	Provide information to the window manager about which window we
	are subsidiary to.

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

void
JXWindow::SetTransientFor
	(
	const JXWindowDirector* director
	)
{
	XSetTransientForHint(*itsDisplay, itsXWindow,
						 (director->GetWindow())->itsXWindow);
}

/******************************************************************************
 SetWMClass

	Provide information to the window manager that can be used to identify
	the window's type.  This is useful when excluding certain types of
	windows from a task bar.

	Call this before SetIcon() to avoid losing the icon's mask.

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

void
JXWindow::SetWMClass
	(
	const JCharacter* instance,
	const JCharacter* c_class
	)
{
	JString data = instance;
	data.AppendCharacter('\0');
	data.Append(c_class);

	XChangeProperty(*itsDisplay, itsXWindow,
					XInternAtom(*itsDisplay, kWMClassAtomName, False), XA_STRING, 8,
					PropModeReplace,
					(unsigned char*) data.GetCString(), data.GetLength()+1);
}

/******************************************************************************
 ColormapChanged

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

void
JXWindow::ColormapChanged
	(
	JXColormap* colormap
	)
{
	XSetWindowColormap(*itsDisplay, itsXWindow, *colormap);
	Refresh();	// need to redraw to update pixel values
}

/******************************************************************************
 DispatchMouse

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

void
JXWindow::DispatchMouse()
{
	Window rootWindow, childWindow;
	int root_x, root_y, x,y;
	unsigned int state;
	if (IsVisible() &&
		XQueryPointer(*itsDisplay, itsXWindow, &rootWindow, &childWindow,
					  &root_x, &root_y, &x, &y, &state) &&
		itsDisplay->GetMouseContainer() == this)
		{
		XMotionEvent xEvent;
		xEvent.type      = MotionNotify;
		xEvent.display   = *itsDisplay;
		xEvent.window    = itsXWindow;
		xEvent.root      = rootWindow;
		xEvent.subwindow = None;
		xEvent.x         = x;
		xEvent.y         = y;
		xEvent.x_root    = root_x;
		xEvent.y_root    = root_y;
		xEvent.state     = state;

		HandleMotionNotify(xEvent);
		}
}

/******************************************************************************
 DispatchCursor

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

void
JXWindow::DispatchCursor()
{
	Window rootWindow, childWindow;
	int root_x, root_y, x,y;
	unsigned int state;
	if (IsVisible() &&
		XQueryPointer(*itsDisplay, itsXWindow, &rootWindow, &childWindow,
					  &root_x, &root_y, &x, &y, &state) &&
		itsDisplay->GetMouseContainer() == this)
		{
		itsButtonPressReceiver->
			DispatchCursor(JPoint(x,y), JXKeyModifiers(state));
		}
}

/******************************************************************************
 DisplayXCursor

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

void
JXWindow::DisplayXCursor
	(
	const JCursorIndex index
	)
{
	if (itsCursorIndex != index)
		{
		itsCursorIndex = index;
		XDefineCursor(*itsDisplay, itsXWindow, itsDisplay->GetXCursorID(index));
		}
}

/******************************************************************************
 Close

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

JBoolean
JXWindow::Close()
{
	if (itsCloseAction == kDeactivateDirector)
		{
		return itsDirector->Deactivate();
		}
	else if (itsCloseAction == kCloseDirector)
		{
		return itsDirector->Close();
		}
	else if (itsCloseAction == kCloseDisplay)
		{
		return itsDisplay->Close();
		}
	else
		{
		assert( itsCloseAction == kQuitApp );
		(JXGetApplication())->Quit();
		return kTrue;
		}
}

/******************************************************************************
 Activate (virtual)

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

void
JXWindow::Activate()
{
	if (!IsActive())
		{
		JXContainer::Activate();
		if (itsFocusWidget == NULL)
			{
			SwitchFocus(kFalse);
			}
		}
}

/******************************************************************************
 Show (virtual)

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

void
JXWindow::Show()
{
	if (!IsVisible())
		{
		JXContainer::Show();
		if (itsFocusWidget == NULL)
			{
			SwitchFocus(kFalse);
			}
		if (itsUseBkgdPixmapFlag)
			{
			Redraw();
			}
		XMapWindow(*itsDisplay, itsXWindow);
		// input focus set by HandleMapNotify()
		}
}

/******************************************************************************
 Hide (virtual)

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

void
JXWindow::Hide()
{
	if (IsVisible())
		{
		// We have to deiconify the window first because otherwise,
		// the icon won't disappear, at least under fvwm and fvwm2.

		const JBoolean wasIconified = itsIsIconifiedFlag;
		if (itsIsIconifiedFlag)
			{
			Deiconify();

			// toss the MapNotify event -- avoids error from XSetInputFocus()

			XEvent xEvent;
			XIfEvent(*itsDisplay, &xEvent, GetNextMapNotifyEvent, NULL);
			itsIsIconifiedFlag = kFalse;
			}
		XUnmapWindow(*itsDisplay, itsXWindow);
		itsIsMappedFlag = kFalse;
		JXContainer::Hide();

// We don't do this because it ought to be deiconified when next shown,
// but we don't want to do it in Show() because windows should stay
// iconified during program startup.
//		if (wasIconified)
//			{
//			Iconify();
//			}
		}
}

// static private

Bool
JXWindow::GetNextMapNotifyEvent
	(
	Display*	display,
	XEvent*		event,
	char*		arg
	)
{
	if (event->type == MapNotify)
		{
		return True;
		}
	else
		{
		return False;
		}
}

/******************************************************************************
 Raise

	Make this the top window.

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

void
JXWindow::Raise
	(
	const JBoolean grabKeyboardFocus
	)
{
	Show();

	if (itsIsIconifiedFlag)
		{
		Deiconify();
		}
	else if (grabKeyboardFocus)
		{
		RequestFocus();
		}

	// By calling Place() we insure that the window is visible on
	// the current virtual desktop.

	Place(itsWMFrameLoc.x, itsWMFrameLoc.y);

	XRaiseWindow(*itsDisplay, itsXWindow);
}

/******************************************************************************
 Lower

	Make this the bottom window.

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

void
JXWindow::Lower()
{
	XLowerWindow(*itsDisplay, itsXWindow);
}

/******************************************************************************
 RequestFocus

	Ask X for keyboard input focus.  This uses XSetInputFocus() rather
	than XGrabKeyboard().

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

void
JXWindow::RequestFocus()
{
	if (itsIsMappedFlag)
		{
		XSetInputFocus(*itsDisplay, itsXWindow, RevertToPointerRoot, CurrentTime);
		}
}

/******************************************************************************
 Iconify

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

void
JXWindow::Iconify()
{
	// change the initial_state hint

	SetWindowStateHint(IconicState);

	// take other necessary actions

	if (IsVisible())
		{
		// We don't modify itsIsIconifiedFlag here because the procedure might
		// fail.  We wait for an UnmapNotify event.

		XIconifyWindow(*itsDisplay, itsXWindow, itsDisplay->GetScreen());
		}
	else if (!itsIsIconifiedFlag)
		{
		itsIsIconifiedFlag = kTrue;
		Broadcast(Iconified());
		}
}

/******************************************************************************
 Deiconify

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

void
JXWindow::Deiconify()
{
	// change the initial_state hint

	SetWindowStateHint(NormalState);

	// take other necessary actions

	if (IsVisible())
		{
		// We don't modify itsIsIconifiedFlag here because we already deal
		// with it in HandleMapNotify().

		XMapWindow(*itsDisplay, itsXWindow);
		}
	else if (itsIsIconifiedFlag)
		{
		itsIsIconifiedFlag = kFalse;
		Broadcast(Deiconified());
		}
}

/******************************************************************************
 SetWindowStateHint (private)

	Sets the initial_state hint.

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

void
JXWindow::SetWindowStateHint
	(
	const int initial_state
	)
{
	XWMHints wmHints;

	XWMHints* origHints = XGetWMHints(*itsDisplay, itsXWindow);
	if (origHints != NULL)
		{
		wmHints = *origHints;
		XFree(origHints);
		}
	else
		{
		wmHints.flags = 0;
		}

	wmHints.flags        |= StateHint;
	wmHints.initial_state = initial_state;
	XSetWMHints(*itsDisplay, itsXWindow, &wmHints);
}

/******************************************************************************
 Refresh (virtual)

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

void
JXWindow::Refresh()
	const
{
	RefreshRect(itsBounds);
}

/******************************************************************************
 RefreshRect

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

void
JXWindow::RefreshRect
	(
	const JRect& rect
	)
	const
{
	XRectangle xrect = JXJToXRect(rect);
	XUnionRectWithRegion(&xrect, itsUpdateRegion, itsUpdateRegion);
	itsDisplay->WindowNeedsUpdate(const_cast<JXWindow*>(this));
}

/******************************************************************************
 Redraw (virtual)

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

void
JXWindow::Redraw()
	const
{
	Refresh();
	(const_cast<JXWindow*>(this))->Update();
}

/******************************************************************************
 RedrawRect

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

void
JXWindow::RedrawRect
	(
	const JRect& rect
	)
	const
{
	RefreshRect(rect);
	(const_cast<JXWindow*>(this))->Update();
}

/******************************************************************************
 BufferDrawing

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

void
JXWindow::BufferDrawing
	(
	const JBoolean bufferDrawing
	)
{
	itsBufferDrawingFlag = JI2B(bufferDrawing || itsUseBkgdPixmapFlag);
	if (!itsBufferDrawingFlag && itsBufferPixmap != None)
		{
		XFreePixmap(*itsDisplay, itsBufferPixmap);
		itsBufferPixmap = None;
		}
}

/******************************************************************************
 Update

	jafl 11/12/97:
		Creating the JXWindowPainter each time we are called is easier
		than keeping it because we don't have to worry about what
		Reset() doesn't clear and because the cost is negligible compared
		with creating the buffer pixmap.

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

void
JXWindow::Update()
{
	if (XEmptyRegion(itsUpdateRegion))
		{
		return;
		}
	else if ((!itsIsMappedFlag || itsIsIconifiedFlag) && !itsUseBkgdPixmapFlag)
		{
		XDestroyRegion(itsUpdateRegion);
		itsUpdateRegion = XCreateRegion();
		return;
		}

	assert( !itsUseBkgdPixmapFlag || itsBufferDrawingFlag );

	// We clear itsUpdateRegion first so widgets call call Refresh() inside Draw()

	Region updateRegion = JXCopyRegion(itsUpdateRegion);
	XDestroyRegion(itsUpdateRegion);
	itsUpdateRegion = XCreateRegion();

	Drawable drawable   = itsXWindow;
	const JCoordinate w = itsBounds.width();
	const JCoordinate h = itsBounds.height();

	if (itsBufferDrawingFlag && itsBufferPixmap == None)
		{
		itsBufferPixmap = XCreatePixmap(*itsDisplay, itsXWindow, w,h,
										itsDisplay->GetDepth());
		if (itsBufferPixmap != None)
			{
			drawable = itsBufferPixmap;
			}
		}
	else if (itsBufferDrawingFlag)
		{
		drawable = itsBufferPixmap;
		}

	XRectangle xrect;
	XClipBox(updateRegion, &xrect);
	const JRect rect = JXXToJRect(xrect);

	JXWindowPainter p(itsGC, drawable, itsBounds, updateRegion);
	DrawAll(p, rect);

	if (itsUseBkgdPixmapFlag && itsBufferPixmap != None)
		{
		XSetWindowBackgroundPixmap(*itsDisplay, itsXWindow, itsBufferPixmap);
		XClearWindow(*itsDisplay, itsXWindow);
		}
	else if (itsUseBkgdPixmapFlag)
		{
		XSetWindowBackground(*itsDisplay, itsXWindow,
							 itsColormap->GetXPixel(itsBackColor));
		}
	else if (itsBufferPixmap != None)
		{
		itsGC->SetClipRegion(updateRegion);
		itsGC->CopyPixels(itsBufferPixmap,
						  xrect.x, xrect.y, xrect.width, xrect.height,
						  itsXWindow, xrect.x, xrect.y);
		}

	// Under normal conditions, we have to toss the pixmap because
	// it uses an enormous amount of server memory.

	if (!itsKeepBufferPixmapFlag && itsBufferPixmap != None &&
		!(itsIsDraggingFlag && itsProcessDragFlag))
		{
		XFreePixmap(*itsDisplay, itsBufferPixmap);
		itsBufferPixmap = None;
		}

	XDestroyRegion(updateRegion);

	itsDisplay->Flush();
}

/******************************************************************************
 Draw (virtual protected)

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

void
JXWindow::Draw
	(
	JXWindowPainter&	p,
	const JRect&		rect
	)
{
}

/******************************************************************************
 DrawBorder (virtual protected)

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

void
JXWindow::DrawBorder
	(
	JXWindowPainter&	p,
	const JRect&		frame
	)
{
}

/******************************************************************************
 DrawBackground (virtual protected)

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

void
JXWindow::DrawBackground
	(
	JXWindowPainter&	p,
	const JRect&		frame
	)
{
	p.SetPenColor(itsBackColor);
	p.SetFilling(kTrue);
	p.JPainter::Rect(itsBounds);
}

/******************************************************************************
 GlobalToLocal (virtual)

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

JPoint
JXWindow::GlobalToLocal
	(
	const JCoordinate x,
	const JCoordinate y
	)
	const
{
	return JPoint(x,y);
}

/******************************************************************************
 LocalToGlobal (virtual)

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

JPoint
JXWindow::LocalToGlobal
	(
	const JCoordinate x,
	const JCoordinate y
	)
	const
{
	return JPoint(x,y);
}

/******************************************************************************
 GlobalToRoot

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

JRect
JXWindow::GlobalToRoot
	(
	const JRect& r
	)
	const
{
	const JPoint topLeft  = GlobalToRoot(r.left, r.top);
	const JPoint botRight = GlobalToRoot(r.right, r.bottom);
	return JRect(topLeft.y, topLeft.x, botRight.y, botRight.x);
}

/******************************************************************************
 RootToGlobal

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

JRect
JXWindow::RootToGlobal
	(
	const JRect& r
	)
	const
{
	const JPoint topLeft  = RootToGlobal(r.left, r.top);
	const JPoint botRight = RootToGlobal(r.right, r.bottom);
	return JRect(topLeft.y, topLeft.x, botRight.y, botRight.x);
}

/******************************************************************************
 CalcDesktopLocation (private)

	Shift the given point to cancel the window manager border.

	direction should be +1 or -1.
		+1 => WMFrame    -> itsXWindow
		-1 => itsXWindow -> WMFrame

	If anything fails, the window manager must be having convulsions
	(e.g. restarting).  In this case, we will get ConfigureNotify after
	things calm down, so we simply return the input values.

	Some window managers are smart enough to automatically compensate for
	the window border when we call XMoveWindow().  We like this behavior,
	so we assume it by default by setting compensate=kFalse.  It won't
	always work, however, so we check the result in Place(), and if it
	didn't work, we change our behavior and try again.  Once we find a
	method that works, we set method=kTrue so we ignore further failures
	as network latency problems.

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

static JBoolean theFoundWMFrameMethodFlag = kFalse;
static JBoolean theWMFrameCompensateFlag  = kFalse;

JPoint
JXWindow::CalcDesktopLocation
	(
	const JCoordinate origX,
	const JCoordinate origY,
	const JCoordinate origDirection
	)
	const
{
	if (origDirection > 0 && !theWMFrameCompensateFlag)
		{
		return JPoint(origX, origY);
		}

	Window rootChild;
	if (!GetRootChild(&rootChild) || rootChild == itsXWindow)
		{
		return JPoint(origX, origY);
		}

	JCoordinate desktopX = origX;
	JCoordinate desktopY = origY;

	int x,y;
	Window childWindow;
	if (XTranslateCoordinates(*itsDisplay, itsXWindow, rootChild,
							  0,0, &x,&y, &childWindow))
		{
		const JCoordinate direction = JSign(origDirection);
		desktopX += x * direction;
		desktopY += y * direction;
		}

	return JPoint(desktopX, desktopY);
}

/******************************************************************************
 GetRootChild (private)

	If possible, returns the X window that is our ancestor and a child
	of the root window.

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

JBoolean
JXWindow::GetRootChild
	(
	Window* rootChild
	)
	const
{
	*rootChild = None;

	Window currWindow = itsXWindow;
	while (1)
		{
		Window rootWindow, parentWindow;
		Window* childList;
		unsigned int childCount;
		if (!XQueryTree(*itsDisplay, currWindow, &rootWindow,
						&parentWindow, &childList, &childCount))
			{
			return kFalse;
			}
		XFree(childList);

		if (parentWindow == rootWindow)
			{
			*rootChild = currWindow;
			return kTrue;
			}
		else
			{
			currWindow = parentWindow;
			}
		}
}

/******************************************************************************
 Place (virtual)

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

static JSize thePlacementAttempt = 0;

void
JXWindow::Place
	(
	const JCoordinate origEnclX,
	const JCoordinate origEnclY
	)
{
	JCoordinate enclX = origEnclX;
	JCoordinate enclY = origEnclY;

	// adjust position so at least part of window is visible
	// (important for virtual desktops)

	const JRect desktopBounds = itsDisplay->GetBounds();

	while (enclX + itsBounds.width() <= desktopBounds.left)
		{
		enclX += desktopBounds.width();
		}
	while (enclX >= desktopBounds.right)
		{
		enclX -= desktopBounds.width();
		}

	while (enclY + itsBounds.height() <= desktopBounds.top)
		{
		enclY += desktopBounds.height();
		}
	while (enclY >= desktopBounds.bottom)
		{
		enclY -= desktopBounds.height();
		}

	// compensate for width of window manager frame

	const JPoint pt = CalcDesktopLocation(enclX, enclY, +1);

	// tell the window manager to move us

	XMoveWindow(*itsDisplay, itsXWindow, pt.x, pt.y);

	long supplied;
	XSizeHints sizeHints;
	if (!XGetWMNormalHints(*itsDisplay, itsXWindow, &sizeHints, &supplied))
		{
		sizeHints.flags = 0;
		}

	sizeHints.flags |= PPosition;
	sizeHints.x      = pt.x;
	sizeHints.y      = pt.y;

	XSetWMNormalHints(*itsDisplay, itsXWindow, &sizeHints);

	// update our private information

	if (!theFoundWMFrameMethodFlag && itsIsMappedFlag)
		{
		itsDisplay->Synchronize();
		JWait(0.5);
		}

	UpdateFrame();

/*	if (itsIsMappedFlag)
		{
		cout << endl;
		cout << thePlacementAttempt;
		cout << theFoundWMFrameMethodFlag << endl;
		cout << GetDesktopLocation() << endl;
		cout << JPoint(enclX, enclY) << endl;
		}
*/
	if (!theFoundWMFrameMethodFlag && itsIsMappedFlag &&
		thePlacementAttempt < 2)
		{
		thePlacementAttempt++;

		if (GetDesktopLocation() != JPoint(enclX, enclY))
			{
			theWMFrameCompensateFlag = JNegate(theWMFrameCompensateFlag);
			Place(enclX, enclY);
			}
		else
			{
			theFoundWMFrameMethodFlag = kTrue;
			}

		assert( thePlacementAttempt > 0 );
		thePlacementAttempt--;
		}
}

/******************************************************************************
 Move (virtual)

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

void
JXWindow::Move
	(
	const JCoordinate dx,
	const JCoordinate dy
	)
{
	Place(itsWMFrameLoc.x + dx, itsWMFrameLoc.y + dy);
}

/******************************************************************************
 SetSize (virtual)

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

void
JXWindow::SetSize
	(
	const JCoordinate origW,
	const JCoordinate origH
	)
{
	JCoordinate w = origW;
	JCoordinate h = origH;
	if (itsHasMinSizeFlag)
		{
		w = JMax(w, itsMinSize.x);
		h = JMax(h, itsMinSize.y);
		}
	if (itsHasMaxSizeFlag)
		{
		w = JMin(w, itsMaxSize.x);
		h = JMin(h, itsMaxSize.y);
		}

	XResizeWindow(*itsDisplay, itsXWindow, w, h);

	long supplied;
	XSizeHints sizeHints;
	if (!XGetWMNormalHints(*itsDisplay, itsXWindow, &sizeHints, &supplied))
		{
		sizeHints.flags = 0;
		}

	sizeHints.flags  |= PSize;
	sizeHints.width   = w;
	sizeHints.height  = h;
	XSetWMNormalHints(*itsDisplay, itsXWindow, &sizeHints);

	UpdateFrame();
}

/******************************************************************************
 AdjustSize (virtual)

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

void
JXWindow::AdjustSize
	(
	const JCoordinate dw,
	const JCoordinate dh
	)
{
	SetSize(itsBounds.width() + dw, itsBounds.height() + dh);
}

/******************************************************************************
 UpdateFrame (private)

	We can't pass in the new frame information because ConfigureNotify
	caused by resize window gives x=0, y=0.

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

void
JXWindow::UpdateFrame()
{
	itsDisplay->Synchronize();

	Window rootWindow;
	int x,y;
	unsigned int w,h, bw, depth;
	const Status ok1 = XGetGeometry(*itsDisplay, itsXWindow, &rootWindow,
									&x, &y, &w, &h, &bw, &depth);
	assert( ok1 );

	// After XGetGeometry(), x=0 and y=0 (at least for fvwm)

	Window childWindow;
	const Bool ok2 = XTranslateCoordinates(*itsDisplay, itsXWindow, rootWindow,
										   0,0, &x, &y, &childWindow);
	assert( ok2 );

	itsDesktopLoc.Set(x,y);
//	if (IsVisible())
//		{
		itsWMFrameLoc = CalcDesktopLocation(x,y, -1);
//		}
//	else
//		{
//		itsWMFrameLoc = itsDesktopLoc;
//		}

	const JCoordinate dw = w - itsBounds.width();
	const JCoordinate dh = h - itsBounds.height();
	itsBounds.top    = 0;
	itsBounds.left   = 0;
	itsBounds.bottom = h;
	itsBounds.right  = w;
	NotifyBoundsResized(dw,dh);

	if ((dw != 0 || dh != 0) && itsBufferPixmap != None)
		{
		XFreePixmap(*itsDisplay, itsBufferPixmap);
		itsBufferPixmap = None;
		}
}

/******************************************************************************
 CenterOnScreen

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

void
JXWindow::CenterOnScreen()
{
	const JRect r = itsDisplay->GetBounds();
	Place((r.left + r.right - itsBounds.width())/2,
		  (r.top + r.bottom - itsBounds.height())/2);
}

/******************************************************************************
 PlaceAsDialogWindow

	Following Macintosh Human Interface Guidelines, dialog windows
	have 1/3 blank space above and 2/3 blank space below.

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

void
JXWindow::PlaceAsDialogWindow()
{
	const JRect r = itsDisplay->GetBounds();
	Place((r.left + r.right - itsBounds.width())/2,
		  (r.top + r.bottom - itsBounds.height())/3);
}

/******************************************************************************
 SetMinSize

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

void
JXWindow::SetMinSize
	(
	const JCoordinate origW,
	const JCoordinate origH
	)
{
	long supplied;
	XSizeHints sizeHints;
	if (!XGetWMNormalHints(*itsDisplay, itsXWindow, &sizeHints, &supplied))
		{
		sizeHints.flags = 0;
		}

	JCoordinate w = origW;
	JCoordinate h = origH;
	if ((sizeHints.flags & PMaxSize) != 0)
		{
		w = JMin(w, (JCoordinate) sizeHints.max_width);
		h = JMin(h, (JCoordinate) sizeHints.max_height);
		}

	sizeHints.flags     |= PMinSize;
	sizeHints.min_width  = w;
	sizeHints.min_height = h;
	XSetWMNormalHints(*itsDisplay, itsXWindow, &sizeHints);
	itsDisplay->Flush();

	itsHasMinSizeFlag = kTrue;
	itsMinSize.x      = w;
	itsMinSize.y      = h;

	if (itsBounds.width() < w || itsBounds.height() < h)
		{
		SetSize(JMax(w,itsBounds.width()), JMax(h,itsBounds.height()));
		}
}

/******************************************************************************
 ClearMinSize

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

void
JXWindow::ClearMinSize()
{
	long supplied;
	XSizeHints sizeHints;
	if (XGetWMNormalHints(*itsDisplay, itsXWindow, &sizeHints, &supplied) &&
		(sizeHints.flags & PMinSize) != 0)
		{
		sizeHints.flags     -= PMinSize;
		sizeHints.min_width  = 0;
		sizeHints.min_height = 0;
		XSetWMNormalHints(*itsDisplay, itsXWindow, &sizeHints);
		itsDisplay->Flush();
		}

	itsHasMinSizeFlag = kFalse;
}

/******************************************************************************
 SetMaxSize

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

void
JXWindow::SetMaxSize
	(
	const JCoordinate origW,
	const JCoordinate origH
	)
{
	long supplied;
	XSizeHints sizeHints;
	if (!XGetWMNormalHints(*itsDisplay, itsXWindow, &sizeHints, &supplied))
		{
		sizeHints.flags = 0;
		}

	JCoordinate w = origW;
	JCoordinate h = origH;
	if ((sizeHints.flags & PMinSize) != 0)
		{
		w = JMax(w, (JCoordinate) sizeHints.min_width);
		h = JMax(h, (JCoordinate) sizeHints.min_height);
		}

	sizeHints.flags     |= PMaxSize;
	sizeHints.max_width  = w;
	sizeHints.max_height = h;
	XSetWMNormalHints(*itsDisplay, itsXWindow, &sizeHints);
	itsDisplay->Flush();

	itsHasMaxSizeFlag = kTrue;
	itsMaxSize.x      = w;
	itsMaxSize.y      = h;

	if (itsBounds.width() > w || itsBounds.height() > h)
		{
		SetSize(JMin(w,itsBounds.width()), JMin(h,itsBounds.height()));
		}
}

/******************************************************************************
 ClearMaxSize

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

void
JXWindow::ClearMaxSize()
{
	long supplied;
	XSizeHints sizeHints;
	if (XGetWMNormalHints(*itsDisplay, itsXWindow, &sizeHints, &supplied) &&
		(sizeHints.flags & PMaxSize) != 0)
		{
		sizeHints.flags     -= PMaxSize;
		sizeHints.max_width  = INT_MAX;
		sizeHints.max_height = INT_MAX;
		XSetWMNormalHints(*itsDisplay, itsXWindow, &sizeHints);
		itsDisplay->Flush();
		}

	itsHasMaxSizeFlag = kFalse;
}

/******************************************************************************
 SetStepSize

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

void
JXWindow::SetStepSize
	(
	const JCoordinate dw,
	const JCoordinate dh
	)
{
	long supplied;
	XSizeHints sizeHints;
	if (!XGetWMNormalHints(*itsDisplay, itsXWindow, &sizeHints, &supplied))
		{
		sizeHints.flags = 0;
		}

	sizeHints.flags     |= PResizeInc;
	sizeHints.width_inc  = dw;
	sizeHints.height_inc = dh;
	XSetWMNormalHints(*itsDisplay, itsXWindow, &sizeHints);
	itsDisplay->Flush();
}

/******************************************************************************
 ClearStepSize

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

void
JXWindow::ClearStepSize()
{
	long supplied;
	XSizeHints sizeHints;
	if (XGetWMNormalHints(*itsDisplay, itsXWindow, &sizeHints, &supplied) &&
		(sizeHints.flags & PResizeInc) != 0)
		{
		sizeHints.flags     -= PResizeInc;
		sizeHints.width_inc  = 1;
		sizeHints.height_inc = 1;
		XSetWMNormalHints(*itsDisplay, itsXWindow, &sizeHints);
		itsDisplay->Flush();
		}
}

/******************************************************************************
 SetBackColor

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

void
JXWindow::SetBackColor
	(
	const JColorIndex color
	)
{
	itsBackColor = color;
	if (!itsUseBkgdPixmapFlag)
		{
		XSetWindowBackground(*itsDisplay, itsXWindow, itsColormap->GetXPixel(color));
		}
}

/******************************************************************************
 SetIcon

	Call this after SetWMClass() to avoid losing the icon's mask.

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

void
JXWindow::SetIcon
	(
	const JXImage& icon
	)
{
	XWMHints wmHints;

	// throw out the original icon

	XWMHints* origHints = XGetWMHints(*itsDisplay, itsXWindow);
	if (origHints != NULL)
		{
		if ((origHints->flags & IconPixmapHint) != 0)
			{
			XFreePixmap(*itsDisplay, origHints->icon_pixmap);
			}
		if ((origHints->flags & IconMaskHint) != 0)
			{
			XFreePixmap(*itsDisplay, origHints->icon_mask);
			}

		wmHints = *origHints;
		XFree(origHints);
		}
	else
		{
		wmHints.flags = 0;
		}

	// set new icon

	wmHints.flags       |= IconPixmapHint;
	wmHints.icon_pixmap  = icon.CreatePixmap();

	JXImageMask* mask;
	if (icon.GetMask(&mask))
		{
		wmHints.flags     |= IconMaskHint;
		wmHints.icon_mask  = mask->CreatePixmap();
		}

	XSetWMHints(*itsDisplay, itsXWindow, &wmHints);
}

/******************************************************************************
 BoundsMoved (virtual protected)

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

void
JXWindow::BoundsMoved
	(
	const JCoordinate dx,
	const JCoordinate dy
	)
{
}

/******************************************************************************
 EnclosingBoundsMoved (virtual protected)

	We will never get this message.

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

void
JXWindow::EnclosingBoundsMoved
	(
	const JCoordinate dx,
	const JCoordinate dy
	)
{
	assert( 0 );
}

/******************************************************************************
 BoundsResized (virtual protected)

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

void
JXWindow::BoundsResized
	(
	const JCoordinate dw,
	const JCoordinate dh
	)
{
}

/******************************************************************************
 EnclosingBoundsResized (virtual protected)

	We will never get this message.

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

void
JXWindow::EnclosingBoundsResized
	(
	const JCoordinate dwb,
	const JCoordinate dhb
	)
{
	assert( 0 );
}

/******************************************************************************
 GetBoundsGlobal (virtual)

	Returns the bounds in global coordinates.

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

JRect
JXWindow::GetBoundsGlobal()
	 const
{
	return itsBounds;
}

/******************************************************************************
 GetFrameGlobal (virtual)

	Returns the frame in global coordinates.

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

JRect
JXWindow::GetFrameGlobal()
	 const
{
	return itsBounds;
}

/******************************************************************************
 GetApertureGlobal (virtual)

	Returns the aperture in global coordinates.

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

JRect
JXWindow::GetApertureGlobal()
	 const
{
	return itsBounds;
}

/******************************************************************************
 ReadGeometry

	Read in position, size, and state and adjust ourselves.

	In the version that takes JString&, the string can be empty, in which
	case, nothing changes.  If the string is not empty, it is assumed
	to contain only the output of WriteGeometry().

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

void
JXWindow::ReadGeometry
	(
	const JString& data
	)
{
	const JSize dataLength = data.GetLength();
	if (dataLength > 0)
		{
		jistrstream(input, data.GetCString(), dataLength);
		ReadGeometry(input);
		}
}

void
JXWindow::ReadGeometry
	(
	istream& input
	)
{
	JFileVersion vers;
	input >> vers;
	if (vers > kCurrentGeometryDataVersion)
		{
		JIgnoreUntil(input, kGeometryDataEndDelimiter);
		return;
		}

	JPoint desktopLoc;
	JCoordinate w,h;
	JBoolean iconified;
	input >> desktopLoc >> w >> h >> iconified;

	JIgnoreUntil(input, kGeometryDataEndDelimiter);

	Place(desktopLoc.x, desktopLoc.y);
	SetSize(w,h);

	if (iconified)
		{
		Iconify();
		}
	else
		{
		Deiconify();
		}
}

/******************************************************************************
 WriteGeometry

	Write out our position, size, and state (iconified).

	We have to write out an ending delimiter that is never used anywhere
	else so we can at least ignore the data if we can't read the given
	version.

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

void
JXWindow::WriteGeometry
	(
	JString* data
	)
	const
{
	ostrstream output;
	WriteGeometry(output);
	output << ends;
	*data = output.str();
	JUnfreeze(output);
}

void
JXWindow::WriteGeometry
	(
	ostream& output
	)
	const
{
	output << kCurrentGeometryDataVersion;
	output << ' ' << GetDesktopLocation();
	output << ' ' << GetFrameWidth();
	output << ' ' << GetFrameHeight();
	output << ' ' << IsIconified();
	output << kGeometryDataEndDelimiter;
}

/******************************************************************************
 HandleEvent

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

void
JXWindow::HandleEvent
	(
	const XEvent& xEvent
	)
{
	if (xEvent.type == EnterNotify)
		{
		HandleEnterNotify(xEvent.xcrossing);
		}
	else if (xEvent.type == LeaveNotify)
		{
		HandleLeaveNotify(xEvent.xcrossing);
		}
	else if (xEvent.type == MotionNotify)
		{
		HandleMotionNotify(xEvent.xmotion);
		}
	else if (xEvent.type == ButtonPress)
		{
		HandleButtonPress(xEvent.xbutton);
		}
	else if (xEvent.type == ButtonRelease)
		{
		HandleButtonRelease(xEvent.xbutton);
		}

	else if (xEvent.type == FocusIn)
		{
		HandleFocusIn(xEvent.xfocus);
		}
	else if (xEvent.type == FocusOut)
		{
		HandleFocusOut(xEvent.xfocus);
		}
	else if (xEvent.type == KeyPress)
		{
		HandleKeyPress(xEvent);
		}

	else if (xEvent.type == Expose && !itsUseBkgdPixmapFlag)
		{
		XRectangle xrect;
		xrect.x      = xEvent.xexpose.x;
		xrect.y      = xEvent.xexpose.y;
		xrect.width  = xEvent.xexpose.width;
		xrect.height = xEvent.xexpose.height;
		XUnionRectWithRegion(&xrect, itsUpdateRegion, itsUpdateRegion);
		itsDisplay->WindowNeedsUpdate(this);
		}

	else if (xEvent.type == ConfigureNotify)
		{
		UpdateFrame();
		}

	else if (xEvent.type == MapNotify)
		{
		HandleMapNotify(xEvent.xmap);
		}
	else if (xEvent.type == UnmapNotify)
		{
		HandleUnmapNotify(xEvent.xunmap);
		}

	else if (IsDeleteWindowMessage(itsDisplay, xEvent))		// trap WM_DELETE_WINDOW first
		{
		Close();											// can delete us
		}
	else if (xEvent.type == ClientMessage)					// otherwise, send to all widgets
		{
		DispatchClientMessage(xEvent.xclient);
		}
}

/******************************************************************************
 IsDeleteWindowMessage (static)

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

JBoolean
JXWindow::IsDeleteWindowMessage
	(
	const JXDisplay*	display,
	const XEvent&		xEvent
	)
{
	return JConvertToBoolean(
		xEvent.type                       == ClientMessage &&
		xEvent.xclient.message_type       == display->GetWMProtocolsXAtom() &&
		xEvent.xclient.format             == 32 &&
		((Atom) xEvent.xclient.data.l[0]) == display->GetDeleteWindowXAtom());
}

/******************************************************************************
 HandleEnterNotify (private)

	If the cursor is grabbed, then this event means that we no longer
	need to process the previous LeaveNotify.

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

void
JXWindow::HandleEnterNotify
	(
	const XCrossingEvent& xEvent
	)
{
	if (JXButtonStates::AllOff(xEvent.state))
		{
		itsDisplay->SetMouseContainer(this);
		itsButtonPressReceiver->
			DispatchNewMouseEvent(EnterNotify, JPoint(xEvent.x, xEvent.y),
								  kJXNoButton, xEvent.state);
		}
	else	// cursor grabbed
		{
		itsCursorLeftFlag = kFalse;
		}
}

/******************************************************************************
 HandleLeaveNotify (private)

	If the cursor is grabbed, then we don't want to process this event
	until the drag is finished.

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

void
JXWindow::HandleLeaveNotify
	(
	const XCrossingEvent& xEvent
	)
{
	if (JXButtonStates::AllOff(xEvent.state))
		{
		SetMouseContainer(NULL, JPoint(xEvent.x, xEvent.y), xEvent.state);
		itsDisplay->SetMouseContainer(NULL);
		}
	else	// cursor grabbed
		{
		itsCursorLeftFlag = kTrue;
		}
}

/******************************************************************************
 HandleMotionNotify (private)

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

void
JXWindow::HandleMotionNotify
	(
	const XMotionEvent& xEvent
	)
{
	XEvent discardEvent;
	while (XCheckMaskEvent(*itsDisplay, PointerMotionMask, &discardEvent))
		{ };

	// XQueryPointer returns the current mouse state, not the state
	// when the XMotionEvent was generated.  This can cause HandleMouseDrag()
	// before HandleMouseDown().

	Window rootWindow, childWindow;
	int x_root, y_root, x,y;
	unsigned int state;
	if (XQueryPointer(*itsDisplay, itsXWindow, &rootWindow, &childWindow,
					  &x_root, &y_root, &x, &y, &state))
		{
		const JBoolean isDrag = JNegate(JXButtonStates::AllOff(xEvent.state));
		if (itsIsDraggingFlag && isDrag &&			// otherwise wait for ButtonPress
			itsProcessDragFlag && itsMouseContainer != NULL)
			{
			const JPoint pt = itsMouseContainer->GlobalToLocal(x,y);
			itsMouseContainer->DispatchMouseDrag(pt, JXButtonStates(xEvent.state),
												 JXKeyModifiers(xEvent.state));
			}
		else if (!itsIsDraggingFlag && !isDrag && itsButtonPressReceiver->IsVisible())
			{
			itsButtonPressReceiver->
				DispatchNewMouseEvent(MotionNotify, JPoint(x,y),
									  kJXNoButton, xEvent.state);
			}
		// otherwise wait for ButtonRelease
		}
}

/******************************************************************************
 HandleButtonPress (private)

	Returns kFalse if the window is deleted.

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

JBoolean
JXWindow::HandleButtonPress
	(
	const XButtonEvent& xEvent
	)
{
	RequestFocus();

	const JXMouseButton currButton = static_cast<JXMouseButton>(xEvent.button);
	const unsigned int state = JXButtonStates::SetState(xEvent.state, currButton, kTrue);

	if (!itsIsDraggingFlag)
		{
		itsIsDraggingFlag  = kTrue;
		itsProcessDragFlag = kFalse;		// JXContainer tells us if it wants it

		const JPoint ptG(xEvent.x, xEvent.y);

		itsFirstClick         = itsSecondClick;
		itsSecondClick.button = currButton;
		itsSecondClick.time   = xEvent.time;
		// itsSecondClick.pt calculated by JXContainer that accepts click

		JXDisplay* display = itsDisplay;	// need local copy, since we might be deleted
		Display* xDisplay  = *display;
		Window xWindow     = itsXWindow;

		itsButtonPressReceiver->
			DispatchNewMouseEvent(ButtonPress, ptG, currButton, state);

		if (!JXDisplay::WindowExists(display, xDisplay, xWindow))
			{
			return kFalse;
			}

		itsCursorLeftFlag = kFalse;
		}

	else if (itsIsDraggingFlag && itsProcessDragFlag && itsMouseContainer != NULL)
		{
		const JPoint pt = itsMouseContainer->GlobalToLocal(xEvent.x, xEvent.y);
		itsMouseContainer->MouseDown(pt, currButton, 1,
									 JXButtonStates(state),
									 JXKeyModifiers(state));
		}

	// If a blocking dialog window was popped up, then we will never get
	// the ButtonRelease event because HandleOneEventForWindow() tossed it.

	if ((JXGetApplication())->HadBlockingWindow())
		{
		itsCleanAfterBlockFlag = kTrue;
		if (itsIsDraggingFlag && itsProcessDragFlag && itsMouseContainer != NULL)
			{
			EndDrag(itsMouseContainer, JPoint(xEvent.x, xEvent.y),
					JXButtonStates(state), JXKeyModifiers(state));
			}
		itsIsDraggingFlag      = kFalse;
		itsCleanAfterBlockFlag = kFalse;
		}

	return kTrue;
}

/******************************************************************************
 CountClicks

	pt must be in the local coordinates of the given container.

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

JSize
JXWindow::CountClicks
	(
	JXContainer*	container,
	const JPoint&	pt
	)
{
	assert( itsMouseContainer != NULL && container == itsMouseContainer );

	itsSecondClick.pt = pt;

	if (itsPrevMouseContainer == itsMouseContainer &&
		itsFirstClick.button == itsSecondClick.button &&
		itsSecondClick.time - itsFirstClick.time <= kJXDoubleClickTime &&
		itsMouseContainer->HitSamePart(itsFirstClick.pt, itsSecondClick.pt))
		{
		itsClickCount++;
		}
	else
		{
		itsClickCount = 1;
		}

	// set it here so it stays NULL until after we check it the first time

	itsPrevMouseContainer = itsMouseContainer;

	return itsClickCount;
}

/******************************************************************************
 HandleButtonRelease (private)

	Returns kFalse if the window is deleted.

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

JBoolean
JXWindow::HandleButtonRelease
	(
	const XButtonEvent& xEvent
	)
{
	if (!itsIsDraggingFlag)
		{
		// We can't use assert() because JXMenuTable gets blasted after
		// the left button is released, while an other button might still
		// be down.  (pretty unlikely, but still possible)

		return kTrue;
		}

	const JXMouseButton currButton = static_cast<JXMouseButton>(xEvent.button);
	const unsigned int state = JXButtonStates::SetState(xEvent.state, currButton, kFalse);

	if (itsProcessDragFlag && itsMouseContainer != NULL)
		{
		JXDisplay* display = itsDisplay;	// need local copy, since we might be deleted
		Display* xDisplay  = *display;
		Window xWindow     = itsXWindow;

		const JPoint pt = itsMouseContainer->GlobalToLocal(xEvent.x, xEvent.y);
		itsMouseContainer->DispatchMouseUp(pt, currButton,
										   JXButtonStates(state),
										   JXKeyModifiers(state));	// this could delete us

		if (!JXDisplay::WindowExists(display, xDisplay, xWindow))
			{
			return kFalse;
			}
		}

	// If drag is finished, release the pointer and process buffered LeaveNotify.

	if (JXButtonStates::AllOff(state))
		{
		itsIsDraggingFlag = kFalse;
		if (!itsPointerGrabbedFlag)
			{
			// balance XGrabPointer() in BeginDrag()
			XUngrabPointer(*itsDisplay, xEvent.time);
			itsDisplay->SetMouseGrabber(NULL);
			}
		if (itsCursorLeftFlag)
			{
			SetMouseContainer(NULL, JPoint(xEvent.x, xEvent.y), state);
			}
		}

	// If a blocking dialog window was popped up, then we will never get
	// other ButtonRelease events because HandleOneEventForWindow() tossed them.

	else if ((JXGetApplication())->HadBlockingWindow() && !itsCleanAfterBlockFlag)
		{
		itsCleanAfterBlockFlag = kTrue;
		if (itsIsDraggingFlag && itsProcessDragFlag && itsMouseContainer != NULL)
			{
			EndDrag(itsMouseContainer, JPoint(xEvent.x, xEvent.y),
					JXButtonStates(state), JXKeyModifiers(state));
			}
		itsIsDraggingFlag      = kFalse;
		itsCleanAfterBlockFlag = kFalse;
		}

	return kTrue;
}

/******************************************************************************
 SetMouseContainer

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

void
JXWindow::SetMouseContainer
	(
	JXContainer*		obj,
	const JPoint&		ptG,
	const unsigned int	state
	)
{
	if (itsMouseContainer != obj)
		{
		if (itsMouseContainer != NULL)
			{
			itsMouseContainer->MouseLeave();
			itsMouseContainer->DeactivateCursor();
			}

		itsMouseContainer = obj;
		if (itsMouseContainer != NULL)
			{
			itsMouseContainer->ActivateCursor(ptG, JXKeyModifiers(state));
			itsMouseContainer->MouseEnter();
			}
		}
}

/******************************************************************************
 BeginDrag

	Generate fake messages to get the given widget ready for HandleMouseDrag().

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

JBoolean
JXWindow::BeginDrag
	(
	JXContainer*			obj,
	const JPoint&			ptG,
	const JXButtonStates&	buttonStates,
	const JXKeyModifiers&	modifiers
	)
{
	assert( obj->GetWindow() == this && !itsIsDraggingFlag );
	assert( !buttonStates.AllOff() );

	if (XGrabPointer(*itsDisplay, itsXWindow, False, kPointerGrabMask,
					 GrabModeAsync, GrabModeAsync, None, None, CurrentTime) !=
		GrabSuccess)
		{
		return kFalse;
		}
	itsDisplay->SetMouseContainer(this);
	itsDisplay->SetMouseGrabber(this);

	JXContainer* savedReceiver = itsButtonPressReceiver;
	itsButtonPressReceiver     = obj;
	itsBPRChangedFlag          = kFalse;

	XButtonPressedEvent xEvent;
	xEvent.type = ButtonPress;
	xEvent.time = CurrentTime;
	xEvent.x    = ptG.x;
	xEvent.y    = ptG.y;

	JXButtonStates newButtonStates;
	for (JIndex i=1; i<=kXButtonCount; i++)
		{
		if (buttonStates.button(i))
			{
			xEvent.state  = newButtonStates.GetState() | modifiers.GetState();
			xEvent.button = i;
			if (!HandleButtonPress(xEvent))
				{
				return kFalse;		// window was deleted
				}
			newButtonStates.SetState(i, kTrue);
			}
		}

	if (!itsBPRChangedFlag)
		{
		itsButtonPressReceiver = savedReceiver;
		}
	return itsProcessDragFlag;
}

/******************************************************************************
 EndDrag

	Generate fake messages to the current drag recipient so that it thinks
	that the mouse was released.

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

void
JXWindow::EndDrag
	(
	JXContainer*			obj,
	const JPoint&			ptG,
	const JXButtonStates&	buttonStates,
	const JXKeyModifiers&	modifiers
	)
{
	assert( itsMouseContainer == obj && itsIsDraggingFlag && itsProcessDragFlag );
	assert( !buttonStates.AllOff() );

	XButtonReleasedEvent xEvent;
	xEvent.type = ButtonRelease;
	xEvent.time = CurrentTime;
	xEvent.x    = ptG.x;
	xEvent.y    = ptG.y;

	JXButtonStates newButtonStates(buttonStates);
	for (JIndex i=1; i<=kXButtonCount; i++)
		{
		if (buttonStates.button(i))
			{
			xEvent.state  = newButtonStates.GetState() | modifiers.GetState();
			xEvent.button = i;
			if (!HandleButtonRelease(xEvent))
				{
				return;		// window was deleted
				}
			newButtonStates.SetState(i, kFalse);
			}
		}

	XUngrabPointer(*itsDisplay, CurrentTime);
	itsDisplay->SetMouseGrabber(NULL);
}

/******************************************************************************
 GrabPointer

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

JBoolean
JXWindow::GrabPointer
	(
	JXContainer* obj
	)
{
	assert( obj->GetWindow() == this );

	const int success =
		XGrabPointer(*itsDisplay, itsXWindow, False, kPointerGrabMask,
					 GrabModeAsync, GrabModeAsync, None, None, CurrentTime);

	if (success == GrabSuccess)
		{
		itsPointerGrabbedFlag  = kTrue;
		itsButtonPressReceiver = obj;
		itsBPRChangedFlag      = kTrue;		// notify BeginDrag()
		itsDisplay->SetMouseContainer(this);
		itsDisplay->SetMouseGrabber(this);
		return kTrue;
		}
	else
		{
		return kFalse;
		}
}

/******************************************************************************
 UngrabPointer

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

void
JXWindow::UngrabPointer
	(
	JXContainer* obj
	)
{
	assert( obj == itsButtonPressReceiver );

	XUngrabPointer(*itsDisplay, CurrentTime);
	itsPointerGrabbedFlag  = kFalse;
	itsButtonPressReceiver = this;
	itsBPRChangedFlag      = kTrue;		// notify BeginDrag()
	itsDisplay->SetMouseGrabber(NULL);
}

/******************************************************************************
 HandleFocusIn (private)

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

void
JXWindow::HandleFocusIn
	(
	const XFocusChangeEvent& xEvent
	)
{
	itsHasFocusFlag = kTrue;
	if (itsFocusWidget != NULL)
		{
		itsFocusWidget->HandleWindowFocusEvent();
		}
}

/******************************************************************************
 HandleFocusOut (private)

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

void
JXWindow::HandleFocusOut
	(
	const XFocusChangeEvent& xEvent
	)
{
	itsHasFocusFlag = kFalse;
	if (itsFocusWidget != NULL)
		{
		itsFocusWidget->HandleWindowUnfocusEvent();
		}
}

/******************************************************************************
 HandleKeyPress (private)

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

void
JXWindow::HandleKeyPress
	(
	const XEvent& xEvent	// avoids cast in call to JXGetButtonAndModifierStates()
	)
{
	if (!IsActive())
		{
		return;
		}

	JCharacter buffer[10];
	KeySym keySym;
	JSize charCount =
		XLookupString(const_cast<XKeyEvent*>(&(xEvent.xkey)), buffer, 10, &keySym, NULL);
	if (charCount == 0)
		{
		buffer[0] = '\0';
		}

	TossKeyRepeatEvents(xEvent.xkey.keycode, xEvent.xkey.state, keySym);

	// translate some useful keys

	if (keySym == XK_KP_Tab || keySym == XK_ISO_Left_Tab)
		{
		keySym = XK_Tab;
		}
	else if (keySym == XK_Left || keySym == XK_KP_Left)
		{
		charCount = 1;
		buffer[0] = kJLeftArrow;
		buffer[1] = '\0';
		}
	else if (keySym == XK_Up || keySym == XK_KP_Up)
		{
		charCount = 1;
		buffer[0] = kJUpArrow;
		buffer[1] = '\0';
		}
	else if (keySym == XK_Right || keySym == XK_KP_Right)
		{
		charCount = 1;
		buffer[0] = kJRightArrow;
		buffer[1] = '\0';
		}
	else if (keySym == XK_Down || keySym == XK_KP_Down)
		{
		charCount = 1;
		buffer[0] = kJDownArrow;
		buffer[1] = '\0';
		}

	// the control modifier causes these to be mis-interpreted

	else if (XK_space <= keySym && keySym <= XK_question)
		{
		charCount = 1;
		buffer[0] = keySym;
		buffer[1] = '\0';
		}
	else if (XK_KP_0 <= keySym && keySym <= XK_KP_9)
		{
		charCount = 1;
		buffer[0] = '0' + (keySym - XK_KP_0);
		buffer[1] = '\0';
		}

	// dispatch key

	unsigned int state;
	const JBoolean foundState = JXGetButtonAndModifierStates(xEvent, itsDisplay, &state);
	assert( foundState );

	const JXKeyModifiers modifiers(state);

	JXKeyModifiers noShift(modifiers);
	noShift.SetState(kJXShiftKeyIndex, kFalse);
	noShift.SetState(kJXShiftLockKeyIndex, kFalse);
	const JBoolean modsOff = noShift.AllOff();

	// ESC cancels Drag-And-Drop

	if (charCount == 1 && buffer[0] == kJEscapeKey &&
		(GetDNDManager())->IsDragging())
		{
		if ((GetDNDManager())->CancelDND())
			{
			EndDrag(itsMouseContainer, JPoint(0,0), JXButtonStates(state), modifiers);
			}

		// We won't dispatch any motion events until the user releases
		// the mouse, but the user needs some reassurance that the drop
		// really has been cancelled.

		DisplayXCursor(kJXDefaultCursor);
		}

	// We check WillAcceptFocus() because we don't want to send keypresses
	// to invisible or inactive widgets.

	else if (keySym == XK_Tab && itsFocusWidget != NULL &&
			 itsFocusWidget->WillAcceptFocus() &&
			 (( modsOff && itsFocusWidget->WantsTab()) ||
			  (!modsOff && itsFocusWidget->WantsModifiedTab())))
		{
		// We send tab directly to the focus widget so it
		// doesn't get lost as a shortcut.

		DeactivateHint();
		itsFocusWidget->HandleKeyPress(kJTabKey, modifiers);
		}
	else if (keySym == XK_Tab)
		{
		SwitchFocus(modifiers.shift());
		}
	else if (buffer[0] != '\0' && IsShortcut((unsigned char) buffer[0], keySym, state))
		{
		// IsShortcut() dispatches event
		}
	else if (itsFocusWidget != NULL && itsFocusWidget->WillAcceptFocus())
		{
		DeactivateHint();

		if (charCount > 0)
			{
			JXDisplay* display = itsDisplay;	// need local copy, since we might be deleted
			Display* xDisplay  = *display;
			Window xWindow     = itsXWindow;

			for (JIndex i=0; i<charCount; i++)
				{
				itsFocusWidget->HandleKeyPress((unsigned char) buffer[i], modifiers);
				if (charCount > 1 && i < charCount-1 &&
					!JXDisplay::WindowExists(display, xDisplay, xWindow))
					{
					break;
					}
				}
			}
		else
			{
			itsFocusWidget->HandleKeyPress(keySym, modifiers);
			}
		}
}

/******************************************************************************
 TossKeyRepeatEvents (private)

	If the user holds down a key, we don't want to accumulate a backlog of
	KeyPress,KeyRelease events.

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

void
JXWindow::TossKeyRepeatEvents
	(
	const unsigned int	keycode,
	const unsigned int	state,
	const KeySym		keySym
	)
{
	if (keySym != XK_Left  && keySym != XK_KP_Left &&
		keySym != XK_Up    && keySym != XK_KP_Up &&
		keySym != XK_Right && keySym != XK_KP_Right &&
		keySym != XK_Down  && keySym != XK_KP_Down &&

		keySym != XK_Page_Up   && keySym != XK_KP_Page_Up &&
		keySym != XK_Page_Down && keySym != XK_KP_Page_Down &&

		keySym != XK_BackSpace && keySym != XK_Delete)
		{
		return;
		}

	XEvent xEvent;
	while (XPending(*itsDisplay) > 0)
		{
		XPeekEvent(*itsDisplay, &xEvent);
		if ((xEvent.type == KeyPress || xEvent.type == KeyRelease) &&
			xEvent.xkey.keycode == keycode &&
			xEvent.xkey.state   == state)
			{
			XNextEvent(*itsDisplay, &xEvent);
			}
		else
			{
			break;
			}
		}
}

/******************************************************************************
 DeactivateHint

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

void
JXWindow::DeactivateHint()
{
	if (itsCurrHintMgr != NULL)
		{
		itsCurrHintMgr->Deactivate();
		itsCurrHintMgr = NULL;
		}
}

/******************************************************************************
 RegisterFocusWidget

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

void
JXWindow::RegisterFocusWidget
	(
	JXWidget* widget
	)
{
	if (!itsFocusList->Includes(widget))
		{
		itsFocusList->Append(widget);
		if (itsFocusList->GetElementCount() == 1)
			{
			assert( itsFocusWidget == NULL );
			SwitchFocus(kFalse);
			}
		}
}

/******************************************************************************
 UnregisterFocusWidget

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

void
JXWindow::UnregisterFocusWidget
	(
	JXWidget* widget
	)
{
	if (!itsIsDestructingFlag)
		{
		itsFocusList->Remove(widget);
		if (widget == itsFocusWidget)
			{
			itsFocusWidget = NULL;
			SwitchFocus(kFalse);
			}
		}
}

/******************************************************************************
 SwitchFocusToFirstWidget

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

JBoolean
JXWindow::SwitchFocusToFirstWidget()
{
	JXWidget* firstWidget;
	if (!FindNextFocusWidget(0, &firstWidget))
		{
		return kTrue;
		}

	if (itsFocusWidget == firstWidget)
		{
		return kTrue;
		}
	else if (UnfocusCurrentWidget())
		{
		itsFocusWidget = firstWidget;
		itsFocusWidget->Focus(0);
		return kTrue;
		}
	else
		{
		return kFalse;
		}
}

/******************************************************************************
 SwitchFocusToFirstWidgetWithAncestor

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

JBoolean
JXWindow::SwitchFocusToFirstWidgetWithAncestor
	(
	JXContainer* ancestor
	)
{
	JXWidget* firstWidget = NULL;

	const JSize count = itsFocusList->GetElementCount();
	for (JIndex i=1; i<=count; i++)
		{
		JXWidget* widget = itsFocusList->NthElement(i);
		if (widget->WillAcceptFocus() &&
			ancestor->IsAncestor(widget))
			{
			firstWidget = widget;
			break;
			}
		}

	if (firstWidget == NULL || itsFocusWidget == firstWidget)
		{
		return kTrue;
		}
	else if (UnfocusCurrentWidget())
		{
		itsFocusWidget = firstWidget;
		itsFocusWidget->Focus(0);
		return kTrue;
		}
	else
		{
		return kFalse;
		}
}

/******************************************************************************
 OKToUnfocusCurrentWidget

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

JBoolean
JXWindow::OKToUnfocusCurrentWidget()
	const
{
	if (itsFocusWidget == NULL ||
		itsFocusWidget->OKToUnfocus())
		{
		return kTrue;
		}
	else
		{
		return kFalse;
		}
}

/******************************************************************************
 UnfocusCurrentWidget

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

JBoolean
JXWindow::UnfocusCurrentWidget()
{
	if (itsFocusWidget == NULL)
		{
		return kTrue;
		}
	else if (itsFocusWidget->OKToUnfocus())
		{
		KillFocus();
		return kTrue;
		}
	else
		{
		return kFalse;
		}
}

/******************************************************************************
 KillFocus

	*** This does not check for valid input.  Use with caution!

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

void
JXWindow::KillFocus()
{
	if (itsFocusWidget != NULL)
		{
		JXWidget* origWidget = itsFocusWidget;
		itsFocusWidget = NULL;		// clear this first
		origWidget->NotifyFocusLost();
		}
}

/******************************************************************************
 SwitchFocusToWidget

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

JBoolean
JXWindow::SwitchFocusToWidget
	(
	JXWidget* widget
	)
{
	if (itsFocusWidget == widget)
		{
		return kTrue;
		}
	else if (UnfocusCurrentWidget() &&
			 itsFocusList->Includes(widget) &&
			 widget->WillAcceptFocus())
		{
		itsFocusWidget = widget;
		itsFocusWidget->Focus(0);
		return kTrue;
		}

	return kFalse;
}

/******************************************************************************
 SwitchFocus (private)

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

void
JXWindow::SwitchFocus
	(
	const JBoolean backward
	)
{
	if (itsFocusList->IsEmpty())
		{
		// nothing to do
		assert( itsFocusWidget == NULL );
		}
	else if (itsFocusWidget == NULL && backward)
		{
		if (FindPrevFocusWidget(0, &itsFocusWidget))
			{
			itsFocusWidget->Focus(0);
			}
		}
	else if (itsFocusWidget == NULL)
		{
		if (FindNextFocusWidget(0, &itsFocusWidget))
			{
			itsFocusWidget->Focus(0);
			}
		}
	else if (itsFocusWidget->OKToUnfocus())
		{
		JXWidget* widget = itsFocusWidget;
		KillFocus();

		// In the following code, we assume nothing about itsFocusList
		// because NotifyFocusLost() could do anything.  (Usually,
		// of course, it should not affect itsFocusList at all.)

		const JBoolean empty = itsFocusList->IsEmpty();

		JIndex currIndex;
		if (!empty && itsFocusList->Find(widget, &currIndex))
			{
			if (backward)
				{
				FindPrevFocusWidget(currIndex, &widget);
				}
			else
				{
				FindNextFocusWidget(currIndex, &widget);
				}
			}
		else if (!empty && backward)
			{
			FindPrevFocusWidget(0, &widget);
			}
		else if (!empty)
			{
			FindNextFocusWidget(0, &widget);
			}
		else
			{
			widget = NULL;
			}

		if (widget != NULL)
			{
			itsFocusWidget = widget;
			itsFocusWidget->Focus(0);
			}
		}
}

/******************************************************************************
 FindNextFocusWidget (private)

	Look forward in the list to find a widget after the specified one.
	If nothing can be found, sets *widget = NULL and returns kFalse.

	(startIndex == 0) => start at beginning of list

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

JBoolean
JXWindow::FindNextFocusWidget
	(
	const JIndex	origStartIndex,
	JXWidget**		focusWidget
	)
	const
{
	*focusWidget = NULL;

	const JSize count = itsFocusList->GetElementCount();
	if (count == 0)
		{
		return kFalse;
		}

	JIndex startIndex = origStartIndex;
	if (startIndex == 0 || startIndex > count)
		{
		startIndex = count;
		}

	JIndex i = startIndex+1;
	if (i > count)
		{
		i = 1;
		}

	while (1)
		{
		JXWidget* widget = itsFocusList->NthElement(i);
		if (widget->WillAcceptFocus())
			{
			*focusWidget = widget;
			return kTrue;
			}
		else if (i == startIndex)
			{
			return kFalse;
			}

		i++;
		if (i > count)
			{
			i = 1;
			}
		}
}

/******************************************************************************
 FindPrevFocusWidget (private)

	Look backwards in the list to find a widget in front of the specified one.
	If nothing can be found, sets *widget = NULL and returns kFalse.

	(startIndex == 0) => start at end of list

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

JBoolean
JXWindow::FindPrevFocusWidget
	(
	const JIndex	origStartIndex,
	JXWidget**		focusWidget
	)
	const
{
	*focusWidget = NULL;

	const JSize count = itsFocusList->GetElementCount();
	if (count == 0)
		{
		return kFalse;
		}

	JIndex startIndex = origStartIndex;
	if (startIndex == 0 || startIndex > count)
		{
		startIndex = 1;
		}

	JIndex i = startIndex-1;
	if (i == 0)
		{
		i = count;
		}

	while (1)
		{
		JXWidget* widget = itsFocusList->NthElement(i);
		if (widget->WillAcceptFocus())
			{
			*focusWidget = widget;
			return kTrue;
			}
		else if (i == startIndex)
			{
			return kFalse;
			}

		i--;
		if (i == 0)
			{
			i = count;
			}
		}
}

/******************************************************************************
 InstallShortcut

	To specify a control character, you must pass in the unmodified character
	along with modifiers.control().  e.g. Pass in 'A',control instead
	of JXCtrl('A'),control.

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

void
JXWindow::InstallShortcut
	(
	JXWidget*				widget,
	const int				key,
	const JXKeyModifiers&	modifiers
	)
{
	InstallShortcut(Shortcut(widget, key, modifiers.GetState()));
}

/******************************************************************************
 InstallMenuShortcut

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

void
JXWindow::InstallMenuShortcut
	(
	JXTextMenu*				menu,
	const JIndex			menuItem,
	const int				key,
	const JXKeyModifiers&	modifiers
	)
{
	InstallShortcut(Shortcut(menu, menuItem, key, modifiers.GetState()));
}

/******************************************************************************
 InstallShortcut (private)

	ShiftLock is always ignored.

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

void
JXWindow::InstallShortcut
	(
	const Shortcut& origShortcut
	)
{
	Shortcut s = origShortcut;
	s.state    = JXKeyModifiers::SetState(s.state, kJXShiftLockKeyIndex, kFalse);

	if (JXKeyModifiers::GetState(s.state, kJXControlKeyIndex))
		{
		s.key = JXCtrl(toupper(s.key));
		}

	itsShortcuts->InsertSorted(s, kFalse);

	// For characters other than the alphabet or tab, return, etc., we
	// never know whether or not the Shift key is required in order to type
	// them.  We therefore install both cases.

	if (!isalpha(origShortcut.key) && !iscntrl(origShortcut.key))
		{
		s.state = JXKeyModifiers::ToggleState(s.state, kJXShiftKeyIndex);
		itsShortcuts->InsertSorted(s, kFalse);
		}
}

/******************************************************************************
 MenuItemInserted

	Update the item indices of the shortcuts.

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

void
JXWindow::MenuItemInserted
	(
	JXTextMenu*		menu,
	const JIndex	newItem
	)
{
	const JSize count = itsShortcuts->GetElementCount();
	for (JIndex i=1; i<=count; i++)
		{
		Shortcut s = itsShortcuts->GetElement(i);
		if (s.menu == menu && s.menuItem >= newItem)
			{
			(s.menuItem)++;
			itsShortcuts->SetElement(i, s);
			}
		}
}

/******************************************************************************
 MenuItemRemoved

	Remove the shortcuts for the item and update the item indices of the
	other shortcuts.

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

void
JXWindow::MenuItemRemoved
	(
	JXTextMenu*		menu,
	const JIndex	oldItem
	)
{
	const JSize count = itsShortcuts->GetElementCount();
	for (JIndex i=count; i>=1; i--)
		{
		Shortcut s = itsShortcuts->GetElement(i);
		if (s.menu == menu && s.menuItem > oldItem)
			{
			(s.menuItem)--;
			itsShortcuts->SetElement(i, s);
			}
		else if (s.menu == menu && s.menuItem == oldItem)
			{
			itsShortcuts->RemoveElement(i);
			}
		}
}

/******************************************************************************
 InstallShortcuts

	Parses the given string to get the shortcuts.  A caret (^) indicates
	that the next character is a control character.  A hash (#) indicates that
	the next character requires the Meta modifier.

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

void
JXWindow::InstallShortcuts
	(
	JXWidget*			widget,
	const JCharacter*	list
	)
{
	if (list == NULL || list[0] == '\0')
		{
		return;
		}

	JXKeyModifiers modifiers;

	const JSize length = strlen(list);
	for (JIndex i=0; i<length; i++)
		{
		const int c = (unsigned char) list[i];
		if (c == '^')
			{
			i++;
			if (i < length)
				{
				const int c1 = toupper((unsigned char) list[i]);
				modifiers.SetState(kJXControlKeyIndex, kTrue);
				InstallShortcut(widget, c1, modifiers);				// e.g. Ctrl-M
				modifiers.SetState(kJXControlKeyIndex, kFalse);
				InstallShortcut(widget, (unsigned char) JXCtrl(c1), modifiers);		// e.g. return key
				}
			}
		else if (c == '#')
			{
			i++;
			if (i < length)
				{
				const int c1 = toupper((unsigned char) list[i]);
				const int c2 = tolower((unsigned char) list[i]);
				modifiers.SetState(kJXMetaKeyIndex, kTrue);
				InstallShortcut(widget, c1, modifiers);
				if (c2 != c1)
					{
					InstallShortcut(widget, c2, modifiers);
					}
				modifiers.SetState(kJXMetaKeyIndex, kFalse);
				}
			}
		else
			{
			InstallShortcut(widget, c, modifiers);
			}
		}
}

/******************************************************************************
 GetULShortcutIndex (static)

	Returns the index into label of the first shortcut character in
	the given list.  Returns zero if the first shortcut character is not
	found in label.  We return zero rather than JBoolean because
	JXWindowPainter::String() accepts zero to mean "no shortcut".

	We perform a case-insensitive search first for "^c", then " c",
	then "[^a-z]c", and finally anywhere.

	For convenience, list can be NULL.

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

JIndex
JXWindow::GetULShortcutIndex
	(
	const JString&	label,
	const JString*	list
	)
{
	if (label.IsEmpty() || list == NULL || list->IsEmpty())
		{
		return 0;
		}

	JCharacter c = list->GetCharacter(1);
	if (c == '^' || c == '#')
		{
		if (list->GetLength() < 2)
			{
			return 0;
			}
		c = list->GetCharacter(2);
		}
	c = tolower(c);

	if (c == tolower(label.GetFirstCharacter()))
		{
		return 1;
		}

	JIndex bdryIndex = 0, anyIndex = 0;
	const JSize length = label.GetLength();
	for (JIndex i=2; i<=length; i++)
		{
		if (c == tolower(label.GetCharacter(i)))
			{
			if (isspace(label.GetCharacter(i-1)))
				{
				return i;
				}
			else if (bdryIndex == 0 && !isalpha(label.GetCharacter(i-1)))
				{
				bdryIndex = i;
				}
			else if (anyIndex == 0)
				{
				anyIndex = i;
				}
			}
		}

	return (bdryIndex > 0 ? bdryIndex :
			(anyIndex > 0 ? anyIndex : 0));
}

/******************************************************************************
 ClearShortcuts

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

void
JXWindow::ClearShortcuts
	(
	JXWidget* widget
	)
{
	const JSize count = itsShortcuts->GetElementCount();
	for (JIndex i=count; i>=1; i--)
		{
		const Shortcut s = itsShortcuts->GetElement(i);
		if (s.widget == widget)
			{
			itsShortcuts->RemoveElement(i);
			}
		}
}

/******************************************************************************
 ClearMenuShortcut

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

void
JXWindow::ClearMenuShortcut
	(
	JXTextMenu*		menu,
	const JIndex	menuItem
	)
{
	const JSize count = itsShortcuts->GetElementCount();
	for (JIndex i=count; i>=1; i--)
		{
		const Shortcut s = itsShortcuts->GetElement(i);
		if (s.menu == menu && s.menuItem == menuItem)
			{
			itsShortcuts->RemoveElement(i);
			}
		}
}

/******************************************************************************
 ClearAllMenuShortcuts

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

void
JXWindow::ClearAllMenuShortcuts
	(
	JXTextMenu* menu
	)
{
	const JSize count = itsShortcuts->GetElementCount();
	for (JIndex i=count; i>=1; i--)
		{
		const Shortcut s = itsShortcuts->GetElement(i);
		if (s.menu == menu)
			{
			itsShortcuts->RemoveElement(i);
			}
		}
}

/******************************************************************************
 IsShortcut (private)

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

JBoolean
JXWindow::IsShortcut
	(
	const int			origKey,
	const KeySym		keySym,
	const unsigned int	origState
	)
{
	int key            = origKey;
	unsigned int state = origState;

	state &= 0xFF0F;	// mask out high 4 bits to remove NumLock, ScrollLock

	JXKeyModifiers modifiers(state);
	if (modifiers.shiftLock())
		{
		modifiers.SetState(kJXShiftLockKeyIndex, kFalse);
		state = modifiers.GetState();
		key   = tolower(key);
		}

	Shortcut target(NULL, key, state);
	JIndex i;
	JBoolean found = itsShortcuts->SearchSorted(target, JOrderedSetT::kAnyMatch, &i);
	if (!found)
		{
		target.key = keySym;
		found      = itsShortcuts->SearchSorted(target, JOrderedSetT::kAnyMatch, &i);
		}

	if (found)
		{
		const Shortcut s = itsShortcuts->GetElement(i);
		if (s.widget != NULL && (s.widget)->WillAcceptShortcut())
			{
			(s.widget)->HandleShortcut(s.key, modifiers);
			}
		else if (s.menu != NULL)
			{
			(s.menu)->HandleNMShortcut(s.menuItem, modifiers);
			}
		}

	// We always return kTrue if it is a shortcut, even if the Widget didn't
	// want to accept it.  This guarantees that keys will always behave
	// the same.  Otherwise, a deactivated button's shortcut would go to the
	// active input field.

	return found;
}

/******************************************************************************
 CompareShortcuts (static private)

	Sorts by key, then by state.

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

JOrderedSetT::CompareResult
JXWindow::CompareShortcuts
	(
	const Shortcut& s1,
	const Shortcut& s2
	)
{
	if (s1.key < s2.key)
		{
		return JOrderedSetT::kFirstLessSecond;
		}
	else if (s1.key > s2.key)
		{
		return JOrderedSetT::kFirstGreaterSecond;
		}
	else if (s1.state < s2.state)
		{
		return JOrderedSetT::kFirstLessSecond;
		}
	else if (s1.state > s2.state)
		{
		return JOrderedSetT::kFirstGreaterSecond;
		}
	else
		{
		return JOrderedSetT::kFirstEqualSecond;
		}
}

/******************************************************************************
 HandleMapNotify (private)

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

void
JXWindow::HandleMapNotify
	(
	const XMapEvent& mapEvent
	)
{
	itsIsMappedFlag = kTrue;

	if (itsIsMenuFlag)
		{
		// Menu windows are not touched by the window manager.
		return;
		}

	// focus to the window for convenience

	if (itsFocusWhenShowFlag)
		{
		RequestFocus();
		}

	// broadcast whether window is iconified or deiconified

	Atom actualType;
	int actualFormat;
	unsigned long itemCount, remainingBytes;
	unsigned char* xdata;

	// WindowMaker takes its own sweet time setting WM_STATE, so we wait up to 5 seconds.

	for (JIndex i=1; i<=10; i++)
		{
		const int result =
			XGetWindowProperty(*itsDisplay, itsXWindow, itsDisplay->GetWMStateXAtom(),
							   0, LONG_MAX, False, AnyPropertyType,
							   &actualType, &actualFormat,
							   &itemCount, &remainingBytes, &xdata);
		if (actualType == itsDisplay->GetWMStateXAtom())
			{
			break;
			}
		else
			{
/*			cout << "WM_STATE results=" << result << " atom=";
			if (actualType == None)
				{
				cout << "None";
				}
			else
				{
				cout << XGetAtomName(*itsDisplay, actualType) << endl;
				}
			cout << endl;
*/			}
		JWait(0.5);
		}
	if (actualType != itsDisplay->GetWMStateXAtom())
		{
		cerr << endl;
		cerr << "Error detected in JXWindow::HandleMapNotify():" << endl;
		cerr << "Your window manager is not setting the WM_STATE property correctly!" << endl;
		cerr << endl;
		JXApplication::Abort(JXDocumentManager::kServerDead, kFalse);
		}
	assert( actualFormat == 32 );
	assert( remainingBytes == 0 );

	if (*reinterpret_cast<long*>(xdata) == NormalState && itsIsIconifiedFlag)
		{
		itsIsIconifiedFlag = kFalse;
		Broadcast(Deiconified());
		}
	else if (*reinterpret_cast<long*>(xdata) == IconicState && !itsIsIconifiedFlag)
		{
		itsIsIconifiedFlag = kTrue;
		Broadcast(Iconified());
		}

	XFree(xdata);

	// The user only expects the window to start iconified the first time.

	SetWindowStateHint(NormalState);
}

/******************************************************************************
 HandleUnmapNotify (private)

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

void
JXWindow::HandleUnmapNotify
	(
	const XUnmapEvent& unmapEvent
	)
{
	itsIsMappedFlag = kFalse;

	if (IsVisible() && !itsIsIconifiedFlag)
		{
		itsIsIconifiedFlag = kTrue;
		Broadcast(Iconified());
		}
}

/******************************************************************************
 CheckForMapOrExpose

	For use by blocking loops that want to refresh a particular window.
	This isn't safe in general because it can invoke huge amounts of
	code.  Use with caution.

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

void
JXWindow::CheckForMapOrExpose()
{
	XEvent xEvent;
	while (XCheckIfEvent(*itsDisplay, &xEvent, GetNextMapOrExposeEvent,
						 reinterpret_cast<char*>(&itsXWindow)))
		{
		HandleEvent(xEvent);
		}
}

// static private

Bool
JXWindow::GetNextMapOrExposeEvent
	(
	Display*	display,
	XEvent*		event,
	char*		arg
	)
{
	const Window window    = (event->xany).window;
	const Window itsWindow = *(reinterpret_cast<Window*>(arg));
	if (window == itsWindow &&
		(event->type == MapNotify || event->type == UnmapNotify ||
		 event->type == Expose))
		{
		return True;
		}
	else
		{
		return False;
		}
}

#define JTemplateType JXWindow::Shortcut
#include <JArray.tmpls>
#undef JTemplateType

#define JTemplateType JXWindow
#include <JPtrArray.tmpls>
#undef JTemplateType
