/* commands.c: the operations we allow.  The routines here are called
   by the toolkit, as ``callbacks'', when certain events happen.

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 "font.h"
#include "gf.h"
#include "xmessage.h"

#include "char.h"
#include "commands.h"
#include "main.h"

/* The filename of the bitmap font we output.  (-output-file)  */
string output_name = NULL;

/* The names we construct for the output files.  */
static string gf_output_name, tfm_output_name;

/* If the `true' output_names are non-null, we are using a temporary
   output file, and should therefore do a copy when the user exits,
   because the input and output files are the same.  */
static string true_gf_output_name = NULL;
static string true_tfm_output_name = NULL;


static bitmap_type enlarge_bitmap (bitmap_type);

/* Exit the program.  We set the callback up to pass the font name as
   the CLIENT_DATA.  */

void
exit_command (Widget w, XtPointer client_data, XtPointer call_data)
{
  string font_name = client_data;
  
  close_font (font_name);

  if (true_gf_output_name != NULL)
    xrename (gf_output_name, true_gf_output_name);

  if (true_tfm_output_name != NULL)
    xrename (tfm_output_name, true_tfm_output_name);

  exit (0);
}

/* Move to the next character in the font.  CLIENT_DATA is a pointer to
   a `font_type' structure, and CALL_DATA is ignored.  */

void
next_command (Widget w, XtPointer client_data, XtPointer call_data)
{
  unsigned this_char;
  char_type *c = NULL;
  font_type *f = (font_type *) client_data;
    
  for (this_char = FONT_CURRENT_CHARCODE (*f) + 1;
       this_char <= MAX_CHARCODE && c == NULL;
       this_char++)
    c = read_char (*f, this_char);
  
  if (c == NULL)
    x_warning (XtParent (w), "You're at the last character");
  else
    show_char (XtParent (w), f, c);
}


/* Move to the previous character in the font.  The arguments are the
   same as for `next_command'.  */

void
prev_command (Widget w, XtPointer client_data, XtPointer call_data)
{
  int this_char;
  char_type *c = NULL;
  font_type *f = (font_type *) client_data;
    
  for (this_char = FONT_CURRENT_CHARCODE (*f) - 1;
       this_char >= 0 && c == NULL;
       this_char--)
    c = read_char (*f, this_char);
  
  if (c == NULL)
    x_warning (XtParent (w), "You're at the first character");
  else
    show_char (XtParent (w), f, c);
}


/* Go back to the image that is in the font file, killing all changes.
   Since we made a copy of the bitmap before making changes, we free the
   storage that the copy is occupying.  It would be nice if we could
   change the modified status of the font, but since we don't keep a
   record, we don't know whether this character is the only one that has
   changed.  */

void
revert_command (Widget w, XtPointer client_data, XtPointer call_data)
{
  char_type *c;
  font_type *f = (font_type *) client_data;
  charcode_type code = FONT_CURRENT_CHARCODE (*f);
  char_type *original = FONT_CHAR (*f, code);
  
  /* Set the pointer in the font structure to null, so `read_char' will
     have to reread the character.  */
  FONT_CHAR (*f, code) = NULL;
  
  /* Reread the character.  */
  c = read_char (*f, code);
  if (c == NULL)
    FATAL2 ("xbfe: Character %d has somehow been deleted from `%s'",
            FONT_CURRENT_CHARCODE (*f), FONT_NAME (*f));

  show_char (XtParent (w), f, c);

  /* Release the storage the current character is using.  We must do
     this after rereading the character from the font, so that the same
     memory is not used.  If that happens, `bitmap_set_values' thinks
     nothing has changed.  */
  free_bitmap (&BCHAR_BITMAP (*original));
  free (original);
}


/* This is invoked when the user hits return (well, when the
   `AcceptValue' action is executed) in the Item widget that allows
   changing the current character.  CLIENT_DATA is a pointer to a
   `font_type' structure.  CALL_DATA is the value string in the item
   widget which the user typed; we hope it parses as a character code.  */

void
char_change_callback (Widget w, XtPointer client_data, XtPointer call_data)
{
  charcode_type charcode; 
  string value_string = call_data;
  
  if (XTPARSE_CHARCODE (charcode, value_string, w))
    {
      font_type *f = (font_type *) client_data;
      char_type *c = read_char (*f, charcode);

      if (c == NULL)
        x_warning (XtParent (w), "No such character");
      else
        show_char (XtParent (w), f, c);
    }
}

/* Change the side bearing values.  */

/* We allow the user to give just simple decimal constants.  If we see
   something else, give a warning.  */
#define CHECK_DECIMAL(widget, str)					\
  if (!integer_ok (str))						\
    {									\
      string s = concat3 ("`", str, "': invalid integer");		\
      x_warning (XtParent (widget), s);					\
      free (s);								\
      return;								\
    }

/* We have to do some things the same when either of the side bearings
   changes.  We change the character C according to the side-bearing
   change of OFFSET pixels.  Why did I make this a macro?  */
#define COMMON_CHANGE(f, c, offset)					\
  {									\
    char_info_type *char_info = &CHAR_BITMAP_INFO (*c);			\
    real ds = BITMAP_FONT_DESIGN_SIZE (FONT_BITMAP_FONT (FONT_INFO (*f)));\
    real pt_width = fix_to_real (CHAR_TFM_WIDTH (*char_info) * ds);	\
    real pt_offset = PIXELS_TO_POINTS (offset, FONT_DPI (*f));		\
    real new_tfm_width = pt_width + pt_offset;				\
    TFM_WIDTH (CHAR_TFM_INFO (*c)) = new_tfm_width;			\
    CHAR_TFM_WIDTH (*char_info) = real_to_fix (new_tfm_width / ds);	\
    CHAR_SET_WIDTH (*char_info) += offset;				\
    FONT_MODIFIED (*f) = true;						\
  }

void
lsb_change_callback (Widget w, XtPointer client_data, XtPointer call_data)
{
  int new_lsb, old_lsb;
  string value_string = call_data;
  font_type *f = (font_type *) client_data;
  char_type *c = FONT_CURRENT_CHAR (*f);

  assert (c != NULL);
  
  CHECK_DECIMAL (w, value_string);

  old_lsb = CHAR_LSB (CHAR_BITMAP_INFO (*c));
  new_lsb = atoi (value_string);
  CHAR_MIN_COL (CHAR_BITMAP_INFO (*c)) = new_lsb;
  CHAR_MAX_COL (CHAR_BITMAP_INFO (*c)) += new_lsb - old_lsb;
  
  COMMON_CHANGE (f, c, new_lsb - old_lsb);
}


void
rsb_change_callback (Widget w, XtPointer client_data, XtPointer call_data)
{
  int new_rsb, old_rsb;
  string value_string = call_data;
  font_type *f = (font_type *) client_data;
  char_type *c = FONT_CURRENT_CHAR (*f);

  assert (c != NULL);
  
  CHECK_DECIMAL (w, value_string);
  
  old_rsb = CHAR_RSB (CHAR_BITMAP_INFO (*c));
  new_rsb = atoi (value_string);

  COMMON_CHANGE (f, c, new_rsb - old_rsb);
}

/* Expand the bitmap by one pixel on all sides.  */

void
expand_command (Widget w, XtPointer client_data, XtPointer call_data)
{
  font_type *f = (font_type *) client_data;
  /* We must use `*bitmap' here, since the editable Bitmap is a child of
     the Viewport.  Incidentally, R3's XtNameToWidget won't work here.  */
  Widget bw = XtNameToWidget (XtParent (w), "*bitmap");
  bitmap_type *b = BitmapBits (bw);
  char_type *c = XTALLOC1 (char_type);
  
  /* Copy the current character, so the `*_set_values' commands can
     notice something is changed.  */
  *c = *FONT_CURRENT_CHAR (*f);

  /* Do the enlargement.  */
  BCHAR_BITMAP (*c) = enlarge_bitmap (*b);
  
  /* We have to increase the bounding box for the character.  The best
     way to do this is not obvious.  Right now, we arrange things so
     that the origin of the character does not move, i.e., the new row
     at the bottom is below the baseline, and the new column at the left
     is into the side bearing.  Perhaps we should have four separate
     expand commands (or something along those lines).  */
  BCHAR_SET_WIDTH (*c) += 2;
  BCHAR_MIN_COL (*c)--;
  BCHAR_MAX_COL (*c)++;
  BCHAR_MIN_ROW (*c)--;
  BCHAR_MAX_ROW (*c)++;

  /* Display the new values.  */
  show_char (XtParent (w), f, c);

  /* Free afterwards; see revert_command.  */
  free_bitmap (b);
}


/* Expand the bitmap B by one pixel on all four sides.  We return a new
   bitmap.  */

static bitmap_type
enlarge_bitmap (bitmap_type b)
{
  bitmap_type new;
  dimensions_type new_size;
  unsigned row;
  
  DIMENSIONS_WIDTH (new_size) = BITMAP_WIDTH (b) + 2;
  DIMENSIONS_HEIGHT (new_size) = BITMAP_HEIGHT (b) + 2;
  
  new = new_bitmap (new_size);
  
  /* `new_bitmap' initializes all the pixels to white, so we don't have
     to do anything to the new rows and columns.  */
  for (row = 1; row < DIMENSIONS_HEIGHT (new_size) - 1; row++)
    {
      one_byte *source = BITMAP_ROW (b, row - 1);
      one_byte *target = BITMAP_ROW (new, row) + 1;
      
      memcpy (target, source, BITMAP_WIDTH (b));
    }
  
  return new;
}

/* Save the font (if it's been modified).  The CLIENT_DATA is a pointer
   to a `font_type' structure, as defined in `main.h'.  */

/* Write C, a `char_info_type', to the output file.  */
#define OUTPUT_CHAR(c)							\
  do									\
    {									\
      gf_char_type gf_char;						\
									\
      GF_CHARCODE (gf_char) = this_char;				\
      GF_BITMAP (gf_char) = BCHAR_BITMAP (c);				\
      GF_CHAR_BB (gf_char) = BCHAR_BB (c);				\
      GF_H_ESCAPEMENT (gf_char) = BCHAR_SET_WIDTH (c);			\
      GF_TFM_WIDTH (gf_char) = BCHAR_TFM_WIDTH (c);			\
      gf_put_char (gf_char);						\
      									\
      tfm_put_char (CHAR_TFM_INFO (c));					\
    }									\
  while (0)


/* We put this in the font comment to identify ourselves.  */
#define OUR_PREFIX "Hand edited"

void
save_command (Widget w, XtPointer client_data, XtPointer call_data)
{
  string font_comment, our_comment;
  string pl_output_name;
  unsigned this_char;
  font_type *f_ptr = (font_type *) client_data;
  font_type f = *f_ptr;
  bitmap_font_type bitmap_font = FONT_BITMAP_FONT (FONT_INFO (f));
  Widget bw = XtNameToWidget (XtParent (w), "*bitmap");

  if (!(FONT_MODIFIED (f) || BitmapModified (bw)))
    {
      x_warning (XtParent (w),
                 "You didn't change the font, so I won't write it out");
      return;
    }
  
  /* The output file is named the same thing as the input file (except
     we always output in GF format), unless the user has specified an
     output name.  */
  if (output_name == NULL)
    output_name = basename (FONT_NAME (f));

  gf_output_name = concat4 (output_name, ".", utoa ((unsigned) FONT_DPI (f)),
                            "gf");
  pl_output_name = concat (output_name, ".pl");
  tfm_output_name = concat (output_name, ".tfm");

  /* Since the user can go on editing after a save, however, we must
     make sure not to overwrite either of the input files.  If they do
     want to end up overwriting, we use a temporary file for writing.
     Then, when the user exits, we'll copy the temporary back to the
     real file.  */
  if (same_file_p (BITMAP_FONT_FILENAME (bitmap_font), gf_output_name))
    {
      string temp_dir = getenv ("TMPDIR") ? : "/tmp";
      true_gf_output_name = gf_output_name;
      gf_output_name = concat5 (temp_dir, "/", itoa (getpid ()),
                                basename (output_name), "gf"); 
    }
  if (same_file_p (FONT_TFM_FILENAME (FONT_INFO (f)), tfm_output_name))
    {
      string temp_dir = getenv ("TMPDIR") ? : "/tmp";
      true_tfm_output_name = tfm_output_name;
      tfm_output_name = concat4 (temp_dir, "/", itoa (getpid ()), ".tfm");
      pl_output_name = concat4 (temp_dir, "/", itoa (getpid ()), ".pl");
    }

  /* Open the output files we will write to.  */
  if (!gf_open_output_file (gf_output_name))
    FATAL_PERROR (gf_output_name);
  if (!tfm_open_pl_output_file (pl_output_name))
    FATAL_PERROR (pl_output_name);
    
  /* Don't keep tacking on our prefix to the beginning of the comment;
     instead, replace it if it's already there.  Also, don't bother to
     put the day of the week in the comment.  */
  our_comment = concat3 (OUR_PREFIX " ", now () + 4, " from ");
  font_comment = FONT_COMMENT (FONT_INFO (f));
  if (strncmp (font_comment, OUR_PREFIX, strlen (OUR_PREFIX)) == 0)
    font_comment += strlen (our_comment);

  /* Output the stuff at the beginning.  */
  gf_put_preamble (concat (our_comment, font_comment));
  tfm_put_global_info (FONT_TFM_FONT (FONT_INFO (f)));

  for (this_char = 0; this_char <= MAX_CHARCODE; this_char++)
    {
      char_type *c = FONT_CHAR (f, this_char);
      
      /* First see if we've read this character, i.e., if the user has
         edited (well, at least selected) it.  */
      if (c != NULL)
        OUTPUT_CHAR (*c);
      else
        { 
          tfm_char_type *tfm_char;
          
          /* The character hasn't been seen, so now we need to see if
             it's in the font.  If it is, though, there is no point in
             doing all the work of decoding and then re-encoding the
             bitmap, so we just read the raw bytes.  */
          raw_char_type *c = get_raw_char (FONT_NAME (f), this_char);
          if (c != NULL && RAW_CHAR_BITMAP_FORMAT (*c) == gf_format)
            {
              /* We found it in the font, although the user hasn't seen
                 it yet.  We just write out the same bytes that were
                 already present in the font file.  */
              gf_put_raw_char (*c);
              free_raw_char (c);
              
              /* Also output the TFM character, just as we read it.  */
              tfm_char = tfm_get_char (this_char);
              if (tfm_char != NULL)
                tfm_put_char (*tfm_char);
            }
          else
            { /* It's not available as a raw character in the right
                 format.  We still have to test if it's available at all.  */
              char_type *c = read_char (f, this_char);
              if (c != NULL)
                {
                  OUTPUT_CHAR (*c);
                  
                  /* We might as well save the character away, to avoid
                     reconstructing the bitmap if they either visit the
                     character or just save again.  */
                  FONT_CHAR (f, this_char) = c;
                }
            }
        }
    }

  gf_put_postamble (real_to_fix (FONT_DESIGN_SIZE (FONT_INFO (f))),
                    FONT_DPI (f), FONT_DPI (f));

  /* Make the TFM file from the PL file.  */
  tfm_convert_pl (NULL, false);
  
  /* Close the output files, since we will write them again from scratch
     next time.  */
  gf_close_output_file ();
  tfm_close_pl_output_file ();
}
