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