/* Bitmap.c: implementation of a Bitmap widget as a subclass of Athena's
   Label widget.

Copyright (C) 1992 Free Software Foundation, Inc.

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */

#include "config.h"

#include "xt-common.h"

#include "bitmap.h"

#include "BitmapP.h"

/* Says whether the rectangle we draw to represent the temporary
   selection is visible, i.e., if we've erased it.  */
static boolean selection_showing = false;

/* Utility routines.  */
static Pixmap bitmap_to_pixmap (Display *, bitmap_type, unsigned);
static void check_action_parameters (string, BitmapWidget, Cardinal, Cardinal);
static boolean find_bitmap_position (XEvent *, BitmapWidget, int *, int *);
static coordinate_type get_pointer_position (XEvent *);
static unsigned param_value (string, string []);
static void redraw_pixel (BitmapWidget, one_byte, long, int, int);

/* Subroutines for handling selections.  */
static bounding_box_type find_selection_bb (BitmapWidget);
static void redraw_selection_bitmap (BitmapWidget, bitmap_type);
static void redraw_selection (BitmapWidget, bitmap_type);
static void show_selection (BitmapWidget);
static void unshow_selection (BitmapWidget);
static void update_selection (BitmapWidget, unsigned, unsigned);



/* Action routines.  */
static action_proc_type
  invert_pixel, fill_selection,
  start_selection, adjust_selection, accept_selection,
  start_paste, move_paste, accept_paste;
                  
/* The mapping of action names to procedures.  */
static XtActionsRec bitmap_actions[]
  = { { "InvertPixel",	       	invert_pixel },
      { "FillSelection",	fill_selection },
      { "StartSelection",      	start_selection },
      { "AdjustSelection",     	adjust_selection },
      { "AcceptSelection",     	accept_selection },
      { "StartPaste",	       	start_paste },
      { "MovePaste",	       	move_paste },
      { "AcceptPaste",		accept_paste },
    };


/* This structure defines the default values of the resources specific
   to the Bitmap widget.  */
#define OFFSET(field) XtOffset (BitmapWidget, bitmap.field)
static XtResource bitmap_resources[]
  = { IMMEDIATE_RESOURCE (expansion, Expansion, Dimension,
                          BITMAP_DEFAULT_EXPANSION),
      IMMEDIATE_RESOURCE (bits, Bitmap, Pointer, NULL),
      IMMEDIATE_RESOURCE (modified, Modified, Boolean, False),
      IMMEDIATE_RESOURCE (shadow, Bitmap, Widget, NULL),
    };


/* Routines in the class record.  */
static void bitmap_initialize (Widget, Widget, ArgList, Cardinal *);
static Boolean bitmap_set_values (Widget, Widget, Widget, ArgList, Cardinal *);
static void bitmap_destroy (Widget);

/* We can inherit most things in the instantiation of the class record.
   The operations `initialize', `set_values', and `destroy' are chained
   automatically by the toolkit, and need no `XtInherit...' constant
   (Xt manual, p.21).  */
BitmapClassRec bitmapClassRec
  = { /* Core class fields.  */
      { /* superclass	  	 */ (WidgetClass) &labelClassRec,
        /* class_name	  	 */ "Bitmap",
        /* widget_size	  	 */ sizeof (BitmapRec),
        /* class_initialize   	 */ NULL,
        /* class_part_initialize */ NULL,
        /* class_inited       	 */ FALSE,
        /* initialize	  	 */ bitmap_initialize,
        /* initialize_hook	 */ NULL,
        /* realize		 */ XtInheritRealize,
        /* actions		 */ bitmap_actions,
        /* num_actions	  	 */ XtNumber (bitmap_actions),
        /* resources	  	 */ bitmap_resources,
        /* num_resources	 */ XtNumber (bitmap_resources),
        /* xrm_class	  	 */ NULLQUARK,
        /* compress_motion	 */ TRUE,
        /* compress_exposure  	 */ TRUE,
        /* compress_enterleave	 */ TRUE,
        /* visible_interest	 */ FALSE,
        /* destroy		 */ bitmap_destroy,
        /* resize		 */ XtInheritResize,
        /* expose		 */ XtInheritExpose,
        /* set_values	  	 */ bitmap_set_values,
        /* set_values_hook	 */ NULL,
        /* set_values_almost	 */ XtInheritSetValuesAlmost,
        /* get_values_hook	 */ NULL,
        /* accept_focus	 	 */ NULL,
        /* version		 */ XtVersion,
        /* callback_private   	 */ NULL,
        /* tm_table		 */ NULL,
        /* query_geometry	 */ XtInheritQueryGeometry,
        /* display_accelerator	 */ XtInheritDisplayAccelerator,
        /* extension		 */ NULL
      },

      /* Simple class fields.  */
      { /* change_sensitive */ XtInheritChangeSensitive },

      /* Label class fields.  */
      { 0 },

      /* Bitmap class fields.  */
      { 0 }
    };

WidgetClass bitmapWidgetClass = (WidgetClass) &bitmapClassRec;



/* Class routines.  */

/* This routine is called at widget creation time by the toolkit, after
   our superclasses have been initialized.  REQUEST is the widget that
   was originally requested by the user; NEW is the widget that has been
   created by our superclasses in response to the requests.
   
   We want to tell our Label superclass that it should deal with a pixmap,
   not with a string.
   
   We do not use the parameters ARGS and N_ARGS.  */

static void
bitmap_initialize (Widget request, Widget new, ArgList args, Cardinal *n_args)
{
  XGCValues select_gc_values;
  BitmapWidget old = xmalloc (sizeof (*old));
  BitmapWidget bw = (BitmapWidget) new;
  bitmap_type *bitmap_ptr = (bitmap_type *) bw->bitmap.bits;
  Arg bitmap_args[] = { { XtNbits, (XtArgVal) bitmap_ptr } };
  Cardinal n = XtNumber (bitmap_args);
  Display *d = XtDisplay (bw);

  if (bitmap_ptr == NULL)
    XtErrorMsg ("noData", "bitmapCreate", "BitmapError",
                "No bitmap specified.", NULL, 0);

  /* Convert the bitmap to a pixmap.  We have to make sure that we
     really do create the pixmap here, by forcing the old and new
     bitmaps to be different.  */
  *old = *(BitmapWidget) request;
  old->bitmap.bits = NULL;
  (void) bitmap_set_values ((Widget) old, request, new, bitmap_args, &n);

  /* Make GC's to use in editing the bits and doing the selection.  */
  bw->bitmap.edit_gc = XCreateGC (XtDisplay (bw), bw->label.pixmap, 0, NULL);
  select_gc_values.function = GXxor;
  select_gc_values.foreground = AllPlanes;
  bw->bitmap.select_gc = XCreateGC (d, bw->label.pixmap,
                                    GCFunction | GCForeground,
                                    &select_gc_values);

  /* If we have a shadow bitmap, we want to use the same GC and bitmap
     for it as we do for the current one.  We should probably do this in
     bitmap_set_values somewhere, also.  */
  if (bw->bitmap.shadow != NULL)
    {
      BitmapWidget shadow_bw = (BitmapWidget) bw->bitmap.shadow;
      XFreeGC (d, shadow_bw->bitmap.edit_gc);
      shadow_bw->bitmap.edit_gc = bw->bitmap.edit_gc;
    }

  /* Initially, we're unmodified, and have no selection.  */
  bw->bitmap.modified = false;
  bw->bitmap.selection = NULL;
}


/* This routine is called when one of the resources in the widget
   changes; for example, in response to an XtSetValues call.  It is also
   called by the `bitmap_initialize' routine.  The toolkit has already
   modified our resources.  CURRENT is the widget before any resources
   were changed; REQUEST is the widget before any class `set_values'
   procedures have been called; NEW is the widget as updated by the
   superclasses.

   We do not use the parameters ARGS and N_ARGS.  */

static Boolean
bitmap_set_values (Widget current, Widget request, Widget new,
                   ArgList args, Cardinal *n_args)
{
  BitmapWidget old_bw = (BitmapWidget) current;
  bitmap_type *old_bitmap_ptr = (bitmap_type *) old_bw->bitmap.bits;
  unsigned old_expansion = old_bw->bitmap.expansion;
  BitmapWidget bw = (BitmapWidget) new;
  bitmap_type *bitmap_ptr = (bitmap_type *) bw->bitmap.bits;
  unsigned expansion = bw->bitmap.expansion;

  if (bitmap_ptr == NULL)
    XtErrorMsg ("noData", "bitmapSetValues", "BitmapError",
                "No bitmap specified.", NULL, 0);

  if (bitmap_ptr == old_bitmap_ptr && expansion == old_expansion)
    return False; /* No redisplay needed.  */

  {
    Display *display = XtDisplay (bw);

    /* Our new width and height, in window coordinates.  */
    unsigned w = BITMAP_WIDTH (*bitmap_ptr) * expansion;
    unsigned h = BITMAP_HEIGHT (*bitmap_ptr) * expansion;

    /* The new image.  */
    Pixmap pixmap = bitmap_to_pixmap (display, *bitmap_ptr, expansion);

    Arg label_args[]
      = { { XtNbitmap,	pixmap },
          { XtNwidth,	w },
          { XtNheight,	h },
        };

    /* If we were already displaying a pixmap, free it.  Perhaps it
       would be better to keep it around, in case the user goes back
       to this character.  */
    if (bw->label.pixmap != None)
      XFreePixmap (display, bw->label.pixmap);

    /* The bitmap has changed, so we should clear the `modified' field.  */
    bw->bitmap.modified = false;

    /* Make the changes.  */
    XtSetValues ((Widget) bw, XTARG (label_args));

    /* We want to be redisplayed now.  */
    return True;
  }
}


/* This routine is called when the widget is destroyed.  We deallocate
   the GC resources we have explicitly created.  */

static void
bitmap_destroy (Widget w)
{
  BitmapWidget bw = (BitmapWidget) w;
  
  XFreeGC (XtDisplay (bw), bw->bitmap.edit_gc);
  XFreeGC (XtDisplay (bw), bw->bitmap.select_gc);
}  



/* These convenience procedures save clients the trouble of constructing
   an ArgList to get the resources in the Bitmap widget.  */

unsigned
BitmapExpansion (Widget w)
{
  BitmapWidget bw = (BitmapWidget) w;
  return bw->bitmap.expansion;
}


bitmap_type *
BitmapBits (Widget w)
{
  BitmapWidget bw = (BitmapWidget) w;
  return (bitmap_type *) bw->bitmap.bits;
}


Boolean
BitmapModified (Widget w)
{
  BitmapWidget bw = (BitmapWidget) w;
  return bw->bitmap.modified;
}



/* This utility routine converts the bitmap B into a Pixmap on the
   display DISPLAY.  Each pixel in B becomes EXPANSION pixels in the
   Pixmap.  If the pixmap creation fails, it gives a fatal error.  */

#define DEPTH 1  /* We don't need grayscale or color bitmaps.  */

static Pixmap
bitmap_to_pixmap (Display *display, bitmap_type b, unsigned expansion)
{
  GC gc;
  XGCValues gc_values;
  unsigned row;
  XRectangle *save_rectangle_list;
  XRectangle *rectangle_list = NULL;
  unsigned nrectangles = 0;
  
  unsigned height = BITMAP_HEIGHT (b) * expansion;
  unsigned w = BITMAP_WIDTH (b);
  unsigned width = BITMAP_WIDTH (b) * expansion;
  
  /* We need a drawable to create the pixmap on.  Since we know we have
     a root window, we don't have to be passed one.  */
  Window root_window = DefaultRootWindow (display);
  Pixmap pixmap = XCreatePixmap (display, root_window, width, height, DEPTH);

  if (pixmap == None)
    FATAL ("bitmap_to_pixmap: Could not allocate pixmap");

  /* Start with the foreground zero (which is the default), since we
     want to clear the pixmap before writing anything.  We want the
     background zero so that the gc will correspond to the bitmap.  */
  gc_values.background = 0;
  gc = XCreateGC (display, pixmap, GCBackground, &gc_values);

  /* Write the bitmap to the pixmap.  We want the one bits in the bitmap
     to end up as one bits in the pixmap, regardless of the actual
     foreground and background pixels in the application.  Why?  Because
     we (and the Label widget) use XCopyPlane to draw the pixmap, and
     XCopyPlane turns one bits into the foreground color, and zero bits
     in the background color.  */
  XFillRectangle (display, pixmap, gc, 0, 0, width, height);
  XSetForeground (display, gc, 1L);

  /* Instead of creating a separate rectangle for each pixel, we
     combine adjacent pixels in each scanline into one rectangle.  We
     could go further, and combine identical adjacent scanlines
     (analysis has shown this happens about 1/3 of the time).  Or we
     could go further still, and use some theory about decomposition
     into a minimal set of rectangles, as cited in `Rectangular
     Convolution for Fast Filtering of Characters', by Avi Naiman and
     Alain Fournier, in the July 1987 SIGGRAPH Proceedings.  But doing
     this simple job seems to make it fast enough.  */
  for (row = 0; row < BITMAP_HEIGHT (b); row++)
    {
      unsigned start;
      boolean done = false;
      one_byte *row_data = BITMAP_ROW (b, row);
      unsigned *transitions = bitmap_find_transitions (row_data, w);
      unsigned *save_transitions = transitions;
      
      /* We must perform the test on `done' before dereferencing
         `transitions' in the increment step of the loop, because if we
         are indeed done, `transitions' may point to garbage.
         bitmap_find_transitions guarantees an even number of
         transitions.  */
      for (start = *transitions++; start != w + 1;
           start = done ? w + 1 : *transitions++)
        {
          XRectangle *r;
          unsigned end = *transitions++;

          XRETALLOC (rectangle_list, ++nrectangles, XRectangle);
          r = &rectangle_list[nrectangles - 1];
          r->x = start * expansion;
          r->y = row * expansion;
          r->width = (end - start) * expansion;
          r->height = expansion;
        }
      free (save_transitions);
    }

  /* The protocol puts some limit on the number of rectangles that can
     be filled in a single request.  It is not uncommon for us to have
     thousands of rectangles in the list, either because of a
     large font (e.g., cminch), or a high resolution.  */
  #define MAX_NRECTANGLES 4000
  save_rectangle_list = rectangle_list;
  while (nrectangles > MAX_NRECTANGLES)
    {
      XFillRectangles (display, pixmap, gc, rectangle_list, MAX_NRECTANGLES);
      nrectangles -= MAX_NRECTANGLES;
      rectangle_list += MAX_NRECTANGLES;
    }
  XFillRectangles (display, pixmap, gc, rectangle_list, nrectangles);
  XFreeGC (display, gc);
  
  free (save_rectangle_list);
  
  return pixmap;
}


/* Redraw the pixel at (COL,ROW) in the bitmap widget BW.  Update the
   pixmap in the widget, and, if realized, the window.  */ 

static void
redraw_pixel (BitmapWidget bw, one_byte bitmap_color,
              long foreground_color, int row, int col)
{
  static long last_foreground = 0; /* XCreateGC's default.  */
  Display *display = XtDisplay (bw);
  unsigned expansion = bw->bitmap.expansion;
  int expanded_row = row * expansion,
      expanded_col = col * expansion;
  bitmap_type *bitmap = (bitmap_type *) bw->bitmap.bits;
  Pixmap label_pixmap = bw->label.pixmap;

  /* Update the bitmap structure.  */
  BITMAP_PIXEL (*bitmap, row, col) = bitmap_color;

  /* Only set the foreground if it has changed, for efficiency.  */
  if (foreground_color != last_foreground)
    {
      XSetForeground (display, bw->bitmap.edit_gc, foreground_color);
      last_foreground = foreground_color;
    }
  XFillRectangle (display, label_pixmap, bw->bitmap.edit_gc,
                  expanded_col, expanded_row, expansion, expansion);
  bw->bitmap.modified = true;
  
  /* We may as well call XCopyPlane ourselves, instead of generating an
     Expose event, since we have all the information necessary.  */
  if (XtIsRealized ((Widget) bw))
    XCopyPlane (display, label_pixmap, XtWindow (bw), bw->label.normal_GC,
                expanded_col, expanded_row, expansion, expansion,
                expanded_col, expanded_row, 1L);
}


/* Return the position of the pointer as recorded in EVENT.  */

static coordinate_type
get_pointer_position (XEvent *event)
{
  coordinate_type c;
  
  switch (event->type)
    {
    case MotionNotify:
      c.x = event->xmotion.x;
      c.y = event->xmotion.y;
      break;
    
    case ButtonPress:
    case ButtonRelease:
      c.x = event->xbutton.x;
      c.y = event->xbutton.y;
      break;
    
    case KeyPress:
    case KeyRelease:
      c.x = event->xkey.x;
      c.y = event->xkey.y;
      break;
    
    case EnterNotify:
    case LeaveNotify:
      c.x = event->xcrossing.x;
      c.y = event->xcrossing.y;
      break;
    
    default:
      c.x = 0;
      c.y = 0;
    }
  
  return c;
}


/* If SEEN != EXPECTED, or if there is no bitmap in BW to operate on,
   report an error for the action NAME.  */

static void
check_action_parameters (string name, BitmapWidget bw, Cardinal seen,
                         Cardinal expected)
{
  if (bw->bitmap.bits == NULL || bw->label.pixmap == None)
    XtErrorMsg ("noData", name, "BitmapError",
                "No bitmap specified", NULL, 0);
  
  if (seen != expected)
    {
      string error_params[] = { itoa (expected), itoa (seen) };
      unsigned n_error_params = XtNumber (error_params);
      XtErrorMsg ("invalidParameters", name, "BitmapError",
                  "Expected %s argument(s), found %s",
                  error_params, &n_error_params);
  }
}    


/* If V is not one of the elements in VALUE_LIST, abort with an error
   message.  Otherwise, return the (zero-based) index of V in the list.
   VALUE_LIST should end with an element that is NULL.  */

static unsigned
param_value (string v, string value_list[])
{
  unsigned ret = 0;
  
  while (value_list[ret] != NULL && !STREQ (value_list[ret], v))
    ret++;

  /* Did we find it?  */
  if (value_list[ret] == NULL)
    {
      unsigned this_value;
      unsigned n_error_params = 2;
      string error_params[n_error_params];
      
      error_params[0] = *value_list;
      for (this_value = 1; this_value < ret; this_value++)
        {
          string temp = error_params[0];
          error_params[0] = concat3 (error_params[0], ",",
                                     value_list[this_value]);
          free (temp);
        }
      
      error_params[1] = v;

      XtErrorMsg ("invalidParameter", "acceptPaste", "BitmapError",
                  "Expected one of (%s), found `%s'",
                  error_params, &n_error_params);
    }
  
  return ret;
}


/* Given a position in window coordinates, we sometimes need to
   translate it to an expanded pixel boundary.  We want to do this via
   rounding, instead of truncating, so that the user can select the
   pixels in the rightmost column and bottommost row of the bitmap.
   
   On the other hand, sometimes we want to truncate the position,
   instead of rounding, so that the starting positions are intuitive.
   
   We assume that the variable `bw' points to a bitmap widget.  */

#define EXPANSION_ROUND(n)						\
  (bw->bitmap.expansion							\
   * (((n) + bw->bitmap.expansion - 1) / bw->bitmap.expansion))

#define EXPANSION_TRUNC(n)						\
  (bw->bitmap.expansion * ((n) / bw->bitmap.expansion))


/* Return whether the cursor position in E is within the bitmap in BW.
   We assume that the bitmap is non-null.  In any case, return the row
   and column in window coordinates in ROW and COL.  */

static boolean
find_bitmap_position (XEvent *e, BitmapWidget bw, int *row, int *col)
{
  coordinate_type pos = get_pointer_position (e);
  bitmap_type b = *(bitmap_type *) bw->bitmap.bits;
  
  /* Convert from the window coordinates to the pixel in the bitmap.  */
  int bitmap_row = pos.y / bw->bitmap.expansion;
  int bitmap_col = pos.x / bw->bitmap.expansion;
  
  *row = pos.y;
  *col = pos.x;

  /* Return whether the row and column are outside the boundaries of the
     bitmap.  */
  return pos.y >= 0 && bitmap_row < BITMAP_HEIGHT (b)
    && pos.x >= 0 && bitmap_col < BITMAP_WIDTH (b);
}



/* Action procedures.  */

/* N_PARAMS should be exactly one, and PARAMS should point to either the
   string `Continuous' or `Discrete' (case is significant).  If the
   latter, we simply invert the pixel at the current mouse location, as
   specified in EVENT.  If the former, we only do the inversion if we
   are at a different pixel on this call than we were on the previous
   one.  */

static void
invert_pixel (Widget w, XEvent *event, String *params, Cardinal *n_params)
{
  static int last_row = -1; /* Expanded pixel position of the last event.  */
  static int last_col = -1;

  /* This is the color of the pixel at the pointer position at the time
     of the last `Discrete' call.  */
  static one_byte source_pixel;

  int row, col;
  
  one_byte target_color;
  long foreground_color;
  
  BitmapWidget bw = (BitmapWidget) w;
  bitmap_type *bitmap = (bitmap_type *) bw->bitmap.bits;
  BitmapWidget shadow_bw = (BitmapWidget) bw->bitmap.shadow;

  check_action_parameters ("invertPixel", bw, *n_params, 1);
  if (!find_bitmap_position (event, bw, &row, &col))
    return;
  
  row /= bw->bitmap.expansion;
  col /= bw->bitmap.expansion;

  if (STREQ (*params, "Continuous"))  
    { /* If we are still within the same (expanded) pixel, do nothing.  */
      if (col == last_col && row == last_row)
        return;

      /* We've moved, so do the inversion.  */
      last_col = col;
      last_row = row;
    }
  else if (STREQ (*params, "Discrete"))
    {
      /* Update `last_row' and `last_col' here, because it is easy to
         jiggle (inadvertently) the mouse a little after clicking.  The
         jiggling will cause a motion event that will invoke us again with
         `Continuous' as the parameter.  If we didn't update the current
         position here, that invocation would invert the pixel again.  */
      last_col = col;
      last_row = row;
      source_pixel = BITMAP_PIXEL (*bitmap, row, col);
    }
  else
    {
      unsigned n_error_params = 1;
      string error_params[1] = { *params };
      XtErrorMsg ("invalidParameter", "invertPixel", "BitmapError",
                  "Expected `Continuous' or `Discrete', found `%s'",
                  error_params, &n_error_params);
    }

  /* See comments in `bitmap_to_pixmap' for why the constants 1 and 0
     are appropriate here.  We depend on getting a `Discrete' event
     before any `Continuous' events; otherwise, `target_color' might be
     garbage.
     
     We are not testing the color of the pixel at the current
     position here.  Why?  Because we want to change pixels on
     `Continuous' events to a single color, not inverting them.  See
     `Bitmap.h' for more discussion of this.  */
  if (source_pixel == WHITE)
    { /* Make it black.  */
      target_color = BLACK;
      foreground_color = 1;
    }
  else
    { /* Make it white.  */
      target_color = WHITE;
      foreground_color = 0;
    }
  
  /* Update ourselves.  */
  redraw_pixel (bw, target_color, foreground_color, row, col);

  /* Update our shadow bitmap, if we have one.  */
  if (shadow_bw != NULL)
    redraw_pixel (shadow_bw, target_color, foreground_color, row, col);
}


/* Fill the selection in the bitmap widget W with the color of the pixel
   the pointer is on, as recorded in EVENT.  N_PARAMS should be zero.  */ 
   
static void
fill_selection (Widget w, XEvent *event, String *params, Cardinal *n_params)
{
  int row, col; /* The coordinates in the bitmap.  */
  BitmapWidget bw = (BitmapWidget) w;

  check_action_parameters ("fillSelection", bw, *n_params, 0);
  if (bw->bitmap.selection != NULL
      && bw->bitmap.select_width > 0 && bw->bitmap.select_height > 0
      && find_bitmap_position (event, bw, &row, &col))
    {
      bitmap_type new;
      bitmap_type *bitmap = (bitmap_type *) bw->bitmap.bits;
      one_byte color = BITMAP_PIXEL (*bitmap, row / bw->bitmap.expansion,
                                     col / bw->bitmap.expansion);
      bounding_box_type select_bb = find_selection_bb (bw);

      for (row = MIN_ROW (select_bb); row <= MAX_ROW (select_bb); row++)
        for (col = MIN_COL (select_bb); col < MAX_COL (select_bb); col++)
          BITMAP_PIXEL (*bitmap, row, col) = color;
      
      bw->bitmap.modified = true;

      new = extract_subbitmap (*bitmap, select_bb);
      redraw_selection (bw, new);
      free_bitmap (&new);
    }
}


/* Prepare to select a rectangle whose upper left corner is the pixel
   enclosing the position recorded in EVENT.  N_PARAMS should be zero.  */

static void
start_selection (Widget w, XEvent *event, String *params, Cardinal *n_params)
{
  int row, col; /* The coordinates in the bitmap.  */
  BitmapWidget bw = (BitmapWidget) w;

  check_action_parameters ("startSelection", bw, *n_params, 0);
  if (find_bitmap_position (event, bw, &row, &col))
    {
      /* Set the upper left corner and initialize the lower right corner to
         the current position.  */
      bw->bitmap.initial_select.x = bw->bitmap.select_ul.x =
        EXPANSION_TRUNC (col);
      bw->bitmap.initial_select.y = bw->bitmap.select_ul.y = 
        EXPANSION_TRUNC (row);
      bw->bitmap.select_width = bw->bitmap.select_height = 0;
    }
}


/* Take the point in EVENT as the new second point to define the
   selection.  */

static void
adjust_selection (Widget w, XEvent *event, String *params, Cardinal *n_params)
{
  int row, col; /* The coordinates in the bitmap.  */
  BitmapWidget bw = (BitmapWidget) w;

  check_action_parameters ("adjustSelection", bw, *n_params, 0);
  unshow_selection (bw);

  if (find_bitmap_position (event, bw, &row, &col))
    { 
      update_selection (bw, row, col);
      show_selection (bw);
      selection_showing = true;
    }
}


/* Take the point in EVENT to be the definitive second point for the
   selection.  We therefore can now set the (sub)bitmap that the selected
   rectangle defines, for pasting.  */

static void
accept_selection (Widget w, XEvent *event, String *params, Cardinal *n_params)
{
  int row, col; /* The coordinates in the bitmap.  */
  BitmapWidget bw = (BitmapWidget) w;

  check_action_parameters ("acceptSelection", bw, *n_params, 0);
  unshow_selection (bw);

  /* If the selection is empty, do nothing.  Do not clear out the
     previous selection, even.  */
  if (bw->bitmap.select_width > 0 && bw->bitmap.select_height > 0
      && find_bitmap_position (event, bw, &row, &col))
    {
      bounding_box_type select_bb;

      /* Set the final coordinates.  */
      update_selection (bw, row, col);

      /* Free the old bitmap.  */
      if (bw->bitmap.selection != NULL)
        free_bitmap (bw->bitmap.selection);

      /* Make the new one.  */
      select_bb = find_selection_bb (bw);
      bw->bitmap.selection = xmalloc (sizeof (bitmap_type));
      *(bw->bitmap.selection)
        = extract_subbitmap (*(bitmap_type *) bw->bitmap.bits, select_bb);
    }
}


/* If something has been selected, show its bounding rectangle with the
   upper left corner at the position in EVENT (so the user can see where
   the paste will happen).  */

static void
start_paste (Widget w, XEvent *event, String *params, Cardinal *n_params)
{
  int row, col; /* The coordinates in the bitmap.  */
  BitmapWidget bw = (BitmapWidget) w;

  check_action_parameters ("startPaste", bw, *n_params, 0);
  /* We obviously can't paste anything if nothing has been selected.
     Perhaps it would be better to show a warning message in that case,
     but for now we just do nothing.  */
  if (bw->bitmap.selection != NULL
      && find_bitmap_position (event, bw, &row, &col))
    {
      bw->bitmap.select_ul.x = EXPANSION_TRUNC (col);
      bw->bitmap.select_ul.y = EXPANSION_TRUNC (row);
      show_selection (bw);
      selection_showing = true;
    }
}


/* Update where the paste rectangle is according to EVENT.  */

static void
move_paste (Widget w, XEvent *event, String *params, Cardinal *n_params)
{
  int row, col; /* The coordinates in the bitmap.  */
  BitmapWidget bw = (BitmapWidget) w;

  check_action_parameters ("movePaste", bw, *n_params, 0);
  unshow_selection (bw);

  if (bw->bitmap.selection != NULL
      && find_bitmap_position (event, bw, &row, &col))
    {
      bw->bitmap.select_ul.x = EXPANSION_TRUNC (col);
      bw->bitmap.select_ul.y = EXPANSION_TRUNC (row);
      show_selection (bw); /* Show the new.  */
      selection_showing = true;
    }
}


/* Take the position in EVENT as the place to paste the selection, and
   update the bitmap.  Either ignore or keep the black in the bitmap
   underneath the paste according to first element of PARAMS, which must
   be either `Opaque' or `Transparent'.  We flip the bitmap vertically,
   horizontally, both, or neither according to the second parameter
   being `FlipVertical', `FlipHorizontal', `FlipBoth', or `FlipNeither'.  */

static void
accept_paste (Widget w, XEvent *event, String *params, Cardinal *n_params) 
{
  coordinate_type paste; /* The coordinates in the bitmap.  */
  int row, col;
  BitmapWidget bw = (BitmapWidget) w;

  check_action_parameters ("acceptPaste", bw, *n_params, 2);
  unshow_selection (bw);

  if (bw->bitmap.selection != NULL
      && find_bitmap_position (event, bw, &row, &col))
    {
      enum { flip_neither, flip_horizontal, flip_vertical, flip_both } flip;
      boolean opaque;
      unsigned source_row, source_col;
      unsigned target_row, target_col;
      unsigned this_row, this_col;
      string param1_values[]
        = { "Transparent", "Opaque", NULL };
      string param2_values[]
        = { "FlipNeither", "FlipHorizontal", "FlipVertical", "FlipBoth",
            NULL };

      bitmap_type source = *(bw->bitmap.selection);
      unsigned source_height = BITMAP_HEIGHT (source),
               source_width = BITMAP_WIDTH (source);
      bitmap_type *target = (bitmap_type *) bw->bitmap.bits;
      unsigned target_height = BITMAP_HEIGHT (*target),
               target_width = BITMAP_WIDTH (*target);
      
      /* Get the parameters we're passed.  */
      opaque = param_value (params[0], param1_values);
      flip = param_value (params[1], param2_values);

      /* Figure out where we are.  */
      paste.x = col / bw->bitmap.expansion;
      paste.y = row / bw->bitmap.expansion;

      /* Update the underlying bitmap.  Copy the black pixels from
         the original if we're pasting transparently.  */
      for (this_row = 0, target_row = paste.y;
           this_row < source_height && target_row < target_height; 
           this_row++, target_row++)
        {
          source_row = (flip == flip_horizontal || flip == flip_neither)
                       ? this_row 
                       : source_height - this_row - 1;

          for (this_col = 0, target_col = paste.x;
               this_col < source_width && target_col < target_width;
               this_col++, target_col++)
            {
              source_col = (flip == flip_vertical || flip == flip_neither)
                           ? this_col
                           : source_width - this_col - 1;
                           
              BITMAP_PIXEL (*target, target_row, target_col)
                = BITMAP_PIXEL (source, source_row, source_col)
                  | (opaque
                     ? WHITE
                     : BITMAP_PIXEL (*target, target_row, target_col));
            }
        }

      bw->bitmap.modified = true;

      /* If necessary, get the combined selection image back.  */
      if (!opaque || flip != flip_neither)
        {
          bounding_box_type select_bb = find_selection_bb (bw);
          source = extract_subbitmap (*target, select_bb);
        }

      redraw_selection (bw, source);

      if (!opaque)
        free_bitmap (&source);
    }
}



/* Convert the selection, which is in window coordinates, to a bounding
   box, in bitmap coordinates.  */

static bounding_box_type
find_selection_bb (BitmapWidget bw)
{
  bounding_box_type select_bb;
  unsigned expansion = bw->bitmap.expansion;
  
  MIN_COL (select_bb) = bw->bitmap.select_ul.x / expansion;
  MIN_ROW (select_bb) = bw->bitmap.select_ul.y / expansion;
  MAX_COL (select_bb)
    = MIN_COL (select_bb) + bw->bitmap.select_width / expansion;
  MAX_ROW (select_bb)
    = MIN_ROW (select_bb) + bw->bitmap.select_height / expansion;

  /* Because of the asymmetry of bounding boxes, we must
     subtract one from the row.  */
  MAX_ROW (select_bb)--;
  
  return select_bb;
}

          
/* Redraw the area in the bitmap widget BW given by the selection.  The
   new appearance of the area is given as a bitmap in SOURCE.  Also
   redraw the shadow bitmap, if it exists.  */

#define COMPUTE_SHADOW_COORD(field)					\
  shadow_bw->bitmap.field = shadow_bw->bitmap.expansion			\
                            * bw->bitmap.field / bw->bitmap.expansion;

static void
redraw_selection (BitmapWidget bw, bitmap_type source)
{
  BitmapWidget shadow_bw = (BitmapWidget) bw->bitmap.shadow;

  redraw_selection_bitmap (bw, source);

  if (shadow_bw != NULL)
    {
      COMPUTE_SHADOW_COORD (select_ul.x);
      COMPUTE_SHADOW_COORD (select_ul.y);
      COMPUTE_SHADOW_COORD (select_width);
      COMPUTE_SHADOW_COORD (select_height);
      redraw_selection_bitmap (shadow_bw, source);
    }
}

static void
redraw_selection_bitmap (BitmapWidget bw, bitmap_type source)
{
  Display *display = XtDisplay (bw);
  Pixmap selection_pixmap = bitmap_to_pixmap (display, source,
                                              bw->bitmap.expansion);

  /* Update the pixmap in our parent label.  */
  XCopyArea (XtDisplay (bw), selection_pixmap, bw->label.pixmap,
             bw->label.normal_GC, 0, 0, 
             bw->bitmap.select_width, bw->bitmap.select_height,
             bw->bitmap.select_ul.x, bw->bitmap.select_ul.y);
  XFreePixmap (display, selection_pixmap);

  /* Update the display.  */
  if (XtIsRealized ((Widget) bw))
    /* Server bug?  On my machine, this doesn't always update large pastes
       correctly; some parts of the window are left.  */
#if 0
    XCopyPlane (display, bw->label.pixmap, XtWindow (bw), 
                bw->label.normal_GC, 
                bw->bitmap.select_ul.x, bw->bitmap.select_ul.y,
                bw->bitmap.select_width, bw->bitmap.select_height,
                bw->bitmap.select_ul.x, bw->bitmap.select_ul.y, 1L);
#endif
    XClearArea (display, XtWindow (bw),
                bw->bitmap.select_ul.x, bw->bitmap.select_ul.y,
                bw->bitmap.select_width, bw->bitmap.select_height,
                True); /* Generate Expose event.  */
}


/* If we are realized, display the currently selected rectangle.  */

static void
show_selection (BitmapWidget bw)
{
  if (XtIsRealized ((Widget) bw))
    XDrawRectangle (XtDisplay (bw), XtWindow (bw), bw->bitmap.select_gc, 
                    bw->bitmap.select_ul.x, bw->bitmap.select_ul.y,
                    bw->bitmap.select_width, bw->bitmap.select_height);
}


/* If the user moves off the bitmap, we need to erase the last
   selection rectangle we drew.  */

static void
unshow_selection (BitmapWidget bw)
{
  if (selection_showing)
    {
      show_selection (bw);
      selection_showing = false;
    }
}


/* Do the appropriate maneuvers for the new row ROW and column COL for
   computing the upper left corner of the selection.  These numbers are
   in unrounded window coordinates, i.e., they may not lie on pixel
   boundaries. If we think of the initial selection point as the origin,
   the new point can be in any of the four quadrants.  */

#define initial_x (bw->bitmap.initial_select.x)
#define initial_y (bw->bitmap.initial_select.y)

static void
update_selection (BitmapWidget bw, unsigned row, unsigned col)
{
  if (col < initial_x)
    {
      bw->bitmap.select_ul.x = EXPANSION_TRUNC (col);
      bw->bitmap.select_width = EXPANSION_ROUND (initial_x - col);
    }
  else
    {
      bw->bitmap.select_ul.x = initial_x;
      bw->bitmap.select_width = EXPANSION_ROUND (col - initial_x);
    }

  if (row < initial_y)
    {
      bw->bitmap.select_ul.y = EXPANSION_TRUNC (row);
      bw->bitmap.select_height = EXPANSION_ROUND (initial_y - row);
    }
  else
    {
      bw->bitmap.select_ul.y = initial_y;
      bw->bitmap.select_height = EXPANSION_ROUND (row - initial_y);
    }
}
