/*********************************************************************
 *
 *         EZWGL, the EZ Widget and Graphics Library
 *
 *             Copyright (C) 1996, 1997  Maorong Zou
 *  
 * 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 (at your option) 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
 * MERCHANTABILITY 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 **********************************************************************/
/*
 *  June 1996.  Beta Release.
 *  Sept 1996.  Release Version 1.0
 *  Dec 1996.  Release Version 1.1 Beta
 *  April 1997.  Release Version 1.2
 *  November 1997.  Release Version 1.3
 */
/*******************************************************************
 *  
 *  Set Keyboard Focusing. 
 *              
 */

#define _EZ_FOCUS_C_
#include "EZ_Widget.h"
#include <X11/Xatom.h>
#include <sys/time.h>
#include <unistd.h>
#include <ctype.h>

/*******************************************************************
 *
 * Functions implemented in this file:
 */
int  EZ_FocusNextWidget MY_ANSIARGS(( EZ_Widget *widget, int dir));
void EZ_SetFocusTo MY_ANSIARGS(( EZ_Widget *widget));
void EZ_ResetFocus MY_ANSIARGS((EZ_Widget *widget, int flag));
EZ_Widget *EZ_FindFocusingWidget MY_ANSIARGS(( EZ_Widget *widget));
EZ_Widget *EZ_GetFocus MY_ANSIARGS((void));
void EZ_ClearFocusRecord MY_ANSIARGS((void));
/*******************************************************************
 *
 * Global Variables:
 */
EZ_Widget *EZ_CurrentFocusingWidget = (EZ_Widget *)NULL; 
EZ_Widget *EZ_PendingFocus =  (EZ_Widget *)NULL; 
int        EZ_WeAreInFocus = 0;
/*******************************************************************
 *
 *  For manipulating keyboard focus. For each toplevel widget, 
 *  there is an entry in the list.
 */
typedef struct  _EZ_FocusRecord_ {
  EZ_Widget *toplevel;
  EZ_Widget *focus;
  struct  _EZ_FocusRecord_ *next;
} EZ_FocusRecord;

static  EZ_FocusRecord *EZ_FocusList = (EZ_FocusRecord *)NULL;

/*******************************************************************
 *
 * Local Functions.
 */
static void ExtractFocusableWidgets MY_ANSIARGS((EZ_Widget *widget,
						 EZ_Widget *root,
						 EZ_Widget **list,
						 int       *nwidgets, 
						 int       *mark, 
						 int       *markA,
						 EZ_FocusRecord *finfo));
static EZ_FocusRecord *findOrCreateFocusRecord 
                      MY_ANSIARGS(( EZ_Widget *root, EZ_Widget *widget));
static void EZ_SetInputFocus
                          MY_ANSIARGS(( EZ_Widget *root, EZ_Widget *widget,
						 EZ_FocusRecord *finfo, int wtime));
/*******************************************************************
 * Each time this function is called, it builds a list of all widgets
 * on the current widget tree. ( it's really not necessary !). It then
 * find a focusable widget on the list.
 */
int EZ_FocusNextWidget(widget, direction)
     EZ_Widget *widget; int direction; /* 0 ==>, !=0  <== */
{
  if(widget)
    {
      EZ_Widget *wlist[256];
      int        nwidgets = 0, mark = -1, markA = -1;
      EZ_Widget *root = widget;
      EZ_FocusRecord *finfo = NULL;
      
      /* find the root */
      while(EZ_WidgetParent(root))
	root = EZ_WidgetParent(root);

      /* make a list */
      if(EZ_WidgetMapped(root))
	{
	  finfo = findOrCreateFocusRecord(root, widget); 
	  ExtractFocusableWidgets(widget, root, wlist, &nwidgets, &mark, &markA, finfo);
	}
      
      if(nwidgets == 0) return(0); /* something is wrong ! */
      else
	{
	  int       i, j;
	  EZ_Widget *tmp;

	  if(mark == -1 && markA >= 0) mark = markA;
	  if(direction == 0)  /* move forward */
	    {
	      for(i=0, j = mark+1; i < nwidgets && j != mark;  i++, j++)
		{
		  if(j == nwidgets) j = 0;
		  tmp = wlist[j];
		  if(EZ_WidgetMapped(tmp))
		    {
		      EZ_SetInputFocus(root, tmp, finfo, 0);
		      break;
		    }
		}
	    }
	  else
	    {
	      for(i=0, j = mark-1; i < nwidgets && j != mark;  i++, j--)
		{
		  if(j < 0 ) j = nwidgets - 1;
		  tmp = wlist[j];
		  if(EZ_WidgetMapped(tmp))
		    {
		      EZ_SetInputFocus(root, tmp, finfo,0);
		      break;
		    }
		}
	    }
	}
    }
  return(1);
}

/*******************************************************************
 *
 *  Extract the focusable widgets on the current widget tree.
 *  root is a toplevel widget, widget is a descendant of root.
 *
 */
static void ExtractFocusableWidgets(widget, root, list, nwidgets, mark, markA, finfo)
     EZ_Widget *widget,*root, **list;
     int       *nwidgets, *mark, *markA;
     EZ_FocusRecord *finfo;
{
  if(EZ_WidgetIsViewable(root) && EZ_GetWidgetDisabledFlag(root)==0)
    {
      Window rootWin = EZ_WidgetWindow(root);
      Window root_return;
      Window parent_return;
      unsigned int nchildren_return;
      Window *children_return = (Window *)NULL;
      
      if(finfo == NULL) finfo = findOrCreateFocusRecord(root, widget); 
      
      if(EZ_GetWidgetFocusableFlag(root) || EZ_GetWidgetEmbededFlag(root))
	{
	  if(root == widget) *markA = *nwidgets;
	  else { if(root == finfo->focus) *mark = *nwidgets; }
	  list[*nwidgets] = root;
	  *nwidgets += 1;
	  if(*nwidgets >= 256) *nwidgets = 0;
	}  

      if(XQueryTree(EZ_Display,rootWin, &root_return, &parent_return,
		    &children_return, &nchildren_return))
	{
	  int Rw = EZ_WidgetWidth(root);
	  int Rh = EZ_WidgetHeight(root);
	  int i;
	  for(i = 0; i < nchildren_return; i++)
	    {
	      EZ_Widget *child = EZ_LookupWidgetFromMappedHT(children_return[i]);
	      if(child)
		{
		  int x = EZ_WidgetOriginX(child);
		  int y = EZ_WidgetOriginY(child);
		  int w = EZ_WidgetWidth(child);
		  int h = EZ_WidgetHeight(child);
		  
		  /* must be completely visible ! */
		  if(x >= 0 && x+w <= Rw && y >= 0 && y +h <= Rh)
		    ExtractFocusableWidgets(widget, child, list, nwidgets, mark, markA, finfo);
		}
	    }
	  if(children_return != (Window *)NULL) XFree(children_return);
	}
    }
}
/***************************************************************
 *
 * The calling function has to find the root of widget and verify 
 * that the windows are viewable.
 */
extern Time EZ_LastEventTime;

static void EZ_SetInputFocus(root, widget, finfo, wtime)
     EZ_Widget *root, *widget;
     EZ_FocusRecord *finfo;
     int wtime;
{
  if(root)
    {
      int            (*OldErrorHandler)();
      {
	/* if widget has the focus, return */
	Window fw; int rt;
	EZ_Widget *tmp;
	XSync(EZ_Display, False);      
	XGetInputFocus(EZ_Display, &fw, &rt);
	tmp = EZ_LookupWidgetFromMappedHT(fw);
	if(tmp == widget) 
	  {
	    EZ_CurrentFocusingWidget = widget; 
	    EZ_WeAreInFocus = 1; 
	    EZ_DrawFocusIndicator(widget, None, FocusIn); 
	    return;
	  }
      }
      
      if(finfo == NULL) finfo = findOrCreateFocusRecord(root, widget);
      EZ_XErrorCode = 0;
      OldErrorHandler = XSetErrorHandler(EZ_XErrorHandler);
      finfo->focus = widget;
      XSetInputFocus(EZ_Display, EZ_WidgetWindow(widget), RevertToParent, 
		     (wtime == 0? EZ_LastEventTime: CurrentTime));      
      XSync(EZ_Display, False);
      XSetErrorHandler(OldErrorHandler);  
      if(EZ_XErrorCode == 0)
	{
	  EZ_CurrentFocusingWidget = widget; 
	  if(wtime != 0) EZ_PendingFocus = widget;
	}
      else {  EZ_CurrentFocusingWidget = NULL;}
    }
}
/***************************************************************/
EZ_Widget *EZ_FindFocusingWidget(widget)
     EZ_Widget *widget;
{
  if(widget)
    {
      EZ_FocusRecord *finfo = findOrCreateFocusRecord(NULL, widget);
      return(finfo->focus);
    }
  return(NULL);
}
/***************************************************************/
void EZ_SetFocusTo(widget)
     EZ_Widget *widget;
{
  if(EZ_GetWidgetDisabledFlag(widget)== 0 && EZ_WidgetIsViewable(widget))
    {
      EZ_FocusRecord *finfo;
      EZ_Widget *root = widget;
      
      while(EZ_WidgetParent(root)) root = EZ_WidgetParent(root);
      finfo = findOrCreateFocusRecord(root, widget);

      EZ_SetInputFocus(root, widget, finfo, 0);
    }
}
/*******************************************************************/
extern EZ_Widget *EZ_GrabedWidget;

void EZ_ResetFocus(widget, flag)
     EZ_Widget *widget; int flag;
{
  EZ_FocusRecord *finfo;
  EZ_Widget *root, *tmp;
  int reset = 1;
  if(EZ_GrabedWidget && EZ_WidgetIsViewable(EZ_GrabedWidget))
    {
      root = EZ_GrabedWidget;
      while(EZ_WidgetParent(root)) root = EZ_WidgetParent(root);
      finfo = findOrCreateFocusRecord(root, widget);
      tmp = finfo->focus;
      if(tmp != NULL && EZ_WidgetIsViewable(tmp))
	{
	  reset = 0;
	  EZ_SetInputFocus(root, tmp, finfo, flag); 
	}
    }
  if(reset)
    {
      root = widget;
      while(EZ_WidgetParent(root)) root = EZ_WidgetParent(root);
      finfo = findOrCreateFocusRecord(root, widget);
      tmp = finfo->focus;
      if(tmp != NULL && EZ_WidgetIsViewable(tmp))
	EZ_SetInputFocus(root, tmp, finfo, flag);
      else 
	{
	  /*
	   * This part is used only when pointer enters an icon/toplevel
	   * window(toplevel windows that are invisible by the window managger),
	   * and the toplevel window is supposed to take focus. We have to
	   * handle two situations.
	   *  1. the widget has children widgets and focus has previously
	   *     set to one of them. This case is handled normally above.
	   *  2. the widget is a leaf widget or no explicit focus has been
	   *     set to its children widgets. We set focus to the toplevel
	   *     in this case. The next line takes care of this situation.
	   */
	  if(flag == 100 && EZ_WidgetIsViewable(widget))
	    EZ_SetInputFocus(root, widget, finfo, 0);
	}
    }
}

/*******************************************************************/
void EZ_RestGVFocusC() 						 
{
  EZ_CurrentFocusingWidget = (EZ_Widget *)NULL;
  EZ_PendingFocus =  (EZ_Widget *)NULL;
  EZ_FocusList = (EZ_FocusRecord *)NULL;
  EZ_ClearFocusRecord();
}
/*******************************************************************/
        
static EZ_FocusRecord *findOrCreateFocusRecord(root, widget)
     EZ_Widget *root, *widget;
{
  EZ_FocusRecord *ptr= EZ_FocusList;
  EZ_Widget *toplevel = root;

  if(toplevel == NULL)
    {
      toplevel = widget;
      while(EZ_WidgetParent(toplevel))
	toplevel= EZ_WidgetParent(toplevel);
    }

  while(ptr != NULL)
    {
      if(ptr->toplevel == toplevel) return(ptr);
      else ptr = ptr->next;
    }
  /* so we don't have it, create a new entry */
  ptr = (EZ_FocusRecord *)my_malloc(sizeof(EZ_FocusRecord),_FOCUS_RECORD_);
  ptr->toplevel = toplevel;
  ptr->focus = NULL;
  ptr->next = EZ_FocusList;
  EZ_FocusList = ptr;

  return(ptr);
}
/*******************************************************************/
void EZ_ClearFocusRecord()
{
  EZ_FocusRecord *tmp, *ptr= EZ_FocusList;
  while(ptr != NULL)
    {
      tmp = ptr;
      ptr = ptr->next;
      (void)my_free(tmp);
    }
  EZ_FocusList = NULL;
  EZ_WeAreInFocus = 0;
}
EZ_Widget *EZ_GetFocus()
{
  return(EZ_CurrentFocusingWidget);
}
/*******************************************************************/
#undef _EZ_FOCUS_C_










