/* 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.  */

/* Written by Yakim Martillo; rearranged by Richard Stallman.  */
/* Converted to X11 by Robert French */

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

#include "screen.h"
#include "screenW.h"		/* [amc] */
#include "screenX.h"		/* [anc] */

#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;

Lisp_Object Vx_mouse_pos;
Lisp_Object Vx_mouse_abs_pos;

Lisp_Object Vx_mouse_item;

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;

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

DEFUN ("coordinates-in-window-p", Fcoordinates_in_window_p,
  Scoordinates_in_window_p, 2, 2, 0,
  "Return non-nil if POSITIONS (a list, (SCREEN-X SCREEN-Y)) is in WINDOW.\n\
Returned value is list of positions expressed\n\
relative to window upper left corner.")
  (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)->left)) ||
	    (XINT (xcoord) >= (XINT (XWINDOW (window)->left) +
			       XINT (XWINDOW (window)->width))))
		return Qnil;

	XFASTINT (xcoord) -= XFASTINT (XWINDOW (window)->left);
	if (XINT (ycoord) == (WS->height -1))
		return Qnil;

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

	XFASTINT (ycoord) -= XFASTINT (XWINDOW (window)->top);
	return Fcons (xcoord, Fcons (ycoord, Qnil));
}

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;

    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);

	*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 - xs->in_border)/
		       xs->font_width));
	*y = min (ws->height-1,
		  max (0, (event->y - xs->in_border)/
		       xs->font_height));

	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 (NULL (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 (!NULL(arg)) return Qnil;
      QUIT;		/* allow aborts */
    }

  return Fcons (com_letter, Fcons (Vx_mouse_pos, Qnil));
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#include "buffer.h"
#include "indent.h"

DEFUN ("epoch::coords-to-point",Fepoch_coords_to_point,Sepoch_coords_to_point,
       2,3,0,
       "Convert X Y SCREEN in character coordinates to a list of\n\
(POINT BUFFER WINDOW SCREEN).")
	(lx,ly,screen) Lisp_Object lx,ly,screen;
{
  Lisp_Object win,buffer,old_inhibit_quit;
  int x,y,hscroll;
  struct W_Screen *ws, *old_ws = cur_Wscreen;
  struct window *w;
  struct buffer *b, *old_b = bf_cur;
  struct position *pos;

  CHECK_NUMBER(lx,0);
  CHECK_NUMBER(ly,0);

  x = XINT(lx);
  y = XINT(ly);

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

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

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

  win = Fepoch_first_window(screen);

  while (!NULL(win))
    {
      w = XWINDOW(win);
      if (NULL(w->buffer)) continue; /* no buffer, not valid */
      if (x >= w->left && x < (w->left + w->width)
	  && y >= w->top && y < (w->top + w->height -1))
	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,minibuf_window)) win = Fnext_window(win,Qt);
      if (EQ(win,minibuf_window)) win = Qnil;
    }

  if (NULL(win)) return Qnil;

  /* found the window */

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

  y -= XFASTINT(w->top);
  x -= XFASTINT(w->left);
  hscroll = XFASTINT(w->hscroll);

  old_inhibit_quit = Vinhibit_quit;
  Vinhibit_quit = Qt;		/* don't allow quits out of here */
  SetBfx(b);
  cur_Wscreen = ws;
  pos = compute_motion (marker_position(w->start) +
			( hscroll > 0 ? hscroll-1 : 0 ),
			0,0,		/* starting position */
			NumCharacters+1,y,x,
			XFASTINT(w->width) - 1 -
			(XFASTINT(w->width) + XFASTINT(w->left) != ws->width),
			hscroll,0);
  cur_Wscreen = old_ws;
  SetBfx(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(pos->bufpos,
	       Fcons(buffer,
		     Fcons(win,
			   Fcons(screen,Qnil))));
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

syms_of_mouse ()
{

  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 (&Scoordinates_in_window_p);
  defsubr (&Sx_mouse_events);
  defsubr (&Sx_proc_mouse_event);
  defsubr (&Sx_get_mouse_event);
  defsubr (&Sepoch_coords_to_point);
}

#endif /* HAVE_X_WINDOWS */
