/*--------------------------------*-C-*---------------------------------*
 * File:	graphics.c
 *
 * This module is all new by Rob Nation
 * <nation@rocket.sanders.lockheed.com>
 *
 * Modifications by mj olesen <olesen@me.QueensU.CA>
 * and Raul Garcia Garcia <rgg@tid.es>
 *----------------------------------------------------------------------*/
/*{{{ includes, defines */
#include "main.h"

#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#include <X11/cursorfont.h>

#include "command.h"		/* for tt_printf() */
#include "debug.h"
#include "graphics.h"
#include "screen.h"

/* commands:
 * 'C' = Clear
 * 'F' = Fill
 * 'G' = Geometry
 * 'L' = Line
 * 'P' = Points
 * 'T' = Text
 * 'W' = Window
 */

#ifndef GRX_SCALE
# define GRX_SCALE	10000
#endif
/*}}}*/
/* extern functions referenced */
/* extern variables referenced */
/* extern variables declared here */

#ifdef RXVT_GRAPHICS
/*{{{ local variables */
static int graphics_up = 0;

typedef struct grcmd_t {
   char cmd;
   short color;
   short ncoords;
   int * coords;
   unsigned char * text;
   struct grcmd_t * next;
} grcmd_t;

typedef struct grwin_t {
   Window win;
   int x, y;
   unsigned int w, h;
   short screen;
   grcmd_t * graphics;
   struct grwin_t * prev, * next;
} grwin_t;

static grwin_t * gr_root = NULL;
/*}}}*/
/*----------------------------------------------------------------------*
 * local functions
 */
/*{{{ Gr_NewWindow() */
static void
Gr_NewWindow (int nargs, int args[])
{
   int x, y;
   unsigned int w, h;
   Window win;
   grwin_t * grwin;
   Cursor cursor;

   if (nargs != 4)
     {
	print_error ("NewWindow: 4 args needed, got %d\n", nargs);
	return;
     }

   x = args [0] * TermWin.width  / GRX_SCALE + TermWin_internalBorder;
   y = args [1] * TermWin.height / GRX_SCALE + TermWin_internalBorder;
   w = args [2] * TermWin.width  / GRX_SCALE;
   h = args [3] * TermWin.height / GRX_SCALE;

   win = XCreateSimpleWindow (Xdisplay, TermWin.vt,
			      x, y, w, h,
			      0,
			      PixColors [fgColor],
			      PixColors [bgColor]);

   cursor = XCreateFontCursor (Xdisplay, XC_crosshair);
   XDefineCursor (Xdisplay, win, cursor);
   XMapWindow (Xdisplay, win);
   XSelectInput (Xdisplay, win, ExposureMask);

   grwin = MALLOC (sizeof(grwin_t),"grwin");
   grwin->win = win;
   grwin->x = x;
   grwin->y = y;
   grwin->w = w;
   grwin->h = h;
   grwin->screen = 0;
   grwin->prev = NULL;
   grwin->next = gr_root;
   if (grwin->next)
     grwin->next->prev = grwin;
   gr_root = grwin;
   grwin->graphics = NULL;
   graphics_up++;

   tt_printf ("\033W%ld\n", (long) grwin->win);
}
/*}}}*/
/*{{{ Gr_ClearWindow() */
static void
Gr_ClearWindow (grwin_t * grwin)
{
   grcmd_t * cmd, * next;

   for (cmd = grwin->graphics; cmd != NULL; cmd = next)
     {
	next = cmd->next;
	free (cmd->coords);
	if (cmd->text != NULL)
	  free (cmd->text);
	free (cmd);
     }
   grwin->graphics = NULL;
   XClearWindow (Xdisplay, grwin->win);
}
/*}}}*/
/*{{{ Gr_Text() */
/*
 * arg [0] = x
 * arg [1] = y
 * arg [2] = alignment
 * arg [3] = strlen (text)
 */
static void
Gr_Text (grwin_t * grwin, grcmd_t * data)
{
   int x, y, align;

   if (data->ncoords < 4 || data->text == NULL || *(data->text) == '\0')
     return;

   x = data->coords [0] * grwin->w / GRX_SCALE;
   y = data->coords [1] * grwin->h / GRX_SCALE;
   align = data->coords [2];

   if (align & RIGHT_TEXT)
     x -= XTextWidth (TermWin.font, data->text, data->coords [3]);
   else if (align & HCENTER_TEXT)
     x -= (XTextWidth (TermWin.font, data->text, data->coords [3]) >> 1);

   if (align & TOP_TEXT)
     y += TermWin.font->ascent;
   else if (align & BOTTOM_TEXT)
     y -= TermWin.font->descent;

   if (align & VCENTER_TEXT)
     y -= TermWin.font->descent + ((TermWin.font->ascent + TermWin.font->descent)>>1);
   if (align & VCAPS_CENTER_TEXT)
     y += (TermWin.font->ascent >> 1);

#ifdef XPM_BACKGROUND
   XClearArea (Xdisplay, grwin->win,
	       x, y,
	       Width2Pixel (data->coords [3]),
	       Height2Pixel (1),
	       0);
#endif
   XDrawString (Xdisplay, grwin->win, TermWin.gc,
		x, y,
		data->text, data->coords [3]);
}
/*}}}*/
/*{{{ Gr_Geometry() */
static void
Gr_Geometry (grwin_t * grwin, grcmd_t * data)
{
   if (grwin)
     tt_printf ("\033G%ld %d %d %u %u %d %d %ld %ld %d\n",
		(long) grwin->win,
		grwin->x, grwin->y, grwin->w, grwin->h,
		TermWin.fwidth,
		TermWin.fheight,
		(long) GRX_SCALE * TermWin.fwidth  / grwin->w,
		(long) GRX_SCALE * TermWin.fheight / grwin->h,
		Xdepth);
   else				/* rxvt terminal window size */
     tt_printf ("\033G0 0 0 %d %d %d %d %ld %ld %d\n",
		TermWin.width - 2 * TermWin_internalBorder,
		TermWin.height - 2 * TermWin_internalBorder,
		TermWin.fwidth,
		TermWin.fheight,
		(long) GRX_SCALE * TermWin.fwidth  / (TermWin.width  - 2 * TermWin_internalBorder),
		(long) GRX_SCALE * TermWin.fheight / (TermWin.height - 2 * TermWin_internalBorder),
		Xdepth);
}
/*}}}*/
/*{{{ Gr_DestroyWindow() */
static void
Gr_DestroyWindow (grwin_t * grwin)
{
   grcmd_t * cmd, * next;

   if (grwin == NULL)
     return;

   for (cmd = grwin->graphics; cmd; cmd = next)
     {
	next = cmd->next;
	free (cmd->coords);
	if (cmd->text != NULL)
	  free (cmd->text);
	free (cmd);
     }

   XDestroyWindow (Xdisplay, grwin->win);
   if (grwin->next != NULL)
     grwin->next->prev = grwin->prev;
   if (grwin->prev != NULL)
     grwin->prev->next = grwin->next;
   else
     gr_root = grwin->next;
   free (grwin);

   graphics_up--;
}
/*}}}*/
/*{{{ Gr_Dispatch() */
static void
Gr_Dispatch (grwin_t * grwin, grcmd_t * data)
{
   int i, n;
   union {
      XPoint pt [NGRX_PTS/2];
      XRectangle rect [NGRX_PTS/4];
   } xdata;

   if (data->color != fgColor)
     {
	XGCValues gcv;

	gcv.foreground = PixColors [data->color];
	XChangeGC (Xdisplay, TermWin.gc, GCForeground, &gcv);
     }
   if (grwin)
     switch (data->cmd) {
      case 'L':
	if (data->ncoords > 3)
	  {
	     for (n = i = 0; i < data->ncoords; i += 2, n++)
	       {
		  xdata.pt [n].x = data->coords [i]   * grwin->w / GRX_SCALE;
		  xdata.pt [n].y = data->coords [i+1] * grwin->h / GRX_SCALE;
	       }
	     XDrawLines (Xdisplay,
			 grwin->win, TermWin.gc, xdata.pt, n, CoordModeOrigin);
	  }
	break;

      case 'P':
	if (data->ncoords > 3)
	  {
	     for (n = i = 0; i < data->ncoords; i += 2, n++)
	       {
		  xdata.pt [n].x = data->coords [i]   * grwin->w / GRX_SCALE;
		  xdata.pt [n].y = data->coords [i+1] * grwin->h / GRX_SCALE;
	       }
	     XDrawPoints (Xdisplay,
			  grwin->win, TermWin.gc, xdata.pt, n, CoordModeOrigin);
	  }
	break;

      case 'F':
	if (data->ncoords > 0)
	  {
	     for (n = i = 0; i < data->ncoords; i += 4, n++)
	       {
		  xdata.rect [n].x = data->coords[i]   * grwin->w / GRX_SCALE;
		  xdata.rect [n].y = data->coords[i+1] * grwin->h / GRX_SCALE;
		  xdata.rect [n].width = ((data->coords[i+2]
					   - data->coords[i]+1) *
					  grwin->w / GRX_SCALE);
		  xdata.rect [n].height = ((data->coords[i+3]
					    - data->coords[i+1]+1) *
					   grwin->h / GRX_SCALE);

#ifdef XPM_BACKGROUND
		  XClearArea (Xdisplay, grwin->win,
			      xdata.rect[n].x,
			      xdata.rect[n].y,
			      xdata.rect[n].width,
			      xdata.rect[n].height,
			      0);
#endif
	       }
	     XFillRectangles (Xdisplay, grwin->win, TermWin.gc, xdata.rect, n);
	  }
	break;
      case 'T':	Gr_Text (grwin, data);	break;
      case 'C':	Gr_ClearWindow (grwin);	break;
     }

   if (data->color != fgColor)
     {
	XGCValues gcv;
	gcv.foreground = PixColors [fgColor];
	XChangeGC (Xdisplay, TermWin.gc, GCForeground, &gcv);
     }
}
/*}}}*/
/*{{{ Gr_Redraw() */
static void
Gr_Redraw (grwin_t * grwin)
{
   grcmd_t * cmd;
   for (cmd = grwin->graphics; cmd != NULL; cmd = cmd->next)
     Gr_Dispatch (grwin, cmd);
}
/*}}}*/
#endif
/*----------------------------------------------------------------------*
 * end of static functions
 */
#ifdef RXVT_GRAPHICS
/*{{{ Gr_ButtonReport() */
void
Gr_ButtonReport (int but, int x, int y)
{
   grwin_t * grwin;

   for (grwin = gr_root; grwin != NULL; grwin = grwin->next)
     if ((x > grwin->x) &&
	 (y > grwin->y) &&
	 (x < grwin->x + grwin->w) &&
	 (y < grwin->y + grwin->h))
     break;

   if (grwin == NULL)
     return;

   x = GRX_SCALE * (x - grwin->x) / grwin->w;
   y = GRX_SCALE * (y - grwin->y) / grwin->h;
   tt_printf ("\033%c%ld;%d;%d;\n", but, (long) grwin->win, x, y);
}
/*}}}*/
/*{{{ Gr_do_graphics() */
void
Gr_do_graphics (int cmd, int nargs, int args [], unsigned char * text)
{
   static Window last_id = None;
   long win_id;
   grwin_t * grwin;
   grcmd_t * newcmd, * oldcmd;
   int i;

   if (cmd == 'W')
     {
	Gr_NewWindow (nargs, args);
	return;
     }

   win_id = (nargs > 0) ? (Window) args [0] : None;

   if ((cmd == 'G') && (win_id == None))
     {
	Gr_Geometry (NULL, NULL);
	return;
     }

   if ((win_id == None) && (last_id != None))
     win_id = last_id;

   if (win_id == None)
     return;

   grwin = gr_root;
   while ((grwin != NULL) && (grwin->win != win_id))
     grwin = grwin->next;

   if (grwin == NULL)
     return;

   if (cmd == 'G')
     {
	Gr_Geometry (grwin, NULL);
	return;
     }

   nargs--; args++;		/* skip over window id */

   /* record this new command */
   newcmd = MALLOC (sizeof(grcmd_t), "grcmd_t");
   newcmd->ncoords = nargs;
   newcmd->coords = MALLOC ((newcmd->ncoords * sizeof(int)), "coords");
   newcmd->next = NULL;
   newcmd->cmd = cmd;
   newcmd->color = scr_get_fgcolor ();
   newcmd->text = text;

   for (i = 0; i < newcmd->ncoords; i++)
     newcmd->coords [i] = args [i];

   /*
    * If newcmd == fill, and rectangle is full window, drop all prior
    * commands.
    */
   if ((newcmd->cmd == 'F') && (grwin) && (grwin->graphics))
     {
	for (i = 0; i < newcmd->ncoords; i += 4)
	  {
	     if ((newcmd->coords[i] == 0) &&
		 (newcmd->coords[i+1] == 0) &&
		 (newcmd->coords[i+2] == GRX_SCALE) &&
		 (newcmd->coords[i+3] == GRX_SCALE))
	       {
		  /* drop previous commands */
		  oldcmd = grwin->graphics;
		  while (oldcmd->next != NULL)
		    {
		       grcmd_t * tmp = oldcmd;
		       oldcmd = oldcmd->next;
		       free (tmp);
		    }
		  grwin->graphics = NULL;
	       }
	  }
     }

   /* insert new command into command list */
   oldcmd = grwin->graphics;
   if (oldcmd == NULL)
     grwin->graphics = newcmd;
   else
     {
	while (oldcmd->next != NULL)
	  oldcmd = oldcmd->next;
	oldcmd->next = newcmd;
     }
   Gr_Dispatch (grwin, newcmd);
}
/*}}}*/
/*{{{ Gr_scroll() */
void
Gr_scroll (int count)
{
   static short prev_start = 0;
   grwin_t * grwin, * next;

   if ((count == 0) && (prev_start == TermWin.view_start))
     return;

   prev_start = TermWin.view_start;

   for (grwin = gr_root; grwin != NULL; grwin = next)
     {
	next = grwin->next;
	grwin->y -= (count * TermWin.fheight);
	if ((grwin->y + grwin->h) < - (TermWin.saveLines * TermWin.fheight))
	  Gr_DestroyWindow (grwin);
	else
	  XMoveWindow (Xdisplay, grwin->win,
		       grwin->x,
		       grwin->y + (TermWin.view_start * TermWin.fheight));
     }
}
/*}}}*/
/*{{{ Gr_ClearScreen() */
void
Gr_ClearScreen (void)
{
   grwin_t * grwin, * next;

   for (grwin = gr_root; grwin != NULL; grwin = next)
     {
	next = grwin->next;
	if ((grwin->screen == 0) && (grwin->y + grwin->h > 0))
	  {
	     if (grwin->y >= 0)
	       Gr_DestroyWindow (grwin);
	     else
	       XResizeWindow (Xdisplay, grwin->win,
			      grwin->w, -grwin->y);
	  }
     }
}
/*}}}*/
/*{{{ Gr_ChangeScreen() */
void
Gr_ChangeScreen (void)
{
   grwin_t * grwin, * next;

   for (grwin = gr_root; grwin != NULL; grwin = next)
     {
	next = grwin->next;
	if (grwin->y + grwin->h > 0)
	  {
	     if (grwin->screen == 1)
	       {
		  XMapWindow (Xdisplay, grwin->win);
		  grwin->screen = 0;
	       }
	     else
	       {
		  XUnmapWindow (Xdisplay, grwin->win);
		  grwin->screen = 1;
	       }
	  }
     }
}
/*}}}*/
/*{{{ Gr_expose() */
void
Gr_expose (Window win)
{
   grwin_t * grwin;

   for (grwin = gr_root; grwin != NULL; grwin = grwin->next)
     {
	if (grwin->win == win)
	  {
	     Gr_Redraw (grwin);
	     break;
	  }
     }
}
/*}}}*/
/*{{{ Gr_Resize() */
void
Gr_Resize (int w, int h)
{
   grwin_t * grwin;

   for (grwin = gr_root; grwin != NULL; grwin = grwin->next)
     {
	if (TermWin.height != h)
	  {
	     grwin->y += (TermWin.height - h);
	     XMoveWindow (Xdisplay, grwin->win,
			  grwin->x,
			  grwin->y + (TermWin.view_start * TermWin.fheight));
	  }
	Gr_Redraw (grwin);
     }
}
/*}}}*/
/*{{{ Gr_reset() */
void
Gr_reset (void)
{
   grwin_t * grwin, * next;

   for (grwin = gr_root; grwin != NULL; grwin = next)
     {
	next = grwin->next;
	Gr_DestroyWindow (grwin);
     }

   graphics_up = 0;
}
/*}}}*/
/*{{{ Gr_Displayed() */
int
Gr_Displayed (void) { return graphics_up; }
/*}}}*/
#endif	/* RXVT_GRAPHICS */
/*----------------------- end-of-file (C source) -----------------------*/
