/*
   Functions for the X window system.
   Copyright (C) 1988 Free Software Foundation.

This file is part of GNU Emacs.

GNU Emacs is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.  No author or distributor
accepts responsibility to anyone for the consequences of using it
or for whether it serves any particular purpose or works at all,
unless he says so in writing.  Refer to the GNU Emacs General Public
License for full details.

Everyone is granted permission to copy, modify and redistribute
GNU Emacs, but only under the conditions described in the
GNU Emacs General Public License.   A copy of this license is
supposed to have been given to you along with GNU Emacs so you
can know your rights and responsibilities.  It should be in a
file named COPYING.  Among other things, the copyright notice
and this notice must be preserved on all copies.  */

/*
 * Epoch 4.0:  Mouse & motion event data now propogates to elisp level
 *	       as pixel values.
 *
 * $Revision: 1.15 $
 * $Source: /import/kaplan/stable/distrib/epoch-4.2/src/RCS/mouse.c,v $
 * $Date: 92/08/05 04:15:56 $
 * $Author: marca $
 */
#ifndef LINT
static char rcsid[] = "$Author: marca $ $Date: 92/08/05 04:15:56 $ $Source: /import/kaplan/stable/distrib/epoch-4.2/src/RCS/mouse.c,v $ $Revision: 1.15 $";
#endif

#include <stdio.h>
#ifdef NULL
#undef NULL
#endif
#include <signal.h>
#include "config.h"
#include "lisp.h"
#include "window.h"
#include "x11term.h"
#include "termchar.h"
#ifdef USG
#include <time.h>
#else
#include <sys/time.h>
#endif
#include <fcntl.h>
#include <setjmp.h>

#include "indent.h"
#include "buffer.h"
#include "screen.h"
#include "screenW.h"		/* Epoch */
#include "screenX.h"		/* Epoch */
#include "dispepoch.h"		/* Epoch */
#include "button.h"		/* Epoch */

/* X11 includes used; use NIL rather than NULL from lisp.h */

#ifdef HAVE_X_WINDOWS

#define abs(x) ((x < 0) ? ((x)) : (x))
#define sgn(x) ((x < 0) ? (-1) : (1))
#define min(a,b) ((a) < (b) ? (a) : (b))
#define max(a,b) ((a) > (b) ? (a) : (b))
  
/* Non-nil if Emacs is running with an X window for display.
   Nil if Emacs is run on an ordinary terminal.  */

Lisp_Object Vxterm;
#ifdef OLD_MOUSE_FUNCS
Lisp_Object Vx_mouse_pos;
Lisp_Object Vx_mouse_abs_pos;

Lisp_Object Vx_mouse_item;
#endif /* OLD_MOUSE_FUNCS */
extern struct Lisp_Vector *MouseMap;

extern XButtonEvent XXm_queue[XMOUSEBUFSIZE];
extern int XXm_queue_num;
extern int XXm_queue_in;
extern int XXm_queue_out;

extern int minibuf_prompt_width;
extern int distinct_minibuffer;
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
DEFUN ("coordinates-in-window-p", Fcoordinates_in_window_p,
  Scoordinates_in_window_p, 2, 2, 0,
       "Return t if POSITIONS (a list, (SCREEN-X SCREEN-Y)) is in WINDOW.\n\
Otherwise, return nil.")
  (coordinate, window)
     register Lisp_Object coordinate, window;
{
  register Lisp_Object xcoord, ycoord;
	
  if (!CONSP (coordinate))
    wrong_type_argument (Qlistp, coordinate);

  CHECK_WINDOW (window, 2);
  xcoord = Fcar (coordinate);
  ycoord = Fcar (Fcdr (coordinate));
  CHECK_NUMBER (xcoord, 0);
  CHECK_NUMBER (ycoord, 1);
  if ((XINT (xcoord) < XINT (XWINDOW (window)->pixleft)) ||
      (XINT (xcoord) >= (XINT (XWINDOW (window)->pixleft) +
			 XINT (XWINDOW (window)->pixwidth))))
    return Qnil;

  if ((XINT (ycoord) < XINT (XWINDOW (window)->pixtop)) ||
      (XINT (ycoord) >= (XINT (XWINDOW (window)->pixtop) +
			 XINT (XWINDOW (window)->pixheight)) - 1))
    return Qnil;

  return Qt;
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
DEFUN ("epoch::coords-to-point",Fepoch_coords_to_point,Sepoch_coords_to_point,
       2,3,0,
       "Convert X Y SCREEN in pixel coordinates to a list of\n\
(POINT BUFFER WINDOW SCREEN).")
	(px,py,screen) Lisp_Object px,py,screen;
{
  Lisp_Object win,buffer,old_inhibit_quit;
  int x,y;
  struct W_Screen *ws, *old_ws = cur_Wscreen;
  struct window *w;
  struct buffer *b, *old_b = current_buffer;
  struct position *pos;
  int startp, hscroll;
  register struct line_header *l = 0;
  register struct char_block *cb;  

  CHECK_NUMBER(px,0);
  CHECK_NUMBER(py,0);

  x = XINT(px);
  y = XINT(py);

  screen = find_block(screen);
  if (NIL(screen)) return Qnil;

  ws = XWSCREEN(XROOT(screen)->win);

  if (x < ws->in_border) x = ws->in_border;
  if (x > ws->in_border + ws->pixw) x = ws->in_border + ws->pixw;
  if (y < ws->in_border) y = ws->in_border;
  if (y > ws->in_border + ws->pixh) y = ws->in_border + ws->pixh;

  /* Got all of the various values, lets find the window first */

  win = Fepoch_first_window(screen);

#if 0 /* This is reproduced immediately following. */
  while (!NIL(win))
    {
      w = XWINDOW(win);
      if (!NIL(w->buffer) &&
	  x >= w->pixleft && x <= (w->pixleft + w->pixwidth)
	  && (y >= w->pixtop && y <= (w->pixtop + w->pixheight ) ||
	      (y < ws->in_border && w->pixtop == ws->in_border)))
	break;
      /* Looks stupid, but the problem is that we have to allow the minibuf
       * as a valid window the _first_ time through here, but not else when,
       * and we can't go on if the first window was the minibuffer
       */
      if (!EQ(win,XROOT(screen)->minibuf_window)) win = Fnext_window(win,Qt);
      if (distinct_minibuffer &&
	  EQ(win,XROOT(screen)->minibuf_window)) win = Qnil;
    }
#endif

  /* This is ugly, but I don't quite know how to fix it yet...
     -marca */
  {
#define SANITY_THRESHOLD 1000
    static int sanity_check;
    sanity_check = 0;

    while (!NIL(win) && (sanity_check < SANITY_THRESHOLD))
      {
        int delta;

        w = XWINDOW(win);

        delta=(w->pixleft==ws->in_border) ? 0 :                      
          XFWIDTH(XXFONT(XSTYLE(XROOT(w->root)->stylenorm)->font));  
        
        if (!NIL(w->buffer) &&
            x >= w->pixleft+delta && x <= (w->pixleft + w->pixwidth) 
            && (y >= w->pixtop && y <= (w->pixtop + w->pixheight ) ||
                (y < ws->in_border && w->pixtop == ws->in_border)))
          break;
        /* Looks stupid, but the problem is that we have to allow the minibuf
         * as a valid window the _first_ time through here, but not else when,
         * and we can't go on if the first window was the minibuffer
         */
        if (!EQ(win,XROOT(screen)->minibuf_window)) win = Fnext_window(win,Qt);
        if (distinct_minibuffer &&
            EQ(win,XROOT(screen)->minibuf_window)) win = Qnil;
        sanity_check++;
      }
    /* If we're punting on a sanity check, return a nil window and let
       somebody else worry about it. */
    if (sanity_check >= SANITY_THRESHOLD)
      {
        win = Qnil;
        /* fprintf (stderr, "[epoch::coords-to-point] Sanity check.\n"); */
      }
  }

  if (NIL(win)) return Qnil;

  /* found the window */

  buffer = w->buffer;
  b = XBUFFER(buffer);

  if (!NIL(w->modeline))
    l = XLINE(w->modeline);

  if (!EQ(win,XROOT(screen)->minibuf_window) &&
      l && l->ypos - l->ascent <= y && y <= l->ypos + l->descent)
    /* In mode line: return nil position. */
    return Fcons(Qnil,
		 Fcons(buffer,
		       Fcons(win,
			     Fcons(screen,Qnil))));
  else
    {
      register int cx= 0,cy = 0;
      
      old_inhibit_quit = Vinhibit_quit;
      Vinhibit_quit = Qt;		/* don't allow quits out of here */
      current_buffer = b;
      cur_Wscreen = ws;
      l = XLINE(w->lines);
      
      while (l)
	{
	  if (l->ypos - l->ascent <= y && y < l->ypos + l->descent)
	    break;
	  l = l->next; cy++;
	}
      if (l)
	{
	  cb = l->body;
	  while (cb != l->end)
	    {
	      if (cb->xpos <= x && x < cb->xpos + cb->width)
		break;
	      cb = cb->next; cx++;
	    }
	  if (XFASTINT(w->pixleft) != ws->in_border)       
	    cx--;                                          
	}
      
      startp = marker_position (w->start);
      hscroll = XINT (w->hscroll);
      /* New interface */
      pos = compute_motion (startp,0,
			    ((EQ (win, XROOT(screen)->minibuf_window) &&
			      startp == 1)
			     ? minibuf_prompt_width : 0)
			    +
			    (hscroll ? 1 - hscroll : 0),
			    ZV+1,cy,cx,hscroll,0,XWINDOW(win),Qnil);
      cur_Wscreen = old_ws;
      current_buffer = old_b;
	
      Vinhibit_quit = old_inhibit_quit;
      /*
       *could have gone 1 line to far, if the correct line wasn't long enough
       */
      if (pos->vpos == y+1) pos->bufpos -= 1; /* backup over newline */
    }
  return Fcons(make_number(pos->bufpos),
	       Fcons(buffer,
		     Fcons(win,
			   Fcons(screen,Qnil))));
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#ifdef OLD_MOUSE_FUNCS  
DEFUN ("x-mouse-events", Fx_mouse_events, Sx_mouse_events, 0, 0, 0,
  "Return number of pending mouse events from X window system.")
  ()
{
	register Lisp_Object tem;

	check_xterm ();

	XSET (tem, Lisp_Int, XXm_queue_num);
	
	return tem;
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* runs through the mouse even Q looking for a valid mouse event. Returns
 * true if one was found, false otherwise.
 */
static int
get_mouse_event(com_letter,x,y)
	char *com_letter;	/* command letter */
	int *x,*y;		/* co-ordinates */
    {
    XButtonEvent *event;
    register char key_mask;
    struct Root_Block *rb;
    extern struct Root_Block *x_find_screen();
    struct X_Screen *xs;
    struct W_Screen *ws;
    Lisp_Object tempx,tempy,screen;
    extern int meta_prefix_char;
    struct X_font *font;

    while (XXm_queue_num)
	{
	event = XXm_queue + XXm_queue_out;
	XXm_queue_out = (XXm_queue_out + 1) % XMOUSEBUFSIZE;
	XXm_queue_num--;

	/* find the screen it happened on */
	rb = x_find_screen(event->window);
	if (rb == (struct Root_Block *)0) continue;
	/* this is the only loop continuation - if we get _here_, then
	 * we're going to return something during the loop and not continue
	 */

	ws = XWSCREEN(rb->win);
	xs = XXSCREEN(rb->x11);
	font = XXFONT(XSTYLE(rb->stylenorm)->font);

	*com_letter = 3-(event->button & 3);
	key_mask = (event->state & 15) << 4;
	/* Report meta in 2 bit, not in 8 bit.  */
	if (key_mask & 0x80)
	    {
	    key_mask |= 0x20;
	    key_mask &= ~0x80;
	    }
	*com_letter |= key_mask;
	if (event->type == ButtonRelease)
	    *com_letter |= 0x04;

	*x = min (ws->width-1,
		  max (0, (event->x - ws->in_border)/
		       XFWIDTH(font)));
	*y = min (ws->height-1,
		  max (0, (event->y - ws->in_border)/
		       XFHEIGHT(font)));

	XSET(tempx,Lisp_Int,*x);
	XSET(tempy,Lisp_Int,*y);
	XSET(screen,Lisp_Root_Block,rb);

	/* set globals */
	Vx_mouse_pos = Fcons (tempx, Fcons (tempy, Fcons (screen , Qnil)));
	Vx_mouse_abs_pos = Fcons (tempx, Fcons (tempy, Fcons (screen, Qnil)));
	Vx_mouse_item = make_number (com_letter);

	return 1;
	}
    return 0;
    }

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

DEFUN ("x-proc-mouse-event", Fx_proc_mouse_event, Sx_proc_mouse_event,
  0, 0, 0,
  "Pulls a mouse event out of the mouse event buffer and dispatches\n\
the appropriate function to act upon this event.")
  ()
    {
    register Lisp_Object mouse_cmd;
    char com_letter;
    int tempx, tempy;
    extern Lisp_Object get_keyelt ();
    extern int meta_prefix_char;
	
    check_xterm ();

    if (get_mouse_event(&com_letter,&tempx,&tempy))
	{
	mouse_cmd = get_keyelt (access_keymap (MouseMap, com_letter));
	if (NIL (mouse_cmd))
	    {
	    if (com_letter&0x04) /* button release */
		Ding ();
	    return Vx_mouse_pos = Qnil;
	    }
	else
	    return call1 (mouse_cmd, Vx_mouse_pos);
	}
    return Qnil;
    }

DEFUN ("x-get-mouse-event", Fx_get_mouse_event, Sx_get_mouse_event,
  1, 1, 0,
  "Get next mouse event out of mouse event buffer (com-letter (x y)).\n\
ARG non-nil means return nil immediately if no pending event;\n\
otherwise, wait for an event.")
  (arg)
     Lisp_Object arg;
{
  char com_letter;
  register char key_mask;
  
  register Lisp_Object tempx;
  register Lisp_Object tempy;

  int x,y;
	
  check_xterm ();

  /* busy wait until an event. it's stupid, but that's the way
   * it was [AMC]
   */
  while (!get_mouse_event(&com_letter,&x,&y))
    {
      if (!NIL(arg)) return Qnil;
      QUIT;		/* allow aborts */
    }

  return Fcons (com_letter, Fcons (Vx_mouse_pos, Qnil));
}
#endif  /* OLD_MOUSE_FUNCS */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
syms_of_mouse ()
{
  defsubr (&Scoordinates_in_window_p);
  defsubr (&Sepoch_coords_to_point);
#ifdef OLD_MOUSE_FUNCS    
  DEFVAR_LISP ("x-mouse-item", &Vx_mouse_item,
	       "Encoded representation of last mouse click, corresponding to\n\
numerical entries in x-mouse-map.");
  Vx_mouse_item = Qnil;
  DEFVAR_LISP ("x-mouse-pos", &Vx_mouse_pos,
	       "Current x-y position of mouse by row, column as specified by font.");
  Vx_mouse_pos = Qnil;
  DEFVAR_LISP ("x-mouse-abs-pos", &Vx_mouse_abs_pos,
	       "Current x-y position of mouse relative to root window.");

  defsubr (&Sx_mouse_events);
  defsubr (&Sx_proc_mouse_event);
  defsubr (&Sx_get_mouse_event);
#endif

}

#endif /* HAVE_X_WINDOWS */
