/*
 *  buttons.c:		Draw MWFA player buttons in X11 window	
 *
 *  Written by:		Ullrich Hafner
 *		
 *  This file is part of FIASCO (Fractal Image And Sequence COdec)
 *  Copyright (C) 1994-2000 Ullrich Hafner
 */

/*
 *  $Date: 2000/06/15 17:23:11 $
 *  $Author: hafner $
 *  $Revision: 5.2 $
 *  $State: Exp $
 */

#include "config.h"

#ifndef X_DISPLAY_MISSING

#include <X11/Xlib.h>
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>

#if STDC_HEADERS
#	include <stdlib.h>
#endif /* not STDC_HEADERS */

#include "types.h"
#include "macros.h"

#include "display.h"
#include "binerror.h"
#include "buttons.h"

/*****************************************************************************

			     local variables
  
*****************************************************************************/

static const int EVENT_MASK = (KeyPressMask | ButtonPressMask |
			       ButtonReleaseMask | ExposureMask);

/*****************************************************************************

				prototypes
  
*****************************************************************************/

static void
draw_progress_bar (x11_info_t *xinfo, binfo_t *binfo, unsigned n,
		   unsigned n_frames);
static void
draw_button (x11_info_t *xinfo, binfo_t *binfo,
	     buttons_t button, bool_t pressed);
static void
draw_control_panel (x11_info_t *xinfo, binfo_t *binfo,
		    unsigned n, unsigned n_frames);

/*****************************************************************************

				public code
  
*****************************************************************************/

binfo_t * 
init_buttons (x11_info_t *xinfo, unsigned n, unsigned n_frames,
	      unsigned buttons_height, unsigned progbar_height)
/*
 *  Initialize a toolbar with the typical collection of video player
 *  buttons (pause, play, record, next, etc.) in the window given by 'xinfo'.
 *  'n' gives the current frame, 'whereas' n_frames is the total number of
 *  frames of the video stream.
 *  The size of the button toolbar is given by 'buttons_height',
 *  the size of the progressbar is given by 'progbar_height'.
 *
 *  Return value:
 *	struct managing the toolbar and progressbar information
 */
{
   XGCValues  values;
   XEvent     event;
   Colormap   cmap;
   XColor     gray, dgray, lgray, red;
   XColor     graye, dgraye, lgraye, rede;
   buttons_t  button;			/* counter */
   binfo_t   *binfo = calloc (1, sizeof (binfo_t));

   if (!binfo)
      error ("Out of memory.");
   
   binfo->width            = xinfo->ximage->width;
   binfo->height           = buttons_height;
   binfo->progbar_height   = progbar_height;
   binfo->record_is_rewind = NO;

   /*
    *  Generate sub-window for control panel
    */
   binfo->window = XCreateSimpleWindow (xinfo->display, xinfo->window,
					0, xinfo->ximage->height,
					binfo->width, binfo->height, 0,
					BlackPixel (xinfo->display,
						    xinfo->screen),
					WhitePixel (xinfo->display,
						    xinfo->screen));
   XSelectInput(xinfo->display, binfo->window, StructureNotifyMask);
   XMapWindow (xinfo->display, binfo->window);
   do
   {
      XNextEvent (xinfo->display, &event);
   }
   while (event.type != MapNotify || event.xmap.event != binfo->window);
   XSelectInput (xinfo->display, binfo->window, EVENT_MASK);

   /*
    *  Generate graphic contexts for different colors.
    */
   cmap = DefaultColormap (xinfo->display, xinfo->screen);
   XAllocNamedColor (xinfo->display, cmap, "#404040", &dgray, &dgraye);
   XAllocNamedColor (xinfo->display, cmap, "white", &lgray, &lgraye);
   XAllocNamedColor (xinfo->display, cmap, "#a8a8a8", &gray, &graye);
   XAllocNamedColor (xinfo->display, cmap, "red", &red, &rede);
   
   values.foreground = BlackPixel (xinfo->display, xinfo->screen);
   values.background = WhitePixel (xinfo->display, xinfo->screen);
   binfo->gc [BLACK] = XCreateGC (xinfo->display,
				  RootWindow (xinfo->display, xinfo->screen),
				  (GCForeground | GCBackground), &values);
   values.foreground = BlackPixel (xinfo->display, xinfo->screen);
   values.background = WhitePixel (xinfo->display, xinfo->screen);
   values.line_width = 3;
   values.join_style = JoinRound;
   binfo->gc [THICKBLACK] = XCreateGC (xinfo->display,
				       RootWindow (xinfo->display,
						   xinfo->screen),
				       (GCForeground | GCBackground
					| GCLineWidth | GCJoinStyle), &values);
   values.foreground = gray.pixel;
   values.background = WhitePixel (xinfo->display, xinfo->screen);
   binfo->gc [NGRAY] = XCreateGC (xinfo->display,
				  RootWindow (xinfo->display, xinfo->screen),
				  (GCForeground | GCBackground), &values);
   values.foreground = lgray.pixel;
   values.background = WhitePixel (xinfo->display, xinfo->screen);
   binfo->gc [LGRAY] = XCreateGC (xinfo->display,
				  RootWindow (xinfo->display, xinfo->screen),
				  (GCForeground | GCBackground), &values);
   values.foreground = dgray.pixel;
   values.background = WhitePixel (xinfo->display, xinfo->screen);
   binfo->gc [DGRAY] = XCreateGC (xinfo->display,
				  RootWindow (xinfo->display, xinfo->screen),
				  (GCForeground | GCBackground), &values);
   values.foreground = red.pixel;
   values.background = WhitePixel (xinfo->display, xinfo->screen);
   binfo->gc [RED]   = XCreateGC (xinfo->display,
				  RootWindow (xinfo->display, xinfo->screen),
				  (GCForeground | GCBackground), &values);

   for (button = 0; button < NO_BUTTON; button++)
      binfo->pressed [button] = NO;

   draw_control_panel (xinfo, binfo, n, n_frames); 
   
   return binfo;
}

void
wait_for_input (x11_info_t *xinfo)
/*
 *  Wait for key press or mouse click in window 'xinfo'.
 *  Redraw 'image' if event other then ButtonPress or KeyPress occurs.
 *  Enlarge or reduce size of image by factor 2^'enlarge_factor'.
 *
 *  No return value.
 *
 *  Side effect:
 *	program is terminated after key press or mouse click.
 */
{
   bool_t leave_loop = NO;
   
   XSelectInput (xinfo->display, xinfo->window, EVENT_MASK);

   while (!leave_loop)
   {
      XEvent event;

      XMaskEvent (xinfo->display, EVENT_MASK, &event);
      switch (event.type)
      {
	 case ButtonPress:
	 case KeyPress:
	    leave_loop = YES;
	    break;
	 default:
	    display_image (0, 0, xinfo);
	    break;
      }
   }
}

void
check_events (x11_info_t *xinfo, binfo_t *binfo, unsigned n, unsigned n_frames)
/*
 *  Check the X11 event loop. If the PAUSE buttonin the of panel 'binfo'
 *  is activated wait until next event occurs.
 *  Redraw 'image' if event other then ButtonPress or ButtonRelease occurs.
 *  Enlarge or reduce size of image by factor 2^'enlarge_factor'.
 *  'n' gives the current frame, 'whereas' n_frames is the total number of
 *  frames of the video stream.
 *
 *  No return values.
 *
 *  Side effects:
 *	status of buttons (binfo->pressed [button]) is changed accordingly.
 */
{
   bool_t leave_eventloop;

   leave_eventloop = (!binfo->pressed [PAUSE_BUTTON]
		      && binfo->pressed [PLAY_BUTTON])
		     || (!binfo->pressed [PAUSE_BUTTON]
			 && binfo->record_is_rewind
			 && binfo->pressed [RECORD_BUTTON])
		     || binfo->pressed [RECORD_BUTTON];
   draw_progress_bar (xinfo, binfo, n, n_frames);

   if (binfo->pressed [PAUSE_BUTTON] && binfo->pressed [PLAY_BUTTON])
   {
      XFlush (xinfo->display);
      draw_button (xinfo, binfo, PLAY_BUTTON, NO); /* clear PLAY mode */
      XFlush (xinfo->display);
   }
   if (binfo->pressed [PAUSE_BUTTON]
       && binfo->record_is_rewind && binfo->pressed [RECORD_BUTTON])
   {
      XFlush (xinfo->display);
      draw_button (xinfo, binfo, RECORD_BUTTON, NO); /* clear PLAY mode */
      XFlush (xinfo->display);
   }

   if (binfo->pressed [STOP_BUTTON])
   {
      XFlush (xinfo->display);
      draw_button (xinfo, binfo, STOP_BUTTON, NO); /* clear STOP button */
      XFlush (xinfo->display);
   }

   do
   {
      XEvent event;
      int    button;
      bool_t wait_release = NO;
	 
      
      if (XCheckMaskEvent (xinfo->display, EVENT_MASK, &event))
      {
	 switch (event.type)
	 {
	    case ButtonPress:
	       wait_release = NO;
	       if (!(binfo->pressed [RECORD_BUTTON] &&
		     !binfo->record_is_rewind))
		  for (button = 0; button < NO_BUTTON; button++)
		  {
		     int x0, y0, x1, y1; /* button coordinates */
		  
		     x0 = button * (binfo->width / NO_BUTTON);
		     y0 = binfo->progbar_height;
		     x1 = x0 + binfo->width / NO_BUTTON;
		     y1 = y0 + binfo->height - binfo->progbar_height - 1;
		     if (event.xbutton.x > x0 && event.xbutton.x < x1
			 && event.xbutton.y > y0 && event.xbutton.y < y1) 
		     {
			draw_button (xinfo, binfo, button,
				     !binfo->pressed [button]);
			wait_release = YES;
			break;
		     }
		  }
	       break;
	    case ButtonRelease:
	       wait_release = NO;
	       break;
	    default:
	       wait_release = NO;
	       draw_control_panel (xinfo, binfo, n, n_frames);
	       display_image (0, 0, xinfo);
	       break;
	 }
	 leave_eventloop = !wait_release
			   && (binfo->pressed [PLAY_BUTTON]
			       || binfo->pressed [STOP_BUTTON]
			       || binfo->pressed [RECORD_BUTTON]
			       || binfo->pressed [QUIT_BUTTON]);
      }
   } while (!leave_eventloop);

   if ((binfo->pressed [RECORD_BUTTON] && !binfo->record_is_rewind)
       && n == n_frames - 1)
   {
      binfo->record_is_rewind = YES;
      draw_button (xinfo, binfo, RECORD_BUTTON, NO);
   }
}

/*****************************************************************************

				private code
  
*****************************************************************************/

static void
draw_control_panel (x11_info_t *xinfo, binfo_t *binfo,
		    unsigned n, unsigned n_frames)
/*
 *  Draw control panel 'binfo' with all buttons and progressbar in
 *  the given 'window'.
 *  'n' gives the current frame, 'whereas' n_frames is the total number of
 *  frames of the video stream.
 *
 *  No return value.
 */
{
   buttons_t button;
   
   XFillRectangle (xinfo->display, binfo->window, binfo->gc [NGRAY],
		   0, 0, binfo->width, binfo->height);
   draw_progress_bar (xinfo, binfo, n, n_frames);
   for (button = 0; button < NO_BUTTON; button++)
      draw_button (xinfo, binfo, button, binfo->pressed [button]);
}

static void
draw_progress_bar (x11_info_t *xinfo, binfo_t *binfo, unsigned n,
		   unsigned n_frames)
/*
 *  Draw progressbar of control panel 'binfo' in the given 'window'.
 *  'n' gives the current frame, whereas 'n_frames' is the total number of
 *  frames of the video stream.
 *
 *  No return value.
 */
{
   unsigned x, y, width, height;

   x 	  = 2;
   y 	  = 1;
   width  = binfo->width - 5;
   height = binfo->progbar_height - 3;
   
   XDrawLine (xinfo->display, binfo->window, binfo->gc [DGRAY],
	      x, y, x + width, y);
   XDrawLine (xinfo->display, binfo->window, binfo->gc [DGRAY],
	      x, y, x, y + height - 1);
   XDrawLine (xinfo->display, binfo->window, binfo->gc [LGRAY],
	      x + width, y + 1, x + width, y + height);
   XDrawLine (xinfo->display, binfo->window, binfo->gc [LGRAY],
	      x, y + height, x + width, y + height);

   x++; y++; width  -= 2; height -= 2;
   XFillRectangle (xinfo->display, binfo->window, binfo->gc [NGRAY],
		   x, y, width, height);

   XFillRectangle (xinfo->display, binfo->window, binfo->gc [BLACK],
		   x + n * max (1, width / n_frames), y,
		   max (1, width / n_frames), height);
}

static void
draw_button (x11_info_t *xinfo, binfo_t *binfo,
	     buttons_t button, bool_t pressed)
/*
 *  Draw 'button' of control panel 'binfo' in the given 'window'.
 *  'pressed' indicates whether the button is pressed or not.
 *
 *  No return value.
 */
{
   grayscale_t top, bottom;		/* index of GC */
   unsigned    x, y, width, height;	/* coordinates of button */
   
   x 	  = button * (binfo->width / NO_BUTTON);
   y 	  = binfo->progbar_height;
   width  = binfo->width / NO_BUTTON;
   height = binfo->height - binfo->progbar_height - 1;
   
   if (width < 4 || height < 4)
      return;
   
   if (pressed)
   {
      top    = DGRAY;
      bottom = LGRAY;
   }
   else
   {
      top    = LGRAY;
      bottom = DGRAY;
   }

   x 	 += 2;
   width -= 4;
   
   XDrawLine (xinfo->display, binfo->window, binfo->gc [top],
	      x, y, x + width, y);
   XDrawLine (xinfo->display, binfo->window, binfo->gc [top],
	      x, y, x, y + height - 1);
   XDrawLine (xinfo->display, binfo->window, binfo->gc [bottom],
	      x + width, y + 1, x + width, y + height);
   XDrawLine (xinfo->display, binfo->window, binfo->gc [bottom],
	      x, y + height, x + width, y + height);

   x++; y++; width  -= 2; height -= 2;
   XFillRectangle (xinfo->display, binfo->window, binfo->gc [NGRAY],
		   x, y, width, height);

   switch (button)
   {
      case STOP_BUTTON:
	 XFillRectangle (xinfo->display, binfo->window, binfo->gc [BLACK],
			 x + width / 2 - 6, y + height / 2 - 4, 11, 11);
	 if (pressed && !binfo->pressed [STOP_BUTTON])
	 {
	    draw_button (xinfo, binfo, PLAY_BUTTON, NO);
	    draw_button (xinfo, binfo, PAUSE_BUTTON, NO); 
	    draw_button (xinfo, binfo, RECORD_BUTTON, NO); 
	 }
	 break;
      case PAUSE_BUTTON:
	 XFillRectangle (xinfo->display, binfo->window, binfo->gc [BLACK],
			 x + width / 2 - 6, y + height / 2 - 4, 5, 11);
	 XFillRectangle (xinfo->display, binfo->window, binfo->gc [BLACK],
			 x + width / 2 + 1, y + height / 2 - 4, 5, 11);
	 break;
      case PLAY_BUTTON:
	 {
	    XPoint triangle [3];

	    triangle [0].x = x + width / 2 - 5;
	    triangle [0].y = y + height / 2 - 5;
	    triangle [1].x = 10;
	    triangle [1].y = 6;
	    triangle [2].x = -10;
	    triangle [2].y = 6;

	    XFillPolygon (xinfo->display, binfo->window, binfo->gc [BLACK],
			  triangle, 3, Convex, CoordModePrevious);
	    if (pressed && !binfo->pressed [PLAY_BUTTON]
		&& binfo->pressed [RECORD_BUTTON])
	       draw_button (xinfo, binfo, RECORD_BUTTON, NO);
	 }
	 break;
      case RECORD_BUTTON:
	 if (!binfo->record_is_rewind)
	 {
	    XFillArc (xinfo->display, binfo->window, binfo->gc [RED],
		      x + width / 2 - 5, y + height / 2 - 5, 11, 11, 0,
		      360 * 64);
	    if (pressed && !binfo->pressed [RECORD_BUTTON])
	    {
	       draw_button (xinfo, binfo, STOP_BUTTON, YES);
	       draw_button (xinfo, binfo, PLAY_BUTTON, NO);
	       draw_button (xinfo, binfo, PAUSE_BUTTON, NO); 
	    }
	 }
	 else
	 {
	    XPoint triangle [3];

	    triangle [0].x = x + width / 2 + 5;
	    triangle [0].y = y + height / 2 - 5;
	    triangle [1].x = -10;
	    triangle [1].y = 6;
	    triangle [2].x = 10;
	    triangle [2].y = 6;

	    XFillPolygon (xinfo->display, binfo->window, binfo->gc [BLACK],
			  triangle, 3, Convex, CoordModePrevious);
	    if (pressed && !binfo->pressed [RECORD_BUTTON]
		&& binfo->pressed [PLAY_BUTTON])
	       draw_button (xinfo, binfo, PLAY_BUTTON, NO);
	 }
	 break;
      case QUIT_BUTTON:
	 {
	    XPoint triangle [3];

	    triangle [0].x = x + width / 2 - 6;
	    triangle [0].y = y + height / 2 + 2;
	    triangle [1].x = 6;
	    triangle [1].y = -7;
	    triangle [2].x = 6;
	    triangle [2].y = 7;

	    XFillPolygon (xinfo->display, binfo->window, binfo->gc [BLACK],
			  triangle, 3, Convex, CoordModePrevious);
	    XFillRectangle (xinfo->display, binfo->window, binfo->gc [BLACK],
			    x + width / 2 - 5, y + height / 2 + 4, 11, 3);
	 }
	 break;
      default:
	 break;
   }
   binfo->pressed [button] = pressed;
}

#endif /* not X_DISPLAY_MISSING */
