/*****************************************************************************
 * draw.c: a simple graphics drawing program.
 *
 *         From:
 *                   The X Window System, 
 *            Programming and Applications with Xt
 *                   OPEN LOOK Edition
 *         by
 *              Douglas Young & John Pew
 *              Prentice Hall, 1991
 *
 *              Example described on pages: 
 *
 *
 *  Copyright 1991 by Prentice Hall
 *  All Rights Reserved
 *
 * This code is based on the OPEN LOOK Intrinsics Toolkit (OLIT) and 
 * the X Window System
 *
 * Permission to use, copy, modify, and distribute this software for 
 * any purpose and without fee is hereby granted, provided that the above
 * copyright notice appear in all copies and that both the copyright notice
 * and this permission notice appear in supporting documentation.
 *
 * Prentice Hall and the authors disclaim all warranties with regard to 
 * this software, including all implied warranties of merchantability and 
 * fitness.
 * In no event shall Prentice Hall or the authors be liable for any special,
 * indirect or consequential damages or any damages whatsoever resulting from 
 * loss of use, data or profits, whether in an action of contract, negligence 
 * or other tortious action, arising out of or in connection with the use 
 * or performance of this software.
 *
 * OPEN LOOK is a trademark of UNIX System Laboratories.
 * X Window System is a trademark of the Massachusetts Institute of Technology
 ****************************************************************************/

#include "draw.h"

main(argc, argv)
  int   argc;
  char *argv[];
{
  Widget        toplevel, canvas, framework, command, tiles;
  graphics_data data;
  int           n;
  Arg           wargs[10];
    
  toplevel = OlInitialize(argv[0], "Draw", NULL, 0, &argc, argv);
  framework = XtCreateManagedWidget("framework", formWidgetClass, 
                                    toplevel, NULL, 0);
  /*
   * Create the column to hold the commands.
   */
  n = 0;
  XtSetArg(wargs[n], XtNlayoutType, OL_FIXEDCOLS);n++;
  XtSetArg(wargs[n], XtNmeasure, 1);n++;
  command = XtCreateManagedWidget("command", 
                                  controlAreaWidgetClass, 
                                  framework, wargs, n);
  /*
   * Create the drawing surface and add the 
   * rubber banding callbacks.
   */
  canvas = XtCreateManagedWidget("canvas", drawAreaWidgetClass, 
                                 framework, NULL, 0);
  XtAddCallback(canvas, XtNexposeCallback, refresh, &data);

  XtAddEventHandler(canvas, ButtonPressMask, FALSE,
                    start_rubber_band, &data);
  XtAddEventHandler(canvas, ButtonMotionMask, FALSE,
                    track_rubber_band, &data);
  XtAddEventHandler(canvas, ButtonReleaseMask, FALSE,
                    end_rubber_band, &data);
  n = 0;
  XtSetArg(wargs[n], XtNyAttachBottom, TRUE);n++;
  XtSetArg(wargs[n], XtNyVaryOffset,   FALSE);n++;
  XtSetArg(wargs[n], XtNyResizable,    TRUE);n++;
  XtSetArg(wargs[n], XtNxAttachRight,  TRUE);n++;
  XtSetArg(wargs[n], XtNxVaryOffset,   FALSE);n++;
  XtSetArg(wargs[n], XtNxResizable,    TRUE);n++;
  XtSetArg(wargs[n], XtNxRefWidget,    command);n++;
  XtSetArg(wargs[n], XtNxAddWidth,     TRUE);n++;
  XtSetValues(canvas, wargs, n);
  /*
   * Initialize the graphics buffer and other data.
   */
  init_data(canvas, &data);
  /*
   * Add the drawing command panel.
   */ 
  create_drawing_commands(command, &data);
  /*
   * Add a palette of fill patterns.
   */
  tiles = xs_create_pixmap_browser(command,bitmaps, 
                                   XtNumber(bitmaps), 
                                   set_fill_pattern, &data,
                                   NULL);
  XtManageChild(tiles);
  XtRealizeWidget(toplevel);
  /*
   * Establish a passive grab on the drawing canvas window.
   */
  XGrabButton(XtDisplay(canvas), AnyButton, AnyModifier, 
              XtWindow(canvas), TRUE, 
              ButtonPressMask | ButtonMotionMask | 
              ButtonReleaseMask,
              GrabModeAsync, GrabModeAsync,
              XtWindow(canvas), 
              XCreateFontCursor(XtDisplay(canvas),
                                XC_crosshair));
  XtMainLoop();
}

struct {
  char   *name;
  void   (*func)();
}  command_info[] = {
      "Line",             draw_line,
      "Circle",           draw_circle,
      "Rectangle",        draw_rectangle,
      "Filled Circle",    draw_filled_circle,
      "Filled Rectangle", draw_filled_rectangle
};

void
create_drawing_commands(parent, data)
  Widget         parent;
  graphics_data *data;
{
  Widget  w, commands;
  Arg     wargs[2];
  int     i, n;
  /*
   * Group all commands in a column.
   */

  n = 0;
  XtSetArg(wargs[n], XtNlayoutType, OL_FIXEDCOLS); n++;
  XtSetArg(wargs[n], XtNmeasure, 1); n++;
  commands = XtCreateManagedWidget("commands", 
                                   exclusivesWidgetClass, parent, 
                                   wargs, n);
  /*
   * Create a button for each drawing function.
   */
  for(i=0;i < XtNumber(command_info); i++){
    XtSetArg(wargs[0], XtNuserData, command_info[i].func);
    w = XtCreateManagedWidget(command_info[i].name,
                              rectButtonWidgetClass, 
                              commands, wargs, 1);
    XtAddCallback(w, XtNselect, activate, data);
    if(i == 0)
      XtCallCallbacks(w, XtNselect, NULL);
  }
}

void
set_fill_pattern(w, client_data, call_data)
  Widget         w;
  XtPointer      client_data;
  XtPointer      call_data;
{
  graphics_data *data = (graphics_data *)client_data;
  Pixmap    tile;
  XImage    *image;
  int       i;
  XGCValues values;
  Arg       wargs[1];
  GC        gc;

  static int mask = GCForeground | GCBackground | 
                    GCTile | GCFillStyle;

  XtSetArg(wargs[0], XtNuserData, &image);
  XtGetValues(w, wargs, 1);
  tile = XCreatePixmap(XtDisplay(w),
                       DefaultRootWindow(XtDisplay(w)),
                       solid_width, solid_height,
                       OlDepthOfObject(w));
  
  values.foreground = data->foreground;
  values.background = data->background;
  gc = XCreateGC(XtDisplay(w), DefaultRootWindow(XtDisplay(w)),
                GCForeground | GCBackground, &values);
  XPutImage(XtDisplay(w), tile, gc, image,
            0, 0, 0, 0, solid_width, solid_height);
  /* 
   * Get a GC using this tile pattern 
   */
  values.foreground = data->foreground;
  values.background = data->background;
  values.fill_style = FillTiled;
  values.tile       = tile;
  data->gc = XtGetGC(w, mask, &values);   
}

init_data(w, data)
  Widget          w;
  graphics_data  *data;
{
  XGCValues values;
  Arg       wargs[5];
  data->current_func = NULL;
  data->next_pos     = 0;

  /*
   * Get the colors the user has set for the widget. 
   */
  XtSetArg(wargs[0], XtNbackground, &data->background);
  XtSetArg(wargs[1], XtNforeground, &data->foreground);
  XtGetValues(w, wargs, 2);
  /*
   * Fill in the values structure
   */  
  values.foreground = data->foreground;
  values.background = data->background;
  values.fill_style = FillTiled;
  /*
   * Get the GC used for drawing.
   */
  data->gc= XtGetGC(w, GCForeground | GCBackground |
                       GCFillStyle, &values);
  /*
   * Get a second GC in XOR mode for rubber banding.
   */
  data->xorgc = xs_create_xor_gc(w);
}

void 
start_rubber_band(w, data, event)
  Widget          w;
  graphics_data  *data;
  XEvent         *event;
{
  if(data->current_func){
    /*
     * Store the starting point and draw the initial figure.
     */
    data->last_x = data->start_x = event->xbutton.x;
    data->last_y = data->start_y = event->xbutton.y;
    (*(data->current_func))(w, data->xorgc,
                            data->start_x, data->start_y, 
                            data->last_x, data->last_y);
  }
}

void
track_rubber_band(w, data, event)
  Widget          w;
  graphics_data  *data;
  XEvent         *event;
{ 
  if(data->current_func){
    /*
     * Erase the previous figure.
     */
    (*(data->current_func))(w, data->xorgc,
                            data->start_x, data->start_y, 
                            data->last_x, data->last_y);
    /*
     * Update the last point.
     */
    data->last_x  =  event->xbutton.x;
    data->last_y  =  event->xbutton.y;
    /*
     * Draw the figure in the new position.
     */
    (*(data->current_func))(w, data->xorgc,
                            data->start_x, data->start_y, 
                            data->last_x, data->last_y);
  }
}

void
end_rubber_band(w, data, event)
  Widget          w;
  graphics_data  *data;
  XEvent         *event;
{
  if(data->current_func){
    /*
     * Erase the XOR image. 
     */
    (*(data->current_func))(w, data->xorgc,
                            data->start_x, data->start_y, 
                            data->last_x, data->last_y);
    /*
     * Draw the figure using the normal GC. 
     */
    (*(data->current_func))(w, data->gc,
                            data->start_x, data->start_y, 
                            event->xbutton.x, 
                            event->xbutton.y);
    /*
     * Update the data, and store the object in 
     * the graphics buffer.
     */
    data->last_x  =  event->xbutton.x;
    data->last_y  =  event->xbutton.y;
    store_object(data);
  }
}

void 
draw_line(w, gc, x, y, x2, y2)
  Widget   w;
  GC       gc;
  Position x, y, x2, y2;
{
  Display *dpy = XtDisplay(w);
  Window   win = XtWindow(w);
  XDrawLine(dpy, win, gc, x, y, x2, y2);
}

void 
draw_rectangle(w, gc, x, y, x2, y2)
  Widget   w;
  GC       gc;
  Position x, y, x2, y2;
{
  Display *dpy = XtDisplay(w);
  Window   win = XtWindow(w);

  check_points(&x, &y, &x2, &y2);
  XDrawRectangle(dpy, win, gc,  x, y, x2 - x, y2 - y);
}

void
draw_filled_rectangle(w, gc, x, y, x2, y2)
  Widget    w;
  GC        gc;
  Position  x, y, x2, y2;
{
  Display *dpy = XtDisplay(w);
  Window   win = XtWindow(w);

  check_points(&x, &y, &x2, &y2);
  XFillRectangle(dpy, win, gc, x, y, x2 - x, y2 - y);
}

check_points(x, y, x2, y2)
  Position *x, *y, *x2, *y2;
{
  if(*x2 < *x){ Position tmp = *x; *x = *x2; *x2 = tmp;}
  if(*y2 < *y){ Position tmp = *y; *y = *y2; *y2 = tmp;}
}

void
draw_circle(w, gc, x, y, x2, y2)
  Widget    w;
  GC        gc;
  Position  x, y, x2, y2;
{
  Display *dpy = XtDisplay(w);
  Window   win = XtWindow(w);

  check_points(&x, &y, &x2, &y2);
  XDrawArc(dpy, win, gc, x, y, x2 - x, y2 - y, 0, 64 * 360);
}

void
draw_filled_circle(w, gc, x, y, x2, y2)
  Widget    w;
  GC        gc;
  Position  x, y, x2, y2;
{
  Display *dpy = XtDisplay(w);
  Window   win = XtWindow(w);

  check_points(&x, &y, &x2, &y2);
  XFillArc(dpy, win, gc, x, y, x2 - x, y2 - y, 0, 64 * 360);
}

void
activate(w, client_data, call_data)
  Widget          w;
  XtPointer   client_data;
  XtPointer   call_data;
{
  graphics_data  *data = (graphics_data *)client_data;
  int (*func)();
  Arg wargs[5];

  XtSetArg(wargs[0], XtNuserData, &func);
  XtGetValues(w, wargs, 1);
  data->current_func = func; 
}

store_object(data)
  graphics_data *data;
{
  /* 
   * Check for space.
   */
  if(data->next_pos >= MAXOBJECTS) {
    printf("Warning: Graphics buffer is full\n");
    return;
  }
  /*
   * Save everything we need to draw this object again.
   */
  data->buffer[data->next_pos].x1 = data->start_x;
  data->buffer[data->next_pos].y1 = data->start_y;
  data->buffer[data->next_pos].x2 = data->last_x;
  data->buffer[data->next_pos].y2 = data->last_y;
  data->buffer[data->next_pos].func = data->current_func;
  data->buffer[data->next_pos].gc = data->gc;
  /*
   * Increment the next position index.
   */
  data->next_pos++;
}

void refresh(w, client_data, call_data)
  Widget          w;
  XtPointer       client_data;
  XtPointer       call_data;
{
  graphics_data  *data = (graphics_data *)client_data;
  int i;

  for(i=0;i<data->next_pos;i++)
    (*(data->buffer[i].func))(w, data->buffer[i].gc,
                               data->buffer[i].x1,
                               data->buffer[i].y1,
                               data->buffer[i].x2,
                               data->buffer[i].y2);
}
