/* display_list.c
 *
 * By Byron C. Darrah
 * 4/28/94, Thursday
 *
 * This module is for actually drawing a prepared list of polygons into a
 * window on a screen.
 */
/* Added Sound support: Randall K. Sharpe 09-01-95 */

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <ctype.h>

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

/*   #include <X11/Xmu/Xmu.h>
     #include <X11/Xmu/WidgetNode.h>
*/

#define DISPLAY_LIST_MODULE

#include "colors.h"
#include "../../display_list.h"

#define DEFAULT_GFX_WIDTH  100
#define DEFAULT_GFX_HEIGHT 125

/* The origin is an offset from (0, 0).  If the default width and
 * height are not used, the default origin will automatically
 * be adjusted as well.
 */
#define DEFAULT_X_ORIGIN    52
#define DEFAULT_Y_ORIGIN    40


extern char command_str[];  /* Hack to let coolmail use X resource mgr */
extern char mailfile_str[]; /* to obtain the command and mail file.    */
extern unsigned int frames;

#ifdef AUDIO
extern char *sndfile; /* same hack as above */
extern int  cool_vol;	
void audio_beep(void);
#endif


unsigned      long color_table[NUMCOLORS];
int           color_mode = 1;
Widget        toplevel, main_gfx_w;
Pixmap        draw_pixbuff, refresh_pixbuff;
GC            main_gfx_gc;
XtAppContext  xtcontext;
char          TimedOut = TRUE;
char          Resized  = FALSE;
char          Selected = FALSE;
const char   *timeout_property = "TimerExpiration";
Atom          xa_timeout;

void disp_expose_handler(Widget w, caddr_t client_data, XEvent *event);
void disp_config_handler(Widget w, caddr_t client_data, XEvent *event);
void disp_select_handler(Widget w, caddr_t client_data, XEvent *event);
void disp_nomask_handler(Widget w, caddr_t client_data, XEvent *event);
void disp_timeout_handler(XtPointer client_data, XtIntervalId *id);
void disp_refresh(void);
void disp_config(void);
int put_points(polygon_t *cur_poly);
int disp_checkgc(char *str);

/*---------------------------------------------------------------------------*/
/*--------------------------- EXPORTED FUNCTIONS ----------------------------*/
/*---------------------------------------------------------------------------*/

/* disp_init initializes the graphics output window and the drawing
 * area buffer (ie: scratch pixmap)
 */

void disp_init(int *argc, char *argv[], int color)
{
   Arg       wargs[2];
   XGCValues gcv;

   /* Initialize the X Intrinsics */

   toplevel = XtInitialize(argv[0], "Coolmail", NULL, 0, argc, argv);

   disp_config();  /* Make use of X Resources, if any */

   /* Create a widgit so we can draw in it's window. */

   /* First, set arguments for the size of the widget */
   XtSetArg(wargs[0], XtNwidth,  (Dimension) DEFAULT_GFX_WIDTH);
   XtSetArg(wargs[1], XtNheight, (Dimension) DEFAULT_GFX_HEIGHT);

   /* Now create the widget -- Any plain old widget will do, so use
    * a regular "widgetClass" widget.
    */
   main_gfx_w = XtCreateManagedWidget("picture",
                                     widgetClass,
                                     toplevel, wargs, 2);

   /* Realize the widget tree */
   XtRealizeWidget(toplevel);

   /* Now find out what geometry was ACTUALLY used */
   disp_getsize(&Gfx_Width, &Gfx_Height);

   /* Set size variables and allocate memory for drawing */
   disp_setsize(Gfx_Width, Gfx_Height);

   /* Allocate a bunch of colors */
   if(color == 0) color_mode = 0;
   init_colors(XtDisplay(main_gfx_w), color_table, color_mode);


   /* Create the graphics context for the main_gfx widget */
   main_gfx_gc = XCreateGC(XtDisplay(main_gfx_w),
                           XtWindow(main_gfx_w), 0, &gcv);
   XSetFillRule(XtDisplay(main_gfx_w), main_gfx_gc, EvenOddRule);

   /* Register event handlers to handle exposures, resizes, and
    * mouse clicks. */
   XtAddEventHandler(main_gfx_w, ExposureMask, FALSE,
                     (XtEventHandler)disp_expose_handler, NULL);
   XtAddEventHandler(main_gfx_w, StructureNotifyMask, FALSE,
                     (XtEventHandler)disp_config_handler, NULL);
   XtAddEventHandler(main_gfx_w, ButtonPressMask, FALSE,
                     (XtEventHandler)disp_select_handler, NULL);
   XtAddEventHandler(main_gfx_w, NoEventMask, True,
                     (XtEventHandler)disp_nomask_handler, NULL);
   xa_timeout = XInternAtom(XtDisplay(main_gfx_w), timeout_property, FALSE);
}

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

/* Hack to use the resource manager to optionally set the value of
 * some variables that we're not really supposed to know about */

void disp_config(void)
{
   char *str;
   Boolean mono = False;

   XtResource mailcmd  = {"mailCommand", "MailCommand", XtRString,
                          sizeof(str), 0, XtRString, command_str};
   XtResource mailfile = {"inbox", "Inbox", XtRString,
                          sizeof(str), 0, XtRString, mailfile_str};
   XtResource monoflag = {"monochrome", "Monochrome", XtRBoolean,
                          sizeof(mono), 0, XtRBoolean, &mono};
   XtResource frmcount = {"frameCount", "FrameCount", XtRInt,
                          sizeof(frames), 0, XtRInt, &frames};

#ifdef AUDIO
   XtResource soundfile  = {"soundFile", "SoundFile", XtRString,
                            sizeof(str), 0, XtRString, sndfile};
   XtResource volume   = {"volume", "Volume", XtRInt,
                          sizeof(cool_vol), 0, XtRInt, &cool_vol};
#endif

   XtGetApplicationResources(toplevel, &str, &mailcmd, 1, NULL, 0);
   strcpy(command_str, str);
   XtGetApplicationResources(toplevel, &str, &mailfile, 1, NULL, 0);
   strcpy(mailfile_str, str);
   XtGetApplicationResources(toplevel, &mono, &monoflag, 1, NULL, 0);
   if (mono) color_mode = 0;
   
   XtGetApplicationResources(toplevel, &frames, &frmcount, 1, NULL, 0);
   if (frames < 2)
      frames = 2;
   
#ifdef AUDIO
   XtGetApplicationResources(toplevel, &str, &soundfile, 1, NULL, 0);
   if(str)
      sndfile = (char *)strdup(str);

   XtGetApplicationResources(toplevel, &cool_vol, &volume, 1, NULL, 0);
   if      (cool_vol < 0)   cool_vol = 0;
   else if (cool_vol > 100) cool_vol = 100;
#endif

}

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

/* This can be filled in later -- for Unix systems, it is not crucial to
 * deallocate everything explicitly before exitting, since the OS will
 * reclaim resources automatically.
 */

void disp_end(void)
{
   ;
}

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

/* This is the exported function that is called to actually display a list of
 * polygons.
 */

void disp_polygons(polygon_node_t *first_pnode)
{
   polygon_node_t *curnode;
   polygon_t *cur_poly;


   /* Clear the drawing area before painting into it */
   XSetForeground(XtDisplay(main_gfx_w), main_gfx_gc,
                  BlackPixelOfScreen(XtScreen(main_gfx_w)));
   XFillRectangle(XtDisplay(main_gfx_w), draw_pixbuff, main_gfx_gc,
                  0, 0, Gfx_Width, Gfx_Height);

   /* Now draw each polygon */
   for(curnode = first_pnode; curnode; curnode = curnode->next)
   {
      cur_poly = curnode->polygon;

      /* Skip all polygons not in front of the focus (origin) */
      if (curnode->k <= 0.0) break;

      XSetForeground(XtDisplay(main_gfx_w), main_gfx_gc,
                     color_table[cur_poly->color]);

      XFillPolygon(XtDisplay(main_gfx_w), draw_pixbuff, main_gfx_gc,
                   (XPoint *)(cur_poly->int_points), cur_poly->npoints, Convex,
                   CoordModeOrigin);

      if(!color_mode)  /* Draw the outline of the polygon */
      {
         XSetForeground(XtDisplay(main_gfx_w), main_gfx_gc,
                        BlackPixelOfScreen(XtScreen(main_gfx_w)));
         XDrawLines(XtDisplay(main_gfx_w), draw_pixbuff, main_gfx_gc,
                   (XPoint *)(cur_poly->int_points), cur_poly->npoints,
                   CoordModeOrigin);
         XDrawLine(XtDisplay(main_gfx_w), draw_pixbuff, main_gfx_gc,
                   (int)(cur_poly->int_points[0].x),
                   (int)(cur_poly->int_points[0].y),
                   (int)(cur_poly->int_points[cur_poly->npoints-1].x),
                   (int)(cur_poly->int_points[cur_poly->npoints-1].y));
      }
   }

   /* Copy the drawing from the drawing buffer to the refresh buffer */
   XCopyArea(XtDisplay(main_gfx_w), draw_pixbuff, refresh_pixbuff,
             main_gfx_gc, 0, 0, Gfx_Width, Gfx_Height, 0, 0);

   disp_refresh();
}

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

/* Pause execution until graphics processing has caught up. */

void disp_sync(void)
{
   XSync(XtDisplay(main_gfx_w), 0);
}

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

/* This function is called to freeze the display.
 * It returns when either: (1) the specified interval (in milliseconds)
 * has expired, (2) the display area is selected with the mouse, or
 * (3) the window is resized.  Note that another timer even is not requested
 * if an earlier requesten one has not yet occurred.
 */

int disp_freeze(unsigned long interval)
{
   XEvent event;

   if (TimedOut && interval > 0)  /* Have at most one TimeOut at a time */
   {
      TimedOut = FALSE;  /* Critical - do not move this out of the `if' */
      XtAddTimeOut(interval, disp_timeout_handler, NULL);
   }

   /* Enter the endless event loop */
   while (!((interval > 0 && TimedOut) || Selected || Resized))
   {
      XtNextEvent(&event);

      switch (event.type)
      {
       case ClientMessage:
         disp_nomask_handler(main_gfx_w, NULL, &event);
         break;
       default:
         XtDispatchEvent(&event);
      }
   }
   if      (interval > 0 && TimedOut)    return (TIMEOUT);
   else if (Resized)  {Resized  = FALSE; return (RESIZE);}
   else               {Selected = FALSE; return (SELECTION);}
}
/*---------------------------------------------------------------------------*/

void disp_bell(void)
{
/* HERE change this to go along with beep */
#ifdef AUDIO
   if (sndfile)
      /* play the soundfile */
      audio_beep();
   else	/* no sound file chosen then use system bell */
      XBell(XtDisplay(main_gfx_w), cool_vol);
#else
   XBell(XtDisplay(main_gfx_w), 50);
#endif
}

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

/* Perform operations necessary to change the size of the drawing area.
 * This involves changing the window size variables, as well as the
 * offset for the cartesian origin, and reallocating the pixmaps associated
 * with drawing and displaying.
 */

void disp_setsize(unsigned short width, unsigned short height)
{
   static char first_time = TRUE;

   /* Check to see if setting the size is really necessary */
   if (Gfx_Width == width && Gfx_Height == height && !first_time)
      return;

   Gfx_Width    = width;
   Gfx_Height   = height;

   disp_x_scale = ((float)Gfx_Width)  / ((float)(DEFAULT_GFX_WIDTH));
   disp_y_scale = ((float)Gfx_Height) / ((float)(DEFAULT_GFX_HEIGHT));

   Gfx_X_Origin = (int)(0.5 + disp_x_scale * (double)DEFAULT_X_ORIGIN);
   Gfx_Y_Origin = (int)(0.5 + disp_y_scale * (double)DEFAULT_Y_ORIGIN);

   /* Free up the pixmaps (Unless they haven't yet been allocated!) */
   if (first_time)
      first_time = FALSE;
   else
   {
      XFreePixmap(XtDisplay(main_gfx_w), draw_pixbuff);
      XFreePixmap(XtDisplay(main_gfx_w), refresh_pixbuff);
   }

   /* Allocate the draw_pixbuff memory for rendering into */
   draw_pixbuff = XCreatePixmap(XtDisplay(main_gfx_w),
                                   DefaultRootWindow(XtDisplay(main_gfx_w)),
                                   Gfx_Width, Gfx_Height,
                                   DefaultDepthOfScreen(XtScreen(main_gfx_w)));

   /* Allocate the refresh_pixbuff for storing finished frames.  This
    * Buffer will drive the actual display.
    */
   refresh_pixbuff = XCreatePixmap(XtDisplay(main_gfx_w),
                                DefaultRootWindow(XtDisplay(main_gfx_w)),
                                Gfx_Width, Gfx_Height,
                                DefaultDepthOfScreen(XtScreen(main_gfx_w)));
}

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

/* Get the current output window's size */

void disp_getsize(unsigned short *width, unsigned short *height)
{

   Arg wargs[2];

   XtSetArg(wargs[0], XtNwidth,  width);
   XtSetArg(wargs[1], XtNheight, height);
   XtGetValues(main_gfx_w, wargs, 2);

}

/*---------------------------------------------------------------------------*/
/*------------------------ INTERNALLY USED FUNCTIONS ------------------------*/
/*---------------------------------------------------------------------------*/

/* Handle exposure of the VR area.
 * The display is simply refreshed.
 */

void disp_expose_handler(Widget w, caddr_t client_data, XEvent *event)
{
   if (((XExposeEvent *)event)->count > 0) return;

   disp_refresh();
}

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

/* This handler is invoked when the output window's structure has
 * changed.  Check to see if the changes involved a window resizing.
 * If so, trip the Resized flag.
 */

void disp_config_handler(Widget w, caddr_t client_data, XEvent *event)
{
   XConfigureEvent *config_event = (XConfigureEvent *) event;

   if (config_event->width != Gfx_Width || config_event->height != Gfx_Height)
      Resized = TRUE;
}

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

/* Handle mouse button presses in the display area.
 */

void disp_select_handler(Widget w, caddr_t client_data, XEvent *event)
{
   Selected = TRUE;
}

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

/* Handle nonmaskable events, such as ClentMessage events.  Currently, I
 * am only interested in timeout events, so I'll just ingore anything else.
 */

void disp_nomask_handler(Widget w, caddr_t client_data, XEvent *event)
{

   switch(event->type)
   {
    case ClientMessage:
      if(event->xclient.message_type == xa_timeout)
         TimedOut = TRUE;
      break;
    default:
      break;
   }
}
/*---------------------------------------------------------------------------*/

/* Handle a timeout.  Since Timeouts are not X events, we need to generate an
 * X event to wake up the X event loop, which is waiting for an X event
 * to tell it that something has happenned.
 */

void disp_timeout_handler(XtPointer  client_data, XtIntervalId *id)
{
     static XEvent dummy_event;

     dummy_event.type = ClientMessage;
     dummy_event.xclient.message_type = xa_timeout;
     dummy_event.xclient.format       = 8;
     dummy_event.xclient.data.b[0]    = (char) 0;

     XSendEvent(XtDisplay(main_gfx_w), XtWindow(main_gfx_w), FALSE,
                ExposureMask, &dummy_event);
}

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

/* Copy whatever is in the main_pixbuff to the display area */

void disp_refresh(void)
{
   XCopyArea(XtDisplay(main_gfx_w), refresh_pixbuff, XtWindow(main_gfx_w),
             main_gfx_gc, 0, 0, Gfx_Width, Gfx_Height, 0, 0);
   XFlush(XtDisplay(main_gfx_w));
}

/*---------------------------------------------------------------------------*/
/*--------------------------- DEBUGGING FUNCTIONS ---------------------------*/
/*---------------------------------------------------------------------------*/

/* Check that the gc is okay by using it to set the foreground color */

int disp_checkgc(char *str)
{
   fprintf(stderr, "GC Check (%s) ... ", str);
   XSetForeground(XtDisplay(main_gfx_w), main_gfx_gc,
                  BlackPixelOfScreen(XtScreen(main_gfx_w)));
   fprintf(stderr, "passed.\n");
   return(1);
}

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

int put_points(polygon_t *cur_poly)
{
   int i;
   for(i = 0; i < cur_poly->npoints; i++)
   {
      printf("         (%d, %d)\n", cur_poly->int_points[i].x,
                                    cur_poly->int_points[i].y);
   }
   return(0);
}

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

/* EOF: display_list.c */
