/**************************************************************************
  Copyright (C) 1992 Guy Moreillon
  All rights reserved.

  This software may be freely copied, modified, and redistributed
  provided that this copyright notice is preserved on all copies.

  You may not distribute this software, in whole or in part, as part of
  any commercial product without the express consent of the authors.

  There is no warranty or other guarantee of fitness of this software
  for any purpose.  It is provided solely "as is".
**************************************************************************/
/*
 * forms.c
 * 
 * This file is part of the basis of the Forms Library.
 *
 * It contains the routines for building up forms and for handling the
 * interaction with the user.
 *
 * Written by Mark Overmars
 *
 * Version 2.0 b
 * Date Jan  3, 1992
 */

#include <stdio.h>
#include <sys/time.h>
#include <gl/gl.h>
#include <gl/device.h>
#include "forms.h"

extern void sginap(long);

FL_FORM *fl_current_form = NULL;
static FL_OBJECT *fl_current_group = NULL;

FL_FORM *fl_bgn_form(int type,float w,float h)
/* Starts a form definition */
{
  fl_init();
  if (fl_current_form != NULL)
    { fl_error("fl_bgn_form","You forgot calling fl_end_form."); }
  fl_current_form = fl_make_form(w,h);
  fl_add_box(type,0.0,0.0,w,h,"");
  return fl_current_form;
}

void fl_end_form()
/* Ends a form definition */
{
  if (fl_current_form == NULL)
    { fl_error("fl_end_form","Ending form definition of NULL form."); return; }
  fl_current_form = NULL;
}

void fl_addto_form(FL_FORM *form)
/* Reopens a form for input */
{
  if (fl_current_form != NULL)
    { fl_error("fl_addto_form","You forgot calling fl_end_form"); }
  if (form == NULL)
    { fl_error("fl_addto_form","Adding to NULL form."); return; }
  fl_current_form = form;
}

FL_OBJECT *fl_bgn_group()
/* Starts a group definition */
{
  if (fl_current_form == NULL)
    { fl_error("fl_bgn_group","Starting group in NULL form."); return NULL;}
  if (fl_current_group != NULL)
    { fl_error("fl_bgn_group","You forgot calling fl_end_group."); fl_end_group();}
  fl_current_group = fl_make_object(FL_BEGIN_GROUP,0,0.0,0.0,0.0,0.0,"",NULL);
  fl_add_object(fl_current_form,fl_current_group);
  return fl_current_group;
}

FL_OBJECT *fl_end_group()
/* Ends a group definition */
{
  FL_OBJECT *ob;
  if (fl_current_form == NULL)
    { fl_error("fl_end_group","Ending group in NULL form."); return NULL; }
  if (fl_current_group == NULL)
    { fl_error("fl_end_group","Ending NULL group."); return NULL; }
  fl_current_group = NULL;
  ob = fl_make_object(FL_END_GROUP,0,0.0,0.0,0.0,0.0,"",NULL);
  fl_add_object(fl_current_form,ob);
  return ob;
}

/************************ Doing the Interaction *************************/

#define MAX_FORM	40

static FL_FORM *forms[MAX_FORM];	/* The forms being shown. */
static int formnumb = 0;	 	/* Their number. */
static FL_FORM *mouseform = NULL;   	/* The current form under mouse*/
static FL_OBJECT *pushobj = NULL;	/* latest pushed object */
static FL_OBJECT *mouseobj = NULL;  	/* object under the mouse */

static void reshape_form(FL_FORM *form)
/* Adapts the size of the form to the new window position and size. */
{
  FL_OBJECT *obj;
  long w,h;
  long oldwin = winget();
  reshapeviewport();
  getsize(&w,&h);
  if ( w != (long) form->w || h != (long) form->h)
  {
    /* Rescale the form */
    obj = form->first;
    while (obj != NULL)
    {
      obj->x *= ((float) w) / form->w;
      obj->y *= ((float) h) / form->h;
      obj->w *= ((float) w) / form->w;
      obj->h *= ((float) h) / form->h;
      obj = obj->next;
    }
    form->w = (float) w; form->h = (float) h;
  }
  ortho2(-0.5,form->w-0.5,-0.5,form->h-0.5);
  getorigin(&(form->x),&(form->y));
  if (oldwin>0) winset(oldwin);
}

void fl_set_form_position(FL_FORM *form, long x, long y)
/* Sets the position of the form on the screen. */
{
  if (form == NULL)
    { fl_error("fl_set_form_position","Changing position NULL form."); return;}
  form->x = x; form->y = y;
}

long fl_show_form(FL_FORM *form,int place,int border,char name[])
/* Displays a particular form. Returns window handle. */
{
  long x,y,w,h,mx,my;
  long wx,wy,screenw,screenh;
  long oldwin = winget();
  if (formnumb == MAX_FORM)
    { fl_error("fl_show_form","Can show only 40 forms."); return -1; }
  if (form == NULL)
    { fl_error("fl_show_form","Trying to display NULL form."); return -1;}
  if (form->visible ) return form->window;
  forms[formnumb++] = form;
  form->deactivated = 0;
  form->visible = 1;
  /* Compute the correct place of the form */
  screenw = getgdesc(GD_XPMAX); screenh = getgdesc(GD_YPMAX);
  if (place == FL_PLACE_SIZE)
    prefsize( (int) form->w, (int) form->h);
  else if (place == FL_PLACE_ASPECT)
    keepaspect( (int) form->w, (int) form->h);
  else if (place != FL_PLACE_FREE)
  {
    if (place == FL_PLACE_CENTER)
      {wx = (screenw - (long)(form->w))/2; wy = (screenh - (long)(form->h))/2;}
    else if (place == FL_PLACE_MOUSE)
    {
      wx = (long) (getvaluator(MOUSEX) - 0.5*form->w);
      wy = (long) (getvaluator(MOUSEY) - 0.5*form->h);
    }
    else if (place == FL_PLACE_POSITION)
    {
      wx = form->x;
      if (wx < 0) wx = (long) screenw - form->w + wx -1;
      wy = form->y;
      if (wy < 0) wy = (long) screenh - form->h + wy -1;
    }
    if (wx < 0) wx = 0;
    if (wx > screenw - form->w) wx = (long) (screenw - form->w);
    if (wy < 0) wy = 0;
    if (wy > screenh - form->h) wy = (long) (screenh - form->h);
    prefposition(wx, wx + (long) form->w -1, wy, wy + (long) form->h -1);
  }
  if (! border) 
    { noborder(); form->window = winopen("Form"); }
  else
    form->window = winopen(name);
  if (form->doublebuf) doublebuffer();
  if (fl_rgbmode) RGBmode();
  gconfig();
  qdevice(MOUSE3); qdevice(MOUSE2); qdevice(MOUSE1);
  qdevice(KEYBD);
  qdevice(LEFTARROWKEY); qdevice(RIGHTARROWKEY);
  qdevice(UPARROWKEY); qdevice(DOWNARROWKEY);
  qdevice(WINQUIT);
  reshape_form(form);
  fl_redraw_form(form);
  /* Check whether the window appear under the mouse and generate an
     extra INPUTCHANGE event if so. This corrects a bug in IRIX 4.0.
  */
  getorigin(&x,&y);
  getsize(&w,&h);
  mx = getvaluator(MOUSEX); my = getvaluator(MOUSEY);
  if (mx>=x && mx<=x+w && my>=y && my<=y+h)
  {
    qenter(INPUTCHANGE, 0);
    qenter(INPUTCHANGE, (short) form->window);
  }
  if (oldwin > 0 ) winset(oldwin);
  return form->window;
}

void fl_hide_form(FL_FORM *form)
/* Hides a particular form */
{
  long x,y,w,h,mx,my;
  int i;
  FL_OBJECT *obj;
  long oldwin = winget();
  if (form == NULL)
    { fl_error("fl_hide_form","Hiding NULL form."); return; }
  if (form->window < 0 )
    { fl_error("fl_hide_form","Hiding invisible form."); return; }
  for (i=0; i<formnumb; i++)
    if (form == forms[i]) forms[i] = forms[--formnumb];
  if (mouseobj != NULL && mouseobj->form == form)
  {
    obj = mouseobj; mouseobj = NULL;
    fl_handle_object(obj,FL_LEAVE,0.0,0.0,0);
  }
  if (pushobj != NULL && pushobj->form == form)
  {
    obj = pushobj; pushobj = NULL;
    fl_handle_object(obj,FL_RELEASE,0.0,0.0,0);
  }
  if (form->focusobj != NULL)
  {
    obj = form->focusobj;
    fl_handle_object(form->focusobj,FL_UNFOCUS,0.0,0.0,0);
    fl_handle_object(obj,FL_FOCUS,0.0,0.0,0);
  }
  /* Check whether the window disappears under the mouse and generate an
     extra INPUTCHANGE event if so. This corrects a bug in IRIX 4.0.
  */
  winset(form->window);
  getorigin(&x,&y);
  getsize(&w,&h);
  mx = getvaluator(MOUSEX); my = getvaluator(MOUSEY);
  if (mx>=x && mx<=x+w && my>=y && my<=y+h)
    qenter(INPUTCHANGE, 0);

  if (oldwin == form->window) oldwin = -1;
  winclose(form->window);
  form->window = -1;
  form->deactivated = 1;
  form->visible = 0;
  if (oldwin>0) winset(oldwin);
}

void fl_activate_form(FL_FORM *form)
/* activates a form */
{
  if (form == NULL)
    { fl_error("fl_activate_form","Activating NULL form."); return; }
  if (form->deactivated) form->deactivated--;
}

void fl_deactivate_form(FL_FORM *form)
/* deactivates a form */
{
  if (form == NULL)
    { fl_error("fl_deactivate_form","Deactivating NULL form."); return; }
  if (!form->deactivated && mouseobj != NULL && mouseobj->form == form)
      fl_handle_object(mouseobj,FL_LEAVE,0.0,0.0,0);
  form->deactivated++;
}

void fl_activate_all_forms()
/* activates all forms */
{
  int i;
  for (i=0; i<formnumb; i++) fl_activate_form(forms[i]);
}

void fl_deactivate_all_forms()
/* deactivates all forms */
{
  int i;
  for (i=0; i<formnumb; i++) fl_deactivate_form(forms[i]);
}

static void fl_handle_form(FL_FORM *form, int event, int key)
/* updates a form according to an event */
{
  int i;
  FL_OBJECT *obj, *obj1;
  float xx,yy;
  if (form == NULL || ! form->visible) return;
  if (form->deactivated && event != FL_DRAW) return;
  winset(form->window);
  if (event != FL_STEP && event != FL_DRAW)
  {
    fl_get_mouse(&xx,&yy);
    obj = fl_find_last(form,FL_FIND_MOUSE,xx,yy);
  }
  switch (event)
  {
    case FL_DRAW:	   /* form must be redrawn */
	reshape_form(form);
	fl_redraw_form(form);
	break;
    case FL_ENTER:    /* Mouse did enter the form */
	mouseobj = obj;
	fl_handle_object(mouseobj,FL_ENTER,xx,yy,0);
	break;
    case FL_LEAVE:    /* Mouse did leave the form */
	fl_handle_object(mouseobj,FL_LEAVE,xx,yy,0);
	mouseobj = NULL;
	break;
    case FL_MOUSE:    /* Mouse position changed in the form */
	if (pushobj != NULL)
	  fl_handle_object(pushobj,FL_MOUSE,xx,yy,0);
	else if (obj != mouseobj)
	{
	  fl_handle_object(mouseobj,FL_LEAVE,xx,yy,0);
	  fl_handle_object(mouseobj = obj,FL_ENTER,xx,yy,0);
	}
	else if (mouseobj != NULL)
	  fl_handle_object(mouseobj,FL_MOVE,xx,yy,0);
	break;
    case FL_PUSH: 	   /* Mouse was pushed inside the form */
	/* change input focus */
	if ( obj != NULL && obj->input && form->focusobj != obj)
	{
	  fl_handle_object(form->focusobj,FL_UNFOCUS,xx,yy,0);
	  fl_handle_object(obj,FL_FOCUS,xx,yy,0);
	}
	/* handle a radio button */
	if (obj != NULL && obj->radio)
	{
	  obj1 = obj;
	  while (obj1->prev != NULL && obj1->objclass != FL_BEGIN_GROUP)
	    obj1 = obj1->prev;
	  while (obj1 != NULL && obj1->objclass != FL_END_GROUP)
	  {
	    if ( obj1->radio && obj1->pushed )
	    {
	      fl_handle_object_direct(obj1,FL_PUSH,xx,yy,0);
	      fl_handle_object_direct(obj1,FL_RELEASE,xx,yy,0);
	      obj1->pushed = 0;
	    }
	    obj1 = obj1->next;
	  }
	}
	/* push the object */
	fl_handle_object(obj,FL_PUSH,xx,yy,0);
	pushobj = obj;
	break;
    case FL_RELEASE:  /* Mouse was released inside the form */
	obj = pushobj; pushobj = NULL;
	fl_handle_object(obj,FL_RELEASE,xx,yy,0);
	break;
    case FL_KEYBOARD: /* A key was pressed */
	/* Check whether an object has this as shortcut. */
	obj1 = form->first;
	while (obj1 != NULL)
	{
	  i = -1;
	  while (obj1->shortcut[++i] != '\0')
	    if (obj1->shortcut[i] == key)
            {
	      fl_handle_object(obj1,FL_PUSH,xx,yy,key);
	      fl_handle_object(obj1,FL_RELEASE,xx,yy,key);
	      return;
	    }
	  obj1 = obj1->next;
        }
	if ( form->focusobj != NULL)
	{
	  if ( key == 9 || key == 13)
	  {
	    obj = fl_find_object(form->focusobj->next,FL_FIND_INPUT,0.,0.);
	    if (obj == NULL) obj = fl_find_first(form,FL_FIND_INPUT,0.,0.);
	    fl_handle_object(form->focusobj,FL_UNFOCUS,xx,yy,0);
	    fl_handle_object(obj,FL_FOCUS,xx,yy,0);
	  }
	  else
	    fl_handle_object(form->focusobj,FL_KEYBOARD,xx,yy,key);
	}
	break;
    case FL_STEP:	   /* A simple step */
	obj1 = fl_find_first(form,FL_FIND_AUTOMATIC,0.0,0.0);
	while (obj1 != NULL)
	{
	  fl_handle_object(obj1,FL_STEP,xx,yy,0);
	  obj1 = fl_find_object(obj1->next,FL_FIND_AUTOMATIC,0.0,0.0);
        }
	break;
  }
}

/***************
  Routine to check for events 
***************/

static short lastinputchange = 0;	/* Last inputchange event value. */

static long lastsec = 0;
static long lastusec = 0;
static struct timeval tp;
static struct timezone tzp;

static float reset_time()
/* Resets the timer */
{
  gettimeofday(&tp,&tzp);
  lastsec = tp.tv_sec; lastusec = tp.tv_usec;
}

static float time_passed()
/* Returns the time passed since the last call */
{
  gettimeofday(&tp,&tzp);
  return (float) (tp.tv_sec - lastsec) + 0.000001 * (tp.tv_usec - lastusec);
}

static void do_interaction_step(int wait)
/* Checks the devices and takes action accordingly.*/
{
  int i;
  Device dev;
  short val;
  /* check devices */
  if (!qtest())
  {
    if (time_passed()<0.01)
      { if (wait) sginap(1); else return;}
    reset_time();
  }
  if (!qtest()) dev = TIMER3; else dev = (Device) qread(&val);
  if (mouseform == NULL && dev != REDRAW && dev != INPUTCHANGE && dev != TIMER3)
    { fl_qenter(dev,val); return; }
  switch (dev)
  {
    case REDRAW:
        for (i=0; i<formnumb; i++)
	  if (forms[i]->window == val)
	    {fl_handle_form(forms[i],FL_DRAW,0);return;}
	fl_qenter(dev,val);
	break;
    case MOUSE1:
    case MOUSE2:
    case MOUSE3:
	if (val)
	{
	  if (getbutton(LEFTCTRLKEY) && getbutton(LEFTSHIFTKEY) &&
                            	getbutton(LEFTALTKEY))
            fl_show_message("FORMS LIBRARY","version 2.0b",
				"Written by Mark Overmars");
	  fl_handle_form(mouseform,FL_PUSH,0);
	}
	else
	  fl_handle_form(mouseform,FL_RELEASE,0);
	break;
    case INPUTCHANGE:
	if (val == 0)
        {
	  lastinputchange = 0;
	  if (mouseform == NULL)
            fl_qenter(dev,val);
          else
	    { fl_handle_form(mouseform,FL_LEAVE,0); mouseform = NULL; }
        }
	else
	{
/***********************
  Sort of Correction for a bug in IRIX 4.0.
  Enter and leave events are not always sent in the
  correct order. And sometimes even double. This
  gives strange effects. To avoid this we only treat an
  enter event if the previous one was a leave event.
************************/
	  if (lastinputchange != 0) break;
          lastinputchange = val;

	  if (mouseform != NULL)
	    { fl_handle_form(mouseform,FL_LEAVE,0); mouseform = NULL; }
	  for (i=0; i<formnumb; i++)
	    if (forms[i]->window == val)
	    { mouseform=forms[i]; fl_handle_form(mouseform,FL_ENTER,0); return;}
 	  fl_qenter(dev,val);
	}
	break;
    case LEFTARROWKEY:
	if (val) fl_handle_form(mouseform,FL_KEYBOARD,1);
	break;
    case RIGHTARROWKEY:
	if (val) fl_handle_form(mouseform,FL_KEYBOARD,2);
	break;
    case UPARROWKEY:
	if (val) fl_handle_form(mouseform,FL_KEYBOARD,3);
	break;
    case DOWNARROWKEY:
	if (val) fl_handle_form(mouseform,FL_KEYBOARD,4);
	break;
    case KEYBD:
	fl_handle_form(mouseform,FL_KEYBOARD,val);
	break;
    case TIMER3:
      	fl_handle_form(mouseform,FL_MOUSE,0);
    	for (i=0; i<formnumb; i++) fl_handle_form(forms[i],FL_STEP,0);
	break;
    default:
    	fl_qenter(dev,val);
  }
}

FL_OBJECT *fl_check_forms()
/* This is the dialogue routine that checks all forms. */
/*
 * Modified by G. Moreillon, July 10, 1992
 * If we don't treat the user events at once,
 * they accumulate in the queue, and it can be
 * a problem in some applications
 */
{
  long oldwin;

  if ( fl_object_qtest() != NULL) return fl_object_qread();
  oldwin = winget();
  do {
    do_interaction_step(FALSE);
    fl_treat_user_event();	    /* That's it */
  } while (qtest() && fl_object_qtest() == NULL);
  if (oldwin>0) winset(oldwin);
  return fl_object_qread();
}

FL_OBJECT *fl_do_forms()
/* This is the dialogue routine that waits for action. */
{
  long oldwin;
  while (1)
  {
    if ( fl_object_qtest() != NULL) return fl_object_qread();
    oldwin = winget();
    do
    {
      do_interaction_step(TRUE);
    } while (qtest() && fl_object_qtest() == NULL);
    if (oldwin>0) winset(oldwin);
    fl_treat_user_event();
  }
}

FL_OBJECT *fl_check_only_forms()
/* This is the dialogue routine that checks all forms. It does never
   return FL_EVENT objects. */
{
  long oldwin;
  if ( fl_object_qtest() != NULL) return fl_object_qread();
  do {
    oldwin = winget();
    do_interaction_step(FALSE);
    if (oldwin>0) winset(oldwin);
    if ( fl_object_qtest() != NULL) return fl_object_qread();
  } while (qtest());
  return NULL;
}

FL_OBJECT *fl_do_only_forms()
/* This is the dialogue routine that waits for action. It does never
   return FL_EVENT as an object. */
{
  long oldwin;
  while (1)
  {
    if ( fl_object_qtest() != NULL) return fl_object_qread();
    oldwin = winget();
    do_interaction_step(TRUE);
    if (oldwin>0) winset(oldwin);
  }
}
