/* The GIMP -- an image manipulation program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * 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 of the License, 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

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

#include <gtk/gtk.h>

#include <libgimp/gimp.h>
#include <libgimp/gimpui.h>

#define GETTEXT_PACKAGE "gimp20"

#include <libintl.h>

#define _(String) gettext (String)

#ifdef gettext_noop
#    define N_(String) gettext_noop (String)
#else
#    define N_(String) (String)
#endif

#ifndef HAVE_BIND_TEXTDOMAIN_CODESET
#    define bind_textdomain_codeset(Domain, Codeset) (Domain)
#endif

#define INIT_I18N()     G_STMT_START{                                \
  bindtextdomain (GETTEXT_PACKAGE"-std-plug-ins",                    \
                  gimp_locale_directory ());                         \
  bind_textdomain_codeset (GETTEXT_PACKAGE"-std-plug-ins", "UTF-8"); \
  textdomain (GETTEXT_PACKAGE"-std-plug-ins");                       \
}G_STMT_END

#define PREVIEW_MASK  (GDK_EXPOSURE_MASK       | \
                       GDK_POINTER_MOTION_MASK | \
                       GDK_BUTTON_PRESS_MASK   | \
                       GDK_BUTTON_RELEASE_MASK | \
                       GDK_BUTTON_MOTION_MASK  | \
                       GDK_KEY_PRESS_MASK      | \
                       GDK_KEY_RELEASE_MASK)

#define PIXBUF_WIDTH 24

static const gchar instruction_text[] = "How to use the Gimp Layer Alignment Plug-in\n\nThis plug-in permits you to align and arrange layers of a Gimp image.  It is probably most useful for arranging text items, but will sometimes be useful for other types of layers as well.  Here are some brief instructions for using it.  (These instructions will eventually migrate into the Help system.)\n\nTo use the plug-in, you begin by selecting two layers from the image, by clicking on them in the plug-in's preview image, where the borders of each layer are shown as a thin dotted line.  The \"Control\" layer defines the reference for alignment.  It will not be moved.  The \"Target\" layer is the one that will be moved into alignment with the Control layer.\n\n To set the Target layer, left-click inside the layer you want to move, in the preview.  This will cause the outline of the layer to turn thick and solid, and a target symbol to appear at the point where you clicked.\n\nTo set the Control layer, press the control key on the keyboard and left-click inside the layer in the preview.  This will cause the outline of the layer to turn into a thick dotted line, and a target symbol to appear at the point where you clicked.  It is perfectly okay to click on the background layer of the image.\n\nOnly visible layers of the image are used.  If layers are stacked one on top of another under the mouse, the topmost one will be selected. To use a different one, you will either have to toggle visibility or change the layer order.  If you need to do one of these things, you should cancel the plug-in and then start it again when the image is ready.\n\nYou use the option buttons to determine which part of the Target layer gets aligned with which part of the Control layer.  There are four options for each:  for horizontal alignment, they are the left edge, the center, the right edge, and the specific point that you click on when you set the layer.  (A target symbol appears at this point.)  In addition, you can set an offset:  this will cause the designated features to be arranged at that distance from each other, i.e., an offset of zero corresponds to perfect alignment.\n\nWhen you have things set up, click the \"Align\" button, and the target layer will be moved into the specified alignment.\n\nIf you have followed this far, it will be easier to get familiar with the plug-in by experimenting with it than by trying to read more detailed descriptions.  It is actually pretty simple to use once you get the hang of it.\n\nIf something goes wrong and you aren't sure how to fix it, you should be able to press the \"Okay\" button and then undo the bad thing in Gimp.  Each layer movement you cause with this plug-in is individually undoable.";

/* XPM */
static const gchar * center_align_xpm[] = {
"24 24 17 1",
"       c None",
".      c #86090D",
"+      c #B20905",
"@      c #F90007",
"#      c #6D6C6A",
"$      c #797A77",
"%      c #AF787A",
"&      c #8C8A88",
"*      c #B2A6A6",
"=      c #AFB1AE",
"-      c #B4AFAD",
";      c #C6C3C1",
">      c #F9AEAE",
",      c #D6D6D3",
"'      c #E2E3E0",
")      c #F5EFEE",
"!      c #FDFFFC",
"!!!!!!!!!!!>@)!!!!!!!!!!",
"!!!!!!!!!!!>@)!!!!!!!!!!",
"!!!!!!!!!!!>@)!!!!!!!!!!",
"!!!!!!!!!!!>@)!!!!!!!!!!",
"!!!!!',,'!!>@;,!!)'!!!!!",
"!!!!)&#$&;,%.##;;;#,!!!!",
"!!!!'&=====%+*====#,!!!!",
"!!!!)&*==-=%+*-==-&'!!!!",
"!!!!!,=--==%+*=-==;)!!!!",
"!!!!!,-==--%+*-=-=;)!!!!",
"!!!!!,-=-=-%+*-===;)!!!!",
"!!!!'&=-;-=%+-=--=#,!!!!",
"!!!!'&*==;-%+*--==#,!!!!",
"!!!!)&==-=-%+*==--$,!!!!",
"!!!!)==*==-%+*-===-)!!!!",
"!!!!!,-;--;%+*=--=;)!!!!",
"!!!!!,===-=%+*====;!!!!!",
"!!!!)===-=-%+*---=&'!!!!",
"!!!!'$$-,,;%.&,,;&#;!!!!",
"!!!!!)''!))%@')!),,!!!!!",
"!!!!!!!!!!!>@)!!!!!!!!!!",
"!!!!!!!!!!!>@)!!!!!!!!!!",
"!!!!!!!!!!!>@)!!!!!!!!!!",
"!!!!!!!!!!!>@)!!!!!!!!!!"};

/* XPM */
static const gchar * left_align_xpm[] = {
"24 24 17 1",
"       c None",
".      c #A32B2A",
"+      c #FE0B15",
"@      c #8F423D",
"#      c #6A6564",
"$      c #797B78",
"%      c #D55E5B",
"&      c #8E908D",
"*      c #B9AAA5",
"=      c #BFA9A6",
"-      c #B0B1AE",
";      c #C3C0BE",
">      c #F9B5B4",
",      c #DADDD9",
"'      c #FBD5D1",
")      c #F0F2EF",
"!      c #FCFEFB",
"!!!!'+>!!!!!!!!!!!!!!!!!",
"!!!!'+>!!!!!!!!!!!!!!!!!",
"!!!!'+>!!!!!!!!!!!!!!!!!",
"!!!!'+>!!!!!!!!!!!!!!!!!",
"!!!!'+>,,!!),,,)!!))!!!!",
"!!!!'.#$&,;;&$$;,;&;!!!!",
"!!!!'@&-----------$;!!!!",
"!!!!'@------------&;!!!!",
"!!!!'%-*-*;--*-----)!!!!",
"!!!!'%*---------*-;)!!!!",
"!!!!'%-------------)!!!!",
"!!!!'@-;-;--;-----&;!!!!",
"!!!!'@&*-----**---$;!!!!",
"!!!!'@------------$-!!!!",
"!!!!'%&*;*=-----*--,!!!!",
"!!!!'%----;-;-;----)!!!!",
"!!!!'%------;-----;)!!!!",
"!!!!'%=;---=--*;;-&,!!!!",
"!!!!'@#&,*;#$&;;;&#-!!!!",
"!!!)'+>,!!),;,)!!,,)!!!!",
"!!!!'+>!!!!!!!!!!!!!!!!!",
"!!!!'+>!!!!!!!!!!!!!!!!!",
"!!!!'+>!!!!!!!!!!!!!!!!!",
"!!!!'+>!!!!!!!!!!!!!!!!!"};

/* XPM */
static const gchar *large_target_xpm[] = {
"24 24 2 1",
". c #000000",
"# c #ffffff",
"########################",
"########################",
"#########.......########",
"#######...........######",
"#####...............####",
"####........#........###",
"###......#######......##",
"###....###########....##",
"##....#############....#",
"##...######....#####...#",
"#...#####.......#####...",
"#...####...###...####...",
"#...####..#####..####...",
"#...####...###...####...",
"#...#####.......#####...",
"#....#####....######...#",
"##....#############....#",
"##.....###########....##",
"###......#######......##",
"####........#........###",
"#####...............####",
"#######...........######",
"#########.......########",
"########################"
};

/* XPM */
static const gchar *small_target_xpm[] = {
"    15    15        2            1",
"# c #000000",
". c #ffffff",
"###############",
"######...######",
"####.......####",
"###...###...###",
"##..#######..##",
"#..####.####..#",
"..####.#.####..",
"..###.###.###..",
"..####.#.####..",
"#..####.####..#",
"##..#######..##",
"###...###...###",
"####.......####",
"######...######",
"###############"
};

enum
  {
    ALIGN_LEFT = 0,
    ALIGN_CENTER,
    ALIGN_RIGHT,
    ALIGN_TOP,
    ALIGN_MIDDLE,
    ALIGN_BOTTOM,
    ALIGN_POINT_HORZ,
    ALIGN_POINT_VERT,
    CTRL_ALIGN_LEFT,
    CTRL_ALIGN_CENTER,
    CTRL_ALIGN_RIGHT,
    CTRL_ALIGN_TOP,
    CTRL_ALIGN_MIDDLE,
    CTRL_ALIGN_BOTTOM,
    CTRL_ALIGN_POINT_HORZ,
    CTRL_ALIGN_POINT_VERT,
  };

typedef struct 
{
  gint     width;
  gint     height;
  gint     xoffset;
  gint     yoffset;
  gint     visible;
  gboolean is_control;
  gboolean selected;
} LayerInfo;

typedef struct
{
  gint       i_selected;
  gint       i_control;
  gint       action_horz;
  gint       action_vert;
  gint       action_ctrl_horz;
  gint       action_ctrl_vert;
  gdouble    horz_offset;
  gdouble    vert_offset;
  gint       ctrl_x0;
  gint       ctrl_y0;
  gint       select_x0;
  gint       select_y0;
} AlignValues;

static GtkWidget *preview;
static gint       preview_height;
static gint       preview_width;
static gint       image_width;
static gint       image_height;
static gint       image_ID;
static gint      *layer_list;
static gint       num_layers;
static LayerInfo *layer_info;
static GdkGC     *main_gc;
static GtkObject *horz_offset_adjustment;
static GtkObject *vert_offset_adjustment;
static GdkPixbuf *small_target_pixbuf;
static GdkPixbuf *thumb_pixbuf = NULL;

/* Declare local functions.
 */
static void      query                 (void);
static void      run                   (const gchar      *name,
                                        gint              nparams,
                                        const GimpParam  *param,
                                        gint             *nreturn_vals,
                                        GimpParam       **return_vals);

static gint      align_dialog          (void);
static gboolean  preview_expose        (GtkWidget        *widget,
                                        GdkEvent         *event);
static void      preview_realize       (GtkWidget        *widget);
static gboolean  preview_events        (GtkWidget        *widget,
                                        GdkEvent         *event);
static GtkWidget *make_preview         (void);
static void      draw_thumbnail        (void);
static void      draw_layers           (void);
static void      read_layer_info       (void);
static void      set_action            (GtkWidget        *widget,
                                        gpointer          data);
static GtkWidget *but_with_pix         (GdkPixbuf        *pixbuf,
                                        GSList          **group,
                                        gint              baction);
static GtkWidget *draw_buttons         (void);
static GdkPixbuf *pixbuf_rotate        (GdkPixbuf        *pixbuf,
                                        gint              width,
                                        gint              angle);
static void       select_layer         (gint              x,
                                        gint              y);
static void       select_control_layer (gint              x,
                                        gint              y);
static void       align_horz           (GtkWidget        *widget,
                                        gpointer          data);
static void       align_vert           (GtkWidget        *widget,
                                        gpointer          data);
static void       show_instructions    (GtkWidget        *widget,
                                        gpointer          data);

GimpPlugInInfo PLUG_IN_INFO =
{
  NULL,  /* init_proc  */
  NULL,  /* quit_proc  */
  query, /* query_proc */
  run,   /* run_proc   */
};

static AlignValues alignvals =
{
  0,               /* i_selected */
  0,               /* i_control  */
  ALIGN_LEFT,      /* action horz */
  ALIGN_TOP,       /* action vert */
  CTRL_ALIGN_LEFT, /* action ctrl horz */
  CTRL_ALIGN_TOP,  /* action ctrl vert */
  0,               /* horz offset */
  0,               /* vert offset */
  0,               /* ctrl x0 */
  0,               /* ctrl y0 */
  0,               /* select x0 */
  0                /* select y0 */
};


MAIN ()

static void
query (void)
{
  static GimpParamDef args[] =
  {
    { GIMP_PDB_INT32, "run_mode", "Interactive, non-interactive" },
    { GIMP_PDB_IMAGE, "image", "Input image" },
    { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" },
  };

  gimp_install_procedure ("plug_in_align",
                          "Align positions of layers",
                          "Allows the user to interactively select a pair of layers"
                          "and align or arrange them in a variety of ways.",
                          "William Skaggs",
                          "William Skaggs",
                          "1995-2004",
                          N_("Align Layers..."),
                          "RGB*, GRAY*, INDEXED*",
                          GIMP_PLUGIN,
                          G_N_ELEMENTS (args), 0,
                          args, NULL);

  gimp_plugin_menu_register ("plug_in_align",
                             N_("<Image>/Layer"));
}

static void
run (const gchar      *name,
     gint              nparams,
     const GimpParam  *param,
     gint             *nreturn_vals,
     GimpParam       **return_vals)
{
  static GimpParam   values[1];
  GimpDrawable      *drawable;
  GimpRunMode        run_mode;
  GimpPDBStatusType  status = GIMP_PDB_SUCCESS;

  run_mode = param[0].data.d_int32;

  INIT_I18N ();

  *nreturn_vals = 1;
  *return_vals  = values;

  values[0].type          = GIMP_PDB_STATUS;
  values[0].data.d_status = status;

  /*  Get the specified image and drawable  */
  image_ID = param[1].data.d_image;
  drawable = gimp_drawable_get (param[2].data.d_drawable);

  if (strcmp (name, "plug_in_align") == 0)
    {
      switch (run_mode)
        {
        case GIMP_RUN_INTERACTIVE:
          /*  Possibly retrieve data  */
          gimp_get_data ("plug_in_align", &alignvals);
          
          /*  First acquire information with a dialog  */
          if (! align_dialog ())
            return;
          break;
        case GIMP_RUN_NONINTERACTIVE:
          status = GIMP_PDB_CALLING_ERROR;
          break;
          
        case GIMP_RUN_WITH_LAST_VALS:
          status = GIMP_PDB_CALLING_ERROR;
          break;
          
        default:
          break;
        }
    }
  else
    status = GIMP_PDB_CALLING_ERROR;

  if (status == GIMP_PDB_SUCCESS)
    {
      gimp_set_data ("plug_in_align", &alignvals, sizeof (AlignValues));
      gimp_displays_flush ();
    }

  values[0].data.d_status = status;
}

#define PREVIEW_SIZE     400

static gint
align_dialog ()
{
  GtkWidget *dlg;
  gint       response;
  gint       pwidth;
  gint       pheight;
  GtkWidget *main_vbox;
  GtkWidget *hbox;
  GdkColor   fg, bg;
  gint8      dash_list[2];

  gimp_ui_init ("align", FALSE);

  image_width  = gimp_image_width (image_ID);
  image_height = gimp_image_height (image_ID);
  layer_list   = NULL;
  layer_info   = NULL;
  read_layer_info ();

  if (image_width > image_height)
    {
      pwidth  = MIN (image_width, PREVIEW_SIZE);
      pheight = image_height * pwidth / image_width;
    }
  else
    {
      pheight = MIN (image_height, PREVIEW_SIZE);
      pwidth  = image_width * pheight / image_height;
    }

  preview_width       = MAX (pwidth, 2);  /* Min size is 2 */
  preview_height      = MAX (pheight, 2);
  small_target_pixbuf = gdk_pixbuf_new_from_xpm_data (small_target_xpm);
  /*
   * create the main dialog window
   */
  dlg = gimp_dialog_new (_("Align Layers"), "align",
                         NULL, 0,
                         gimp_standard_help_func, "plug-in-align",

                         GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
                         GTK_STOCK_OK,     GTK_RESPONSE_OK,

                         NULL);

  main_vbox = gtk_vbox_new (FALSE, 4);
  gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 4);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->vbox), main_vbox,
                      TRUE, TRUE, 0);
  gtk_widget_show (main_vbox);

  gtk_box_pack_start (GTK_BOX (main_vbox), make_preview (), FALSE, FALSE, 0);

  hbox = gtk_hbox_new (FALSE, 0);
  gtk_box_pack_start (GTK_BOX (main_vbox), hbox, FALSE, FALSE, 0);
  gtk_widget_show (hbox);

  gtk_box_pack_start (GTK_BOX (hbox), draw_buttons (), FALSE, FALSE, 0);
  
  gtk_widget_show (dlg);

  /* 
   * create the graphics context for the preview area
   */
  main_gc = gdk_gc_new (preview->window);
  gdk_gc_set_function (main_gc, GDK_INVERT);
  fg.pixel = 0xFFFFFFFF;
  bg.pixel = 0x00000000;
  gdk_gc_set_foreground (main_gc, &fg);
  gdk_gc_set_background (main_gc, &bg);
  gdk_gc_set_line_attributes (main_gc, 1, GDK_LINE_SOLID, 
                              GDK_CAP_BUTT, GDK_JOIN_MITER);
  dash_list[0] = 2;
  dash_list[1] = 2;
  gdk_gc_set_dashes (main_gc, 0, dash_list, 2);

  response = (gimp_dialog_run (GIMP_DIALOG (dlg)) == GTK_RESPONSE_OK);

  if (response)
    {
      /* do something */
    }

  gtk_widget_destroy (dlg);

  return response;
}


static GtkWidget *
make_preview ()
{
  GtkWidget *xframe;
  GtkWidget *vbox;
  GtkWidget *hbox;
  GtkWidget *table;
  GtkWidget *ruler;
  GtkWidget *label;

  vbox = gtk_vbox_new (FALSE, 0);
  gtk_container_set_border_width (GTK_CONTAINER (vbox), 2);

  hbox = gtk_hbox_new (FALSE, 0);
  gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
  gtk_widget_show (hbox);

  xframe = gtk_frame_new (NULL);
  gtk_frame_set_shadow_type (GTK_FRAME (xframe), GTK_SHADOW_IN);
  gtk_box_pack_start (GTK_BOX (hbox), xframe, FALSE, FALSE, 0);
  gtk_widget_show (xframe);

  table = gtk_table_new (3, 3, FALSE);
  gtk_container_add (GTK_CONTAINER (xframe), table);
  gtk_widget_show (table);

  preview = gtk_drawing_area_new ();
  gtk_widget_set_events (GTK_WIDGET (preview), PREVIEW_MASK);
  g_signal_connect (preview , "realize",
                    G_CALLBACK (preview_realize),
                    NULL);
  g_signal_connect (preview , "event",
                    G_CALLBACK (preview_events),
                    NULL);
  g_signal_connect_after (preview , "expose_event",
                          G_CALLBACK (preview_expose),
                          NULL);
  gtk_drawing_area_size (GTK_DRAWING_AREA (preview), 
                         preview_width, preview_height);
  gtk_table_attach (GTK_TABLE (table), preview, 1, 2, 1, 2,
                    GTK_FILL , GTK_FILL , 0, 0);
  gtk_widget_show (preview);

  ruler = gtk_hruler_new ();
  gtk_ruler_set_range (GTK_RULER (ruler), 0, preview_width, 0, PREVIEW_SIZE);
  g_signal_connect_swapped (preview, "motion_notify_event",
                            G_CALLBACK (GTK_WIDGET_CLASS (G_OBJECT_GET_CLASS (ruler))
                                        ->motion_notify_event),
                            ruler);
  gtk_table_attach (GTK_TABLE (table), ruler, 1, 2, 0, 1,
                    GTK_FILL, GTK_FILL, 0, 0);
  gtk_widget_show (ruler);


  ruler = gtk_vruler_new ();
  gtk_ruler_set_range (GTK_RULER (ruler), 0, preview_height, 0, PREVIEW_SIZE);
  g_signal_connect_swapped (preview, "motion_notify_event",
                            G_CALLBACK (GTK_WIDGET_CLASS (G_OBJECT_GET_CLASS (ruler))
                                        ->motion_notify_event),
                            ruler);
  gtk_table_attach (GTK_TABLE (table), ruler, 0, 1, 1, 2,
                    GTK_FILL, GTK_FILL, 0, 0);
  gtk_widget_show (ruler);

  label = gtk_label_new ("Click to select the layer to move.\nCtrl-click to select the fixed layer to align it with.");
  gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
  gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
  gtk_widget_show (label);

  gtk_widget_show (vbox);

  return vbox;
}

static void
preview_realize (GtkWidget *widget)
{
  GdkDisplay *display = gtk_widget_get_display (widget);

  gdk_window_set_cursor (preview->window,
                         gdk_cursor_new_for_display (display, GDK_CROSSHAIR));
}

static gboolean
preview_expose (GtkWidget *widget,
                GdkEvent  *event)
{
  draw_thumbnail ();
  draw_layers ();

  return FALSE;
}

static void
select_layer (gint     x,
              gint     y)
{
  gint       k;
  LayerInfo *info;
  gint       x0, y0, x1, y1;
  gdouble    scale = preview_width / (gdouble) image_width;

  for (k = 0; k < num_layers; k++)
    layer_info[k].selected = FALSE;

  for (k = 0; k < num_layers; k++)
    {
      info = &layer_info[k];
      x0 = scale * info->xoffset;
      y0 = scale * info->yoffset;
      x1 = x0 + scale * info->width;
      y1 = y0 + scale * info->height;

      if (x0 <= x && y0 <= y && x <= x1 && y < y1)
        {
          info->selected = TRUE;
          alignvals.i_selected = k;
          alignvals.select_x0 = x / scale - info->xoffset;
          alignvals.select_y0 = y / scale - info->yoffset;
          break;
        }
    }
}

static void
select_control_layer (gint     x,
                      gint     y)
{
  gint       k;
  LayerInfo *info;
  gint       x0, y0, x1, y1;
  gdouble    scale = preview_width / (gdouble) image_width;

  for (k = 0; k < num_layers; k++)
    layer_info[k].is_control = FALSE;

  for (k = 0; k < num_layers; k++)
    {
      info = &layer_info[k];
      x0 = scale * info->xoffset;
      y0 = scale * info->yoffset;
      x1 = x0 + scale * info->width;
      y1 = y0 + scale * info->height;

      if (x0 <= x && y0 <= y && x <= x1 && y < y1)
        {
          info->is_control = TRUE;
          alignvals.i_control = k;
          alignvals.ctrl_x0 = x / scale;
          alignvals.ctrl_y0 = y / scale;
          break;
        }
    }
}

static gint
preview_events (GtkWidget *widget,
                GdkEvent  *event)
{
  GdkEventButton *bevent;
  GdkEventMotion *mevent;

  switch (event->type)
    {
    case GDK_EXPOSE:
      break;
    case GDK_BUTTON_PRESS:
      bevent = (GdkEventButton *) event;
      if (bevent->button == 1)
        {
          if (bevent->state & GDK_CONTROL_MASK)
            select_control_layer (bevent->x, bevent->y);
          else
            select_layer (bevent->x, bevent->y);
        }
      break;
    case GDK_BUTTON_RELEASE:
      preview_expose (preview, NULL);
      break;
    case GDK_MOTION_NOTIFY:
      mevent = (GdkEventMotion *) event;
      break;
    case GDK_KEY_PRESS:
      break;
    case GDK_KEY_RELEASE:
      break;
    default:
      break;
    }

  return FALSE;
}

static void
draw_thumbnail ()
{
  if (! thumb_pixbuf)
    thumb_pixbuf = gimp_image_get_thumbnail (image_ID,
                                            preview_width, preview_height,
                                            GIMP_PIXBUF_LARGE_CHECKS);

  if (!thumb_pixbuf)
    fprintf (stderr, "Could not get thumbnail pixbuf.\n");

  if (thumb_pixbuf)
    gdk_draw_pixbuf (preview->window,
                     preview->style->fg_gc[GTK_STATE_NORMAL],
                     thumb_pixbuf, 0, 0,
                     0, 0,
                     gdk_pixbuf_get_width (thumb_pixbuf),
                     gdk_pixbuf_get_height (thumb_pixbuf),
                     GDK_RGB_DITHER_NONE, 0, 0);
}

static void
draw_layers ()
{
  gint       x0;
  gint       y0;
  gint       w;
  gint       h;
  gint       k;
  gdouble    scale = preview_width / (gdouble)image_width;
  LayerInfo *info;

  gdk_gc_set_function (main_gc, GDK_INVERT);

  for (k = 0; k < num_layers; k++)
    {
      info = &layer_info[k];
      
      if (info->visible)
        {
          if (info->selected)
            gdk_gc_set_line_attributes (main_gc, 4, GDK_LINE_SOLID, 
                                        GDK_CAP_BUTT, GDK_JOIN_MITER);
          else if  (info->is_control)
            gdk_gc_set_line_attributes (main_gc, 4, GDK_LINE_ON_OFF_DASH, 
                                        GDK_CAP_BUTT, GDK_JOIN_ROUND);
          else
            gdk_gc_set_line_attributes (main_gc, 1, GDK_LINE_ON_OFF_DASH, 
                                        GDK_CAP_BUTT, GDK_JOIN_MITER);
          
          x0 = scale * info->xoffset;
          y0 = scale * info->yoffset;
          w  = scale * info->width;
          h  = scale * info->height;
          
          gdk_draw_rectangle (preview->window, main_gc, 
                              FALSE, x0, y0, w, h);

          if (info->selected)
            {
              w = gdk_pixbuf_get_width  (small_target_pixbuf);
              h = gdk_pixbuf_get_height (small_target_pixbuf);
              x0 = scale * (info->xoffset + alignvals.select_x0) - w / 2;
              y0 = scale * (info->yoffset + alignvals.select_y0) - h / 2;
/*            gdk_draw_pixbuf (preview->window, NULL,  */
/*                             small_target_pixbuf, */
/*                             0, 0, x0, y0, -1, -1,  */
/*                             GDK_RGB_DITHER_NONE, 0, 0);  */
            }

          if (info->is_control)
            {
              w = gdk_pixbuf_get_width  (small_target_pixbuf);
              h = gdk_pixbuf_get_height (small_target_pixbuf);
              x0 = scale * alignvals.ctrl_x0 - w / 2;
              y0 = scale * alignvals.ctrl_y0 - h / 2;
/*            gdk_draw_pixbuf (preview->window, NULL, */
/*                             small_target_pixbuf, */
/*                             0, 0, x0, y0, -1, -1,  */
/*                             GDK_RGB_DITHER_NONE, 0, 0);  */
            }


        }
    }
}

static void
read_layer_info ()
{
  gint k;

  if (layer_list)
    g_free (layer_list);

  if (layer_info)
    g_free (layer_info);

  layer_list = gimp_image_get_layers (image_ID, &num_layers);
  layer_info = (LayerInfo*)g_malloc (num_layers * sizeof (LayerInfo));
  for (k = 0; k < num_layers; k++)
    {
      layer_info[k].width = gimp_drawable_width (layer_list[k]);
      layer_info[k].height = gimp_drawable_height (layer_list[k]);
      gimp_drawable_offsets (layer_list[k],
                             &layer_info[k].xoffset,
                             &layer_info[k].yoffset);
      layer_info[k].visible = gimp_drawable_get_visible (layer_list[k]);

      layer_info[k].selected = FALSE;
      layer_info[k].is_control = FALSE;
    }
}

static GdkPixbuf *
pixbuf_rotate (GdkPixbuf  *pixbuf,
               gint        width,
               gint        angle)
{
  GdkPixbuf *pixbuf1;
  guchar    *pixels1;
  guchar    *pixels0;
  gint       i, j, k;
  gint       bpp      = 4;

  pixbuf1 = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, width, width);
  pixels0 = gdk_pixbuf_get_pixels (pixbuf);
  pixels1 = gdk_pixbuf_get_pixels (pixbuf1);

  for (j = 0; j < width; j++)
    for (k = 0; k < width; k++)
      for (i = 0; i < bpp; i++)
        switch (angle)
          {
          case   0:
          case 360:
            pixels1[width*bpp*j + bpp*k + i] 
              = pixels0[width*bpp*j + bpp*k + i];
            break;
          case   90:
          case -270:
            pixels1[width*bpp*k + bpp*(width - j - 1) + i] 
              = pixels0[width*bpp*j + bpp*k + i];
            break;
          case  180:
          case -180:
            pixels1[width*bpp*(width - j - 1) + bpp*(width - k - 1) + i] 
              = pixels0[width*bpp*j + bpp*k + i];
            break;
          case  -90:
          case  270:
            pixels1[width*bpp*(width - k - 1) + bpp*j + i] 
              = pixels0[width*bpp*j + bpp*k + i];
            break;
          default:
            g_message("Attempt to rotate by invalid angle (%d)", angle);
            return NULL;
          }

  return pixbuf1;
}
 


static GtkWidget *
draw_buttons ()
{
  GtkWidget *button;
  GtkWidget *vbox;
  GSList    *horz_group = NULL;
  GSList    *vert_group = NULL;
  GSList    *ctrl_horz_group = NULL;
  GSList    *ctrl_vert_group = NULL;
  GtkWidget *table;
  GtkWidget *frame;
  gint       row;
  gint       col;
  GdkPixbuf *pixbuf_left, *pixbuf_top, *pixbuf_right, *pixbuf_bottom;
  GdkPixbuf *pixbuf_center, *pixbuf_middle, *pixbuf_target;
  GtkWidget *label;
  GtkWidget *spinbutton;

  pixbuf_left   = gdk_pixbuf_new_from_xpm_data (left_align_xpm);
  pixbuf_center = gdk_pixbuf_new_from_xpm_data (center_align_xpm);
  pixbuf_target = gdk_pixbuf_new_from_xpm_data (large_target_xpm);
  pixbuf_top    = pixbuf_rotate (pixbuf_left,   PIXBUF_WIDTH,  90);
  pixbuf_right  = pixbuf_rotate (pixbuf_left,   PIXBUF_WIDTH, 180);
  pixbuf_bottom = pixbuf_rotate (pixbuf_left,   PIXBUF_WIDTH, -90);
  pixbuf_middle = pixbuf_rotate (pixbuf_center, PIXBUF_WIDTH,  90);

  /* Create group */
  vbox = gtk_vbox_new (FALSE, 0);

  frame = gimp_frame_new ("Options");
  gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
  gtk_container_set_border_width (GTK_CONTAINER (frame), 12);
  gtk_widget_show (frame);

  table = gtk_table_new (10, 12, FALSE);
  gtk_container_add (GTK_CONTAINER (frame), table);
  gtk_widget_show (table);

  row = col = 0;

  gtk_table_set_row_spacing (GTK_TABLE (table), row, 10);

  label = gtk_label_new ("Horizontal");
  gtk_table_attach_defaults (GTK_TABLE (table), label, 2, 6, row, row+1);
  gtk_widget_show (label);

  label = gtk_label_new ("Vertical");
  gtk_table_attach_defaults (GTK_TABLE (table), label, 7, 11, row, row+1);
  gtk_widget_show (label);

  row++;

  button = gtk_button_new_with_label ("Align");
  gtk_table_attach_defaults (GTK_TABLE (table), button, 2, 6, row, row+1);
  g_signal_connect (button, "clicked",
                    G_CALLBACK (align_horz), 
                    NULL);
  gtk_widget_show (button);

  button = gtk_button_new_with_label ("Align");
  gtk_table_attach_defaults (GTK_TABLE (table), button, 7, 11, row, row+1);
  g_signal_connect (button, "clicked",
                    G_CALLBACK (align_vert), 
                    NULL);
  gtk_widget_show (button);

  gtk_table_set_row_spacing (GTK_TABLE (table), row, 10);
  ++row;

  ++col;
  ++col;

  button = but_with_pix (pixbuf_left, &horz_group, ALIGN_LEFT);
  gtk_table_attach_defaults (GTK_TABLE (table), button, col, col+1, row, row+1);
  gimp_help_set_help_data (button, _("Align left edge of target layer"), NULL);

  ++col;

  button = but_with_pix (pixbuf_center, &horz_group, ALIGN_CENTER);
  gtk_table_attach_defaults (GTK_TABLE (table), button, col, col+1, row, row+1);
  gimp_help_set_help_data (button, _("Align vertical center of target layer"), NULL);

  ++col;

  button = but_with_pix (pixbuf_right, &horz_group, ALIGN_RIGHT);
  gtk_table_attach_defaults (GTK_TABLE (table), button, col, col+1, row, row+1);
  gimp_help_set_help_data (button, _("Align right edge of target layer"), NULL);

  ++col;

  button = but_with_pix (pixbuf_target, &horz_group, ALIGN_POINT_HORZ);
  gtk_table_attach_defaults (GTK_TABLE (table), button, col, col+1, row, row+1);
  gimp_help_set_help_data (button, _("Align marked point on target layer"), NULL);

  ++col;
  ++col;

  button = but_with_pix (pixbuf_top, &vert_group, ALIGN_TOP);
  gtk_table_attach_defaults (GTK_TABLE (table), button, col, col+1, row, row+1);
  gimp_help_set_help_data (button, _("Align left edge of target layer"), NULL);

  ++col;

  button = but_with_pix (pixbuf_middle, &vert_group, ALIGN_MIDDLE);
  gtk_table_attach_defaults (GTK_TABLE (table), button, col, col+1, row, row+1);
  gimp_help_set_help_data (button, _("Align vertical center of target layer"), NULL);

  ++col;

  button = but_with_pix (pixbuf_bottom, &vert_group, ALIGN_BOTTOM);
  gtk_table_attach_defaults (GTK_TABLE (table), button, col, col+1, row, row+1);
  gimp_help_set_help_data (button, _("Align bottom edge of target layer"), NULL);

  ++col;

  button = but_with_pix (pixbuf_target, &vert_group, ALIGN_POINT_VERT);
  gtk_table_attach_defaults (GTK_TABLE (table), button, col, col+1, row, row+1);
  gimp_help_set_help_data (button, _("Align marked point on target layer"), NULL);

  ++row; col = 0;

  label = gtk_label_new ("Select the part of the moving layer to align");
  gtk_table_attach_defaults (GTK_TABLE (table), label, 2, 11, row, row+1);
  gtk_widget_show (label);

  gtk_table_set_row_spacing (GTK_TABLE (table), row, 20);
  ++row;

  label = gtk_label_new ("Select the part of the fixed layer to align with");
  gtk_table_attach_defaults (GTK_TABLE (table), label, 2, 11, row, row+1);
  gtk_widget_show (label);

  ++row; col = 0;
  ++col;
  ++col;

  /* Put Control buttons in */
  button = but_with_pix (pixbuf_left, &ctrl_horz_group, CTRL_ALIGN_LEFT);
  gtk_table_attach_defaults (GTK_TABLE (table), button, col, col+1, row, row+1);
  gimp_help_set_help_data (button, _("Align with left edge of control layer"), NULL);

  ++col;

  button = but_with_pix (pixbuf_center, &ctrl_horz_group, CTRL_ALIGN_CENTER);
  gtk_table_attach_defaults (GTK_TABLE (table), button, col, col+1, row, row+1);
  gimp_help_set_help_data (button, _("Align with vertical center of control layer"), NULL);

  ++col;

  button = but_with_pix (pixbuf_right, &ctrl_horz_group, CTRL_ALIGN_RIGHT);
  gtk_table_attach_defaults (GTK_TABLE (table), button, col, col+1, row, row+1);
  gimp_help_set_help_data (button, _("Align with right edge of control layer"), NULL);

  ++col;

  button = but_with_pix (pixbuf_target, &ctrl_horz_group, CTRL_ALIGN_POINT_HORZ);
  gtk_table_attach_defaults (GTK_TABLE (table), button, col, col+1, row, row+1);
  gimp_help_set_help_data (button, _("Align with marked point in control layer"), NULL);

  ++col;
  gtk_table_set_col_spacing (GTK_TABLE (table), col, 20);
  ++col;

  button = but_with_pix (pixbuf_top, &ctrl_vert_group, CTRL_ALIGN_TOP);
  gtk_table_attach_defaults (GTK_TABLE (table), button, col, col+1, row, row+1);
  gimp_help_set_help_data (button, _("Align with top edge of control layer"), NULL);

  ++col;

  button = but_with_pix (pixbuf_middle, &ctrl_vert_group, CTRL_ALIGN_MIDDLE);
  gtk_table_attach_defaults (GTK_TABLE (table), button, col, col+1, row, row+1);
  gimp_help_set_help_data (button, _("Align with horizontal center of control layer"), NULL);

  ++col;

  button = but_with_pix (pixbuf_bottom, &ctrl_vert_group, CTRL_ALIGN_BOTTOM);
  gtk_table_attach_defaults (GTK_TABLE (table), button, col, col+1, row, row+1);
  gimp_help_set_help_data (button, _("Align with bottom edge of control layer"), NULL);

  ++col;

  button = but_with_pix (pixbuf_target, &ctrl_vert_group, CTRL_ALIGN_POINT_VERT);
  gtk_table_attach_defaults (GTK_TABLE (table), button, col, col+1, row, row+1);
  gimp_help_set_help_data (button, _("Align with marked point in control layer"), NULL);

  ++row; col = 0;

  label = gtk_label_new ("");
  gtk_table_set_row_spacing (GTK_TABLE (table), row, 10);
  ++row;

  label = gtk_label_new ("Offset");
  gtk_table_attach_defaults (GTK_TABLE (table), label, 2, 4, row, row+1);
  gtk_widget_show (label);

  spinbutton = gimp_spin_button_new (&horz_offset_adjustment, 
                                     alignvals.horz_offset, 
                                     -(gdouble)image_width, 
                                     (gdouble)image_width, 
                                     1., 20., 20., 1., 0); 
  gtk_table_attach_defaults (GTK_TABLE (table), spinbutton, 4, 6, row, row+1);
  g_signal_connect (horz_offset_adjustment, "value-changed",
                    G_CALLBACK (gimp_double_adjustment_update), 
                    &alignvals.horz_offset);
  gtk_widget_show (spinbutton);

  label = gtk_label_new ("Offset");
  gtk_table_attach_defaults (GTK_TABLE (table), label, 7, 9, row, row+1);
  gtk_widget_show (label);

  spinbutton = gimp_spin_button_new (&vert_offset_adjustment, 
                                     alignvals.vert_offset, 
                                     -(gdouble)image_width, 
                                     (gdouble)image_height, 
                                     1., 20., 20., 1., 0); 
  gtk_table_attach_defaults (GTK_TABLE (table), spinbutton, 9, 11, row, row+1);
  g_signal_connect (vert_offset_adjustment, "value-changed",
                    G_CALLBACK (gimp_double_adjustment_update), 
                    &alignvals.vert_offset);
  gtk_widget_show (spinbutton);


  gtk_table_set_row_spacing (GTK_TABLE (table), row, 20);
  ++row;

  button = gtk_button_new_with_label ("View Instructions");
  gtk_table_attach_defaults (GTK_TABLE (table), button, 0, 11, row, row+1);
  g_signal_connect (button, "clicked",
                    G_CALLBACK (show_instructions), 
                    NULL);
  gtk_widget_show (button);

  gtk_widget_show (vbox);

  return vbox;
}

static GtkWidget *
but_with_pix (GdkPixbuf    *pixbuf,
              GSList      **group,
              gint          baction)
{
  GtkWidget *button;
  GtkWidget *image;

  button = gtk_radio_button_new (*group);
  gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (button), FALSE);
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), FALSE);

  image  = gtk_image_new_from_pixbuf (pixbuf);
  gtk_container_add (GTK_CONTAINER (button), image);
  gtk_widget_show (image);

  g_signal_connect (button, "clicked",
                    G_CALLBACK (set_action),
                    GINT_TO_POINTER (baction));
  gtk_widget_show (button);

  *group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (button));

  return button;
}

static void
set_action (GtkWidget *widget,
            gpointer   data)
{
  gint option = GPOINTER_TO_INT (data);

  switch (option)
    {
    case ALIGN_LEFT:
    case ALIGN_CENTER:
    case ALIGN_RIGHT:
    case ALIGN_POINT_HORZ:
      alignvals.action_horz = option;
      break;
    case ALIGN_TOP:
    case ALIGN_MIDDLE:
    case ALIGN_BOTTOM:
    case ALIGN_POINT_VERT:
      alignvals.action_vert = option;
      break;
    case CTRL_ALIGN_LEFT:
    case CTRL_ALIGN_CENTER:
    case CTRL_ALIGN_RIGHT:
    case CTRL_ALIGN_POINT_HORZ:
      alignvals.action_ctrl_horz = option;
      break;
    case CTRL_ALIGN_TOP:
    case CTRL_ALIGN_MIDDLE:
    case CTRL_ALIGN_BOTTOM:
    case CTRL_ALIGN_POINT_VERT:
      alignvals.action_ctrl_vert = option;
      break;
    default:
      g_message ("Unknown option: %d", option);
      break;
    }
}
  
static void
align_horz (GtkWidget *widget,
            gpointer   data)
{
  LayerInfo *sel_info  = &layer_info[alignvals.i_selected];
  LayerInfo *ctrl_info = &layer_info[alignvals.i_control];
  gint       x0 = 0;
  gint       xbar = 0;

  switch (alignvals.action_ctrl_horz)
    {
    case CTRL_ALIGN_LEFT:
      x0 = ctrl_info->xoffset;
      break;
    case CTRL_ALIGN_CENTER:
      x0 = ctrl_info->xoffset + 0.5 * ctrl_info->width;
      break;
    case CTRL_ALIGN_RIGHT:
      x0 = ctrl_info->xoffset + ctrl_info->width;
      break;
    case CTRL_ALIGN_POINT_HORZ:
      x0 = alignvals.ctrl_x0;
      break;
    default:
      g_message ("Unknown horizontal control action.");
      break;
    }

  switch (alignvals.action_horz)
    {
    case ALIGN_LEFT:
      xbar = 0;
      break;
    case ALIGN_CENTER:
      xbar = 0.5 * sel_info->width;
      break;
    case ALIGN_RIGHT:
      xbar = sel_info->width;
      break;
    case ALIGN_POINT_HORZ:
      xbar = alignvals.select_x0;
      break;
    default:
      g_message ("Unknown horizontal action.");
      break;
    }

  sel_info->xoffset = x0 - xbar + alignvals.horz_offset;

  gimp_layer_set_offsets (layer_list[alignvals.i_selected], 
                          sel_info->xoffset, 
                          sel_info->yoffset);

  gimp_displays_flush ();
  if (thumb_pixbuf)
    gdk_pixbuf_unref (thumb_pixbuf);
  thumb_pixbuf = NULL;
  preview_expose (preview, NULL);
}

static void
align_vert (GtkWidget *widget,
            gpointer   data)
{
  LayerInfo *sel_info  = &layer_info[alignvals.i_selected];
  LayerInfo *ctrl_info = &layer_info[alignvals.i_control];
  gint       y0 = 0;
  gint       ybar = 0;

  switch (alignvals.action_ctrl_vert)
    {
    case CTRL_ALIGN_TOP:
      y0 = ctrl_info->yoffset;
      break;
    case CTRL_ALIGN_MIDDLE:
      y0 = ctrl_info->yoffset + 0.5 * ctrl_info->height;
      break;
    case CTRL_ALIGN_BOTTOM:
      y0 = ctrl_info->yoffset + ctrl_info->height;
      break;
    case CTRL_ALIGN_POINT_VERT:
      y0 = alignvals.ctrl_y0;
      break;
    default:
      g_message ("Unknown vertical control action.");
      break;
    }

  switch (alignvals.action_vert)
    {
    case ALIGN_TOP:
      ybar = 0;
      break;
    case ALIGN_MIDDLE:
      ybar = 0.5 * sel_info->height;
      break;
    case ALIGN_BOTTOM:
      ybar = sel_info->height;
      break;
    case ALIGN_POINT_VERT:
      ybar = alignvals.select_y0;
      break;
    default:
      g_message ("Unknown vertical action.");
      break;
    }

  sel_info->yoffset = y0 - ybar + alignvals.vert_offset;

  gimp_layer_set_offsets (layer_list[alignvals.i_selected], 
                          sel_info->xoffset, 
                          sel_info->yoffset);

  gimp_displays_flush ();
  if (thumb_pixbuf)
    gdk_pixbuf_unref (thumb_pixbuf);
  thumb_pixbuf = NULL;
  preview_expose (preview, NULL);
}

static void
show_instructions (GtkWidget *widget,
                   gpointer   data)
{
  GtkWidget   *dlg;
  GtkWidget   *scrollwin;
  GtkWidget   *instruction_label;

  dlg = gimp_dialog_new (_("Instructions"), "instructions",
                         NULL, GTK_DIALOG_DESTROY_WITH_PARENT,
                         gimp_standard_help_func, "plug-in-align",

                         GTK_STOCK_OK,     GTK_RESPONSE_OK,

                         NULL);

  gtk_window_set_default_size (GTK_WINDOW (dlg), 400, 300);

  scrollwin = gtk_scrolled_window_new (NULL, NULL);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->vbox), scrollwin,
                      TRUE, TRUE, 0);
  gtk_widget_show (scrollwin);

  instruction_label = gtk_label_new (instruction_text);
  gtk_label_set_line_wrap (GTK_LABEL (instruction_label), TRUE);
  gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scrollwin),
                                         instruction_label);
  gtk_widget_show (instruction_label);

  gtk_widget_show (dlg);

  gimp_dialog_run (GIMP_DIALOG (dlg));

  gtk_widget_destroy (dlg);
}
