Logo Search packages:      
Sourcecode: mtpaint version File versions  Download package

mainwindow.c

/*    mainwindow.c
      Copyright (C) 2004-2009 Mark Tyler and Dmitry Groshev

      This file is part of mtPaint.

      mtPaint 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 3 of the License, or
      (at your option) any later version.

      mtPaint 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 mtPaint in the file COPYING.
*/

#include "global.h"

#include "mygtk.h"
#include "memory.h"
#include "png.h"
#include "mainwindow.h"
#include "viewer.h"
#include "otherwindow.h"
#include "inifile.h"
#include "canvas.h"
#include "polygon.h"
#include "layer.h"
#include "info.h"
#include "prefs.h"
#include "ani.h"
#include "channels.h"
#include "toolbar.h"
#include "csel.h"
#include "shifter.h"
#include "spawn.h"
#include "font.h"
#include "icons.h"


#define GREY_W 153
#define GREY_B 102
const unsigned char greyz[2] = {GREY_W, GREY_B}; // For opacity squares

char *channames[NUM_CHANNELS + 1], *allchannames[NUM_CHANNELS + 1];

///   INIFILE ENTRY LISTS

00052 typedef struct {
      char *name;
      int *var;
      int defv;
} inilist;

static inilist ini_bool[] = {
      { "layermainToggle",    &show_layers_main,      FALSE },
      { "sharperReduce",      &sharper_reduce,  FALSE },
      { "tablet_USE",         &tablet_working,  FALSE },
      { "tga565",       &tga_565,         FALSE },
      { "tgaDefdir",          &tga_defdir,            FALSE },
      { "disableTransparency", &opaque_view,          FALSE },
      { "smudgeOpacity",      &smudge_mode,           FALSE },
      { "undoableLoad", &undo_load,       FALSE },
      { "showMenuIcons",      &show_menu_icons, FALSE },
      { "colorGrid",          &color_grid,            FALSE },
      { "showTileGrid", &show_tile_grid,  FALSE },
      { "pasteCommit",  &paste_commit,          FALSE },
      { "couple_RGBA",  &RGBA_mode,       TRUE  },
      { "gridToggle",         &mem_show_grid,         TRUE  },
      { "optimizeChequers",   &chequers_optimize,     TRUE  },
      { "quitToggle",         &q_quit,          TRUE  },
      { "continuousPainting", &mem_continuous,  TRUE  },
      { "opacityToggle",      &mem_undo_opacity,      TRUE  },
      { "imageCentre",  &canvas_image_centre,   TRUE  },
      { "view_focus",         &vw_focus_on,           TRUE  },
      { "pasteToggle",  &show_paste,            TRUE  },
      { "cursorToggle", &cursor_tool,           TRUE  },
      { "autopreviewToggle",  &brcosa_auto,           TRUE  },
#if STATUS_ITEMS != 5
#error Wrong number of "status?Toggle" inifile items defined
#endif
      { "status0Toggle",      status_on + 0,          TRUE  },
      { "status1Toggle",      status_on + 1,          TRUE  },
      { "status2Toggle",      status_on + 2,          TRUE  },
      { "status3Toggle",      status_on + 3,          TRUE  },
      { "status4Toggle",      status_on + 4,          TRUE  },
      { NULL,                 NULL }
};

static inilist ini_int[] = {
      { "jpegQuality",  &jpeg_quality,          85  },
      { "pngCompression",     &png_compression, 9   },
      { "tgaRLE",       &tga_RLE,         0   },
      { "jpeg2000Rate", &jp2_rate,        1   },
      { "silence_limit",      &silence_limit,         18  },
      { "gradientOpacity",    &grad_opacity,          128 },
      { "gridMin",            &mem_grid_min,          8   },
      { "undoMBlimit",  &mem_undo_limit,  32  },
      { "undoCommon",         &mem_undo_common, 25  },
      { "backgroundGrey",     &mem_background,  180 },
      { "pixelNudge",         &mem_nudge,       8   },
      { "recentFiles",  &recent_files,          10  },
      { "lastspalType", &spal_mode,       2   },
      { "panSize",            &max_pan,         128 },
      { "undoDepth",          &mem_undo_depth,  DEF_UNDO },
      { "tileWidth",          &tgrid_dx,        32  },
      { "tileHeight",         &tgrid_dy,        32  },
      { "gridRGB",            grid_rgb + 0,     RGB_2_INT( 50,  50,  50) },
      { "gridBorder",         grid_rgb + 1,     RGB_2_INT(  0, 219,   0) },
      { "gridTrans",          grid_rgb + 2,     RGB_2_INT(  0, 109, 109) },
      { "gridTile",           grid_rgb + 3,     RGB_2_INT(170, 170, 170) },
      { NULL,                 NULL }
};


GtkWidget *main_window, *vbox_main, *main_vsplit, *main_hsplit, *main_split,
      *drawing_palette, *drawing_canvas, *vbox_right, *vw_scrolledwindow,
      *scrolledwindow_canvas, *main_hidden[4],

      *menu_widgets[TOTAL_MENU_IDS],
      *dock_pane, *dock_area;

int   view_image_only, viewer_mode, drag_index, q_quit, cursor_tool;
int   show_menu_icons, paste_commit;
int   files_passed, file_arg_start, drag_index_vals[2], cursor_corner, show_dock;
char **global_argv;


static int perim_status, perim_x, perim_y, perim_s;   // Tool perimeter

static void clear_perim_real( int ox, int oy )
{
      int x0, y0, x1, y1, zoom = 1, scale = 1;


      /* !!! This uses the fact that zoom factor is either N or 1/N !!! */
      if (can_zoom < 1.0) zoom = rint(1.0 / can_zoom);
      else scale = rint(can_zoom);

      x0 = margin_main_x + ((perim_x + ox) * scale) / zoom;
      y0 = margin_main_y + ((perim_y + oy) * scale) / zoom;
      x1 = margin_main_x + ((perim_x + ox + perim_s - 1) * scale) / zoom + scale - 1;
      y1 = margin_main_y + ((perim_y + oy + perim_s - 1) * scale) / zoom + scale - 1;

      repaint_canvas(x0, y0, 1, y1 - y0 + 1);
      repaint_canvas(x1, y0, 1, y1 - y0 + 1);
      repaint_canvas(x0 + 1, y0, x1 - x0 - 1, 1);
      repaint_canvas(x0 + 1, y1, x1 - x0 - 1, 1);
}

00154 typedef struct {
      GtkWidget *widget;
      int actmap;
} dis_information;

static dis_information *dis_array;
static int dis_count, dis_allow;
static int dis_miss = ~0;

void mapped_dis_add(GtkWidget *widget, int actmap)
{
      if (!actmap) return;
      if (dis_count >= dis_allow) dis_array = realloc(dis_array,
            (dis_allow += 128) * sizeof(dis_information));
      /* If no memory, just die of SIGSEGV */
      dis_array[dis_count].widget = widget;
      dis_array[dis_count].actmap = actmap;
      dis_count++;
}

/* Enable or disable menu items */
void mapped_item_state(int statemap)
{
      int i;

      if (dis_miss == statemap) return; // Nothing changed
      for (i = 0; i < dis_count; i++)
            gtk_widget_set_sensitive(dis_array[i].widget,
                  !!(dis_array[i].actmap & statemap));
      dis_miss = statemap;
}

static void pressed_load_recent(int item)
{
      int change;
      char txt[64], *c;

      sprintf( txt, "file%i", item );
      c = inifile_get( txt, "." );

      if ( layers_total==0 )
            change = check_for_changes();
      else
            change = check_layers_for_changes();

      if ( change == 2 || change == -10 )
            do_a_load(c);           // Load requested file
}

static void pressed_crop()
{
      int res, x1, y1, x2, y2;

      mtMIN( x1, marq_x1, marq_x2 )
      mtMIN( y1, marq_y1, marq_y2 )
      mtMAX( x2, marq_x1, marq_x2 )
      mtMAX( y2, marq_y1, marq_y2 )

      if ( marq_status != MARQUEE_DONE ) return;
      if ( x1==0 && x2>=(mem_width-1) && y1==0 && y2>=(mem_height-1) ) return;

      res = mem_image_resize(x2 - x1 + 1, y2 - y1 + 1, -x1, -y1, 0);

      if (!res)
      {
            pressed_select(FALSE);
            change_to_tool(DEFAULT_TOOL_ICON);
            update_stuff(UPD_GEOM);
      }
      else memory_errors(res);
}

void pressed_select(int all)
{
      int i = 0;

      /* Remove old selection */
      if (marq_status != MARQUEE_NONE)
      {
            i = UPD_SEL;
            if (marq_status >= MARQUEE_PASTE) i = UPD_SEL | CF_DRAW;
            else paint_marquee(0, 0, 0);
            marq_status = MARQUEE_NONE;
      }
      if ((tool_type == TOOL_POLYGON) && (poly_status != POLY_NONE))
      {
            poly_points = 0;
            poly_status = POLY_NONE;
            i = UPD_SEL | CF_DRAW; // Have to erase polygon
      }
      /* And deal with selection persistence too */
      marq_x1 = marq_y1 = marq_x2 = marq_y2 = -1;

      while (all) /* Select entire canvas */
      {
            i |= UPD_SEL;
            marq_x1 = 0;
            marq_y1 = 0;
            marq_x2 = mem_width - 1;
            marq_y2 = mem_height - 1;
            if (tool_type != TOOL_SELECT)
            {
                  /* Switch tool, and let that & marquee persistence
                   * do all the rest except full redraw */
                  change_to_tool(TTB_SELECT);
                  i &= CF_DRAW;
                  break;
            }
            marq_status = MARQUEE_DONE;
            if (i & CF_DRAW) break; // Full redraw will draw marquee too
            paint_marquee(1, 0, 0);
            break;
      }
      if (i) update_stuff(i);
}

static void pressed_remove_unused()
{
      int i;

      i = mem_remove_unused_check();
      if ( i <= 0 )
            alert_box( _("Error"), _("There were no unused colours to remove!"),
                  _("OK"), NULL, NULL);
      if ( i > 0 )
      {
            spot_undo(UNDO_XPAL);

            mem_remove_unused();
            mem_undo_prepare();

            update_stuff(UPD_PAL);
      }
}

static void pressed_default_pal()
{
      spot_undo(UNDO_PAL);
      mem_pal_copy( mem_pal, mem_pal_def );
      mem_cols = mem_pal_def_i;
      update_stuff(UPD_PAL);
}

static void pressed_remove_duplicates()
{
      char *mess;
      int dups = scan_duplicates();

      if (!dups)
      {
            alert_box( _("Error"), _("The palette does not contain 2 colours that have identical RGB values"), _("OK"), NULL, NULL );
            return;
      }
      mess = g_strdup_printf(_("The palette contains %i colours that have identical RGB values.  Do you really want to merge them into one index and realign the canvas?"), dups);
      if (alert_box(_("Warning"), mess, _("Yes"), _("No"), NULL) == 1)
      {
            spot_undo(UNDO_XPAL);

            remove_duplicates();
            mem_undo_prepare();
            update_stuff(UPD_PAL);
      }
      g_free(mess);
}

static void pressed_dither_A()
{
      mem_find_dither(mem_col_A24.red, mem_col_A24.green, mem_col_A24.blue);
      update_stuff(UPD_ABP);
}

static void pressed_mask(int val)
{
      mem_mask_setall(val);
      update_stuff(UPD_CMASK);
}

// System clipboard import

static GtkTargetEntry clip_formats[] = {
      { NULL, 0, FT_NONE },
      { "application/x-mtpaint-clipboard", 0, FT_PNG | FTM_EXTEND },
      { "image/png", 0, FT_PNG },
      { "image/bmp", 0, FT_BMP },
      { "image/x-bmp", 0, FT_BMP },
      { "image/x-MS-bmp", 0, FT_BMP },
#if (GTK_MAJOR_VERSION == 1) || defined GDK_WINDOWING_X11
      /* These two don't make sense without X */
      { "PIXMAP", 0, FT_PIXMAP },
      { "BITMAP", 0, FT_PIXMAP },
      /* !!! BITMAP requests are handled same as PIXMAP - because it is only
       * done to appease buggy XPaint which requests both and crashes if
       * receiving only one - WJ */
#endif
};
#define CLIP_TARGETS (sizeof(clip_formats) / sizeof(GtkTargetEntry))
static GdkAtom clip_atoms[CLIP_TARGETS];

/* Seems it'll be better to prefer BMP when talking to the likes of GIMP -
 * they send PNGs really slowly (likely, compressed to the max); but not
 * everyone supports alpha in BMPs. */

static int clipboard_check_fn(GtkSelectionData *data, gpointer user_data)
{
      GdkAtom *targets, tst;
      int i, j, k, n = data->length / sizeof(GdkAtom);

      if ((n <= 0) || (data->format != 32) ||
            (data->type != GDK_SELECTION_TYPE_ATOM)) return (FALSE);

      /* Convert names to atoms if not done already */
      if (!clip_atoms[1])
      {
            for (i = 1; i < CLIP_TARGETS; i++) clip_atoms[i] =
                  gdk_atom_intern(clip_formats[i].target, FALSE);
      }

      /* Search for best match */
      targets = (GdkAtom *)data->data;
      for (i = 0 , k = CLIP_TARGETS; i < n; i++)
      {
            tst = *targets++;
//g_print("\"%s\" ", gdk_atom_name(tst));
            for (j = 1; (j < k) && (tst != clip_atoms[j]); j++);
            k = j;
      }
//g_print(": %d\n", k);
      *(int *)user_data = k;
      return (k < CLIP_TARGETS);
}

static int check_clipboard(int which)
{
      int res;

      if (internal_clipboard(which)) return (0); // if we're who put data there
      if (!process_clipboard(which, "TARGETS",
            GTK_SIGNAL_FUNC(clipboard_check_fn), &res)) return (0); // no luck
      return (res);
}

static int clipboard_import_fn(GtkSelectionData *data, gpointer user_data)
{
//g_print("!!! %X %d\n", data->data, data->length);
      return (load_mem_image((unsigned char *)data->data, data->length,
            ((int *)user_data)[0], ((int *)user_data)[1]) == 1);
}

int import_clipboard(int mode)
{
      int i = 0, n = 0, udata[2] = { mode };

#if (GTK_MAJOR_VERSION == 1) || defined GDK_WINDOWING_X11
      // If no luck with CLIPBOARD, check PRIMARY too
      for (; !i && (n < 2); n++)
#endif
      if ((i = check_clipboard(n)))
      {
            udata[1] = (int)clip_formats[i].info;
            i = process_clipboard(n, clip_formats[i].target,
                  GTK_SIGNAL_FUNC(clipboard_import_fn), udata);
      }
      return (i);
}

static void setup_clip_save(ls_settings *settings)
{
      init_ls_settings(settings, NULL);
      memcpy(settings->img, mem_clip.img, sizeof(chanlist));
      settings->pal = mem_pal;
      settings->width = mem_clip_w;
      settings->height = mem_clip_h;
      settings->bpp = mem_clip_bpp;
      settings->colors = mem_cols;
}

static int clipboard_export_fn(GtkSelectionData *data, gpointer user_data)
{
      ls_settings settings;
      unsigned char *buf;
      int res, len, type = (int)user_data;

//g_print("Entered! %X %d\n", data, type);
      if (!data) return (FALSE); // Someone else stole system clipboard
      if (!mem_clipboard) return (FALSE); // Our own clipboard got emptied

      /* Prepare settings */
      setup_clip_save(&settings);
      settings.mode = FS_CLIPBOARD;
      settings.ftype = type;
      settings.png_compression = 1; // Speed is of the essence

      res = save_mem_image(&buf, &len, &settings);
//g_print("Save returned %d\n", res);
      if (res) return (FALSE); // No luck creating in-memory image

#if (GTK_MAJOR_VERSION == 1) || defined GDK_WINDOWING_X11
      if ((type & FTM_FTYPE) == FT_PIXMAP)
      {
            /* !!! XID of pixmap gets returned in buffer pointer */
            gtk_selection_data_set(data, data->target, 32, (guchar *)&buf, len);
            return (TRUE);
      }
#endif

/* !!! Should allocation for data copying fail, GTK+ will die horribly - so
 * maybe it'll be better to hack up the function and pass the original data
 * instead; but to do so, I'd need to use g_try_*() allocation functions in
 * memFILE writing path - WJ */
      gtk_selection_data_set(data, data->target, 8, buf, len);
      free(buf);
      return (TRUE);
}

static int export_clipboard()
{
      int res;

      if (!mem_clipboard) return (FALSE);
      res = offer_clipboard(0, clip_formats + 1, CLIP_TARGETS - 1,
            GTK_SIGNAL_FUNC(clipboard_export_fn));
#if (GTK_MAJOR_VERSION == 1) || defined GDK_WINDOWING_X11
      /* Offer both CLIPBOARD and PRIMARY */
      res |= offer_clipboard(1, clip_formats + 1, CLIP_TARGETS - 1,
            GTK_SIGNAL_FUNC(clipboard_export_fn));
#endif
      return (res);
}

int gui_save(char *filename, ls_settings *settings)
{
      int res = -2, fflags = file_formats[settings->ftype].flags;
      char *mess = NULL, *f8;

      /* Mismatched format - raise an error right here */
      if ((fflags & FF_NOSAVE) || !(fflags & FF_SAVE_MASK))
      {
            int maxc = 0;
            char *fform = NULL, *fname = file_formats[settings->ftype].name;

            /* RGB to indexed (or to unsaveable) */
            if (mem_img_bpp == 3) fform = _("RGB");
            /* Indexed to RGB, or to unsaveable format */
            else if (!(fflags & FF_IDX) || (fflags & FF_NOSAVE))
                  fform = _("indexed");
            /* More than 16 colors */
            else if (fflags & FF_16) maxc = 16;
            /* More than 2 colors */
            else maxc = 2;
            /* Build message */
            if (fform) mess = g_strdup_printf(_("You are trying to save an %s image to an %s file which is not possible.  I would suggest you save with a PNG extension."),
                  fform, fname);
            else mess = g_strdup_printf(_("You are trying to save an %s file with a palette of more than %d colours.  Either use another format or reduce the palette to %d colours."),
                  fname, maxc, maxc);
      }
      else
      {
            /* Prepare to save image */
            memcpy(settings->img, mem_img, sizeof(chanlist));
            settings->pal = mem_pal;
            settings->width = mem_width;
            settings->height = mem_height;
            settings->bpp = mem_img_bpp;
            settings->colors = mem_cols;

            res = save_image(filename, settings);
      }
      if (res < 0)
      {
            if (res == -1)
            {
                  f8 = gtkuncpy(NULL, filename, 0);
                  mess = g_strdup_printf(_("Unable to save file: %s"), f8);
                  g_free(f8);
            }
            else if ((res == WRONG_FORMAT) && (settings->ftype == FT_XPM))
                  mess = g_strdup(_("You are trying to save an XPM file with more than 4096 colours.  Either use another format or posterize the image to 4 bits, or otherwise reduce the number of colours."));
            if (mess)
            {
                  alert_box( _("Error"), mess, _("OK"), NULL, NULL );
                  g_free(mess);
            }
      }
      else
      {
            notify_unchanged();
            register_file( filename );
      }

      return res;
}

static void pressed_save_file()
{
      ls_settings settings;

      while (mem_filename[0])
      {
            init_ls_settings(&settings, NULL);
            settings.ftype = file_type_by_ext(mem_filename, FF_IMAGE);
            if (settings.ftype == FT_NONE) break;
            settings.mode = FS_PNG_SAVE;
            if (gui_save(mem_filename, &settings) < 0) break;
            return;
      }
      file_selector(FS_PNG_SAVE);
}

char mem_clip_file[PATHBUF];

static void load_clip(int item)
{
      char clip[PATHBUF];
      int i;

      if (item == -1) // System clipboard
            i = import_clipboard(FS_CLIPBOARD);
      else // Disk file
      {
            snprintf(clip, PATHBUF, "%s%i", mem_clip_file, item);
            i = load_image(clip, FS_CLIP_FILE, FT_PNG) == 1;
      }

      if (!i) alert_box( _("Error"), _("Unable to load clipboard"), _("OK"), NULL, NULL );

      update_stuff(UPD_XCOPY);
      if (i && (MEM_BPP >= mem_clip_bpp)) pressed_paste(TRUE);
}

static void save_clip(int item)
{
      ls_settings settings;
      char clip[PATHBUF];
      int i;

      if (item == -1) // Exporting clipboard
      {
            export_clipboard();
            return;
      }

      /* Prepare settings */
      setup_clip_save(&settings);
      settings.mode = FS_CLIP_FILE;
      settings.ftype = FT_PNG;

      snprintf(clip, PATHBUF, "%s%i", mem_clip_file, item);
      i = save_image(clip, &settings);

      if ( i!=0 ) alert_box( _("Error"), _("Unable to save clipboard"), _("OK"), NULL, NULL );
}

void pressed_opacity(int opacity)
{
      if (IS_INDEXED) opacity = 255;
      tool_opacity = opacity < 1 ? 1 : opacity > 255 ? 255 : opacity;
      update_stuff(UPD_OPAC);
}

void pressed_value(int value)
{
      if (mem_channel == CHN_IMAGE) return;
      channel_col_A[mem_channel] =
            value < 0 ? 0 : value > 255 ? 255 : value;
      update_stuff(UPD_CAB);
}

static void toggle_view()
{
      int i;

      view_image_only = !view_image_only;

      for ( i=0; i<1; i++ )
            if (view_image_only) gtk_widget_hide(main_hidden[i]);
                  else gtk_widget_show(main_hidden[i]);

      for ( i=1; i<TOOLBAR_MAX; i++ )
      {
            if ( toolbar_boxes[i] ) gtk_widget_hide(toolbar_boxes[i]);
      }

      if ( !view_image_only ) toolbar_showhide();     // Switch toolbar/status/palette on if needed
}

void zoom_in()
{
      if (can_zoom >= 1) align_size(can_zoom + 1);
      else align_size(1.0 / (rint(1.0 / can_zoom) - 1));
}

void zoom_out()
{
      if (can_zoom > 1) align_size(can_zoom - 1);
      else align_size(1.0 / (rint(1.0 / can_zoom) + 1));
}

static void zoom_grid(int state)
{
      mem_show_grid = state;
      update_stuff(UPD_RENDER);
}

static gboolean delete_event( GtkWidget *widget, GdkEvent *event, gpointer data );
static void quit_all(int mode)
{
      if (mode || q_quit) delete_event( NULL, NULL, NULL );
}

/* Forward declaration */
static void mouse_event(int event, int xc, int yc, guint state, guint button,
      gdouble pressure, int mflag, int dx, int dy);

/* For "dual" mouse control */
static int unreal_move, lastdx, lastdy;

static void move_mouse(int dx, int dy, int button)
{
      static GdkModifierType bmasks[4] =
            {0, GDK_BUTTON1_MASK, GDK_BUTTON2_MASK, GDK_BUTTON3_MASK};
      GdkModifierType state;
      int x, y, zoom = 1, scale = 1;

      if (!unreal_move) lastdx = lastdy = 0;
      if (!mem_img[CHN_IMAGE]) return;
      dx += lastdx; dy += lastdy;

      gdk_window_get_pointer(drawing_canvas->window, &x, &y, &state);

      if (button) /* Clicks simulated without extra movements */
      {
            state |= bmasks[button];
            mouse_event(GDK_BUTTON_PRESS, x, y, state, button, 1.0, 1, dx, dy);
            state ^= bmasks[button];
            mouse_event(GDK_BUTTON_RELEASE, x, y, state, button, 1.0, 1, dx, dy);
            return;
      }

      if ((state & (GDK_BUTTON1_MASK | GDK_BUTTON3_MASK)) ==
            (GDK_BUTTON1_MASK | GDK_BUTTON3_MASK)) button = 13;
      else if (state & GDK_BUTTON1_MASK) button = 1;
      else if (state & GDK_BUTTON3_MASK) button = 3;
      else if (state & GDK_BUTTON2_MASK) button = 2;

      /* !!! This uses the fact that zoom factor is either N or 1/N !!! */
      if (can_zoom < 1.0) zoom = rint(1.0 / can_zoom);
      else scale = rint(can_zoom);

      if (zoom > 1) /* Fine control required */
      {
            lastdx = dx; lastdy = dy;
            mouse_event(GDK_MOTION_NOTIFY, x, y, state, button, 1.0, 1, dx, dy);

            /* Nudge cursor when needed */
            if ((abs(lastdx) >= zoom) || (abs(lastdy) >= zoom))
            {
                  dx = lastdx * can_zoom;
                  dy = lastdy * can_zoom;
                  lastdx -= dx * zoom;
                  lastdy -= dy * zoom;
                  unreal_move = 3;
                  /* Event can be delayed or lost */
                  move_mouse_relative(dx, dy);
            }
            else unreal_move = 2;
      }
      else /* Real mouse is precise enough */
      {
            unreal_move = 1;

            /* Simulate movement if failed to actually move mouse */
            if (!move_mouse_relative(dx * scale, dy * scale))
            {
                  lastdx = dx; lastdy = dy;
                  mouse_event(GDK_MOTION_NOTIFY, x, y, state, button, 1.0, 1, dx, dy);
            }
      }
}

void stop_line()
{
      if ( line_status != LINE_NONE ) repaint_line(0);
      line_status = LINE_NONE;
}

int check_zoom_keys_real(int act_m)
{
      int action = act_m >> 16;

      if ((action == ACT_ZOOM) || (action == ACT_VIEW) || (action == ACT_VWZOOM))
      {
            action_dispatch(action, (act_m & 0xFFFF) - 0x8000, 0, TRUE);
            return (TRUE);
      }
      return (FALSE);
}

int check_zoom_keys(int act_m)
{
      int action = act_m >> 16;

      if (check_zoom_keys_real(act_m)) return (TRUE);
      if ((action == ACT_DOCK) || (action == ACT_QUIT) ||
            (action == DLG_BRCOSA) || (action == ACT_PAN) ||
            (action == ACT_CROP) || (action == ACT_SWAP_AB) ||
            (action == DLG_PATTERN) || (action == DLG_BRUSH) ||
            (action == ACT_TOOL))
            action_dispatch(action, (act_m & 0xFFFF) - 0x8000, 0, TRUE);
      else return (FALSE);

      return (TRUE);
}

#define _C (GDK_CONTROL_MASK)
#define _S (GDK_SHIFT_MASK)
#define _A (GDK_MOD1_MASK)
#define _CS (GDK_CONTROL_MASK | GDK_SHIFT_MASK)
#define _CSA (GDK_CONTROL_MASK | GDK_SHIFT_MASK | GDK_MOD1_MASK)

static const int mod_bits[] = { 0, _C, _S, _CS, _CSA };

#define MOD_0   0x00
#define MOD_c   0x01
#define MOD_s   0x02
#define MOD_cs  0x03
#define MOD_csa 0x04
#define MOD_S   0x22
#define MOD_cS  0x23
#define MOD_Cs  0x13
#define MOD_CS  0x33

00785 typedef struct {
      short action, mode;
      int key;
      unsigned char mod;
} key_action;

static key_action main_keys[] = {
      { ACT_QUIT, 0,          GDK_q,            MOD_0   },
      { ACT_ZOOM, 0,          GDK_plus,   MOD_cs  },
      { ACT_ZOOM, 0,          GDK_KP_Add, MOD_cs  },
      { ACT_ZOOM, -1,         GDK_minus,  MOD_cs  },
      { ACT_ZOOM, -1,         GDK_KP_Subtract, MOD_cs },
      { ACT_ZOOM, -10,        GDK_KP_1,   MOD_cs  },
      { ACT_ZOOM, -10,        GDK_1,            MOD_cs  },
      { ACT_ZOOM, -4,         GDK_KP_2,   MOD_cs  },
      { ACT_ZOOM, -4,         GDK_2,            MOD_cs  },
      { ACT_ZOOM, -2,         GDK_KP_3,   MOD_cs  },
      { ACT_ZOOM, -2,         GDK_3,            MOD_cs  },
      { ACT_ZOOM, 1,          GDK_KP_4,   MOD_cs  },
      { ACT_ZOOM, 1,          GDK_4,            MOD_cs  },
      { ACT_ZOOM, 4,          GDK_KP_5,   MOD_cs  },
      { ACT_ZOOM, 4,          GDK_5,            MOD_cs  },
      { ACT_ZOOM, 8,          GDK_KP_6,   MOD_cs  },
      { ACT_ZOOM, 8,          GDK_6,            MOD_cs  },
      { ACT_ZOOM, 12,         GDK_KP_7,   MOD_cs  },
      { ACT_ZOOM, 12,         GDK_7,            MOD_cs  },
      { ACT_ZOOM, 16,         GDK_KP_8,   MOD_cs  },
      { ACT_ZOOM, 16,         GDK_8,            MOD_cs  },
      { ACT_ZOOM, 20,         GDK_KP_9,   MOD_cs  },
      { ACT_ZOOM, 20,         GDK_9,            MOD_cs  },
      { ACT_VIEW, 0,          GDK_Home,   MOD_0   },
      { DLG_BRCOSA,     0,          GDK_Insert, MOD_cs  },
      { ACT_PAN,  0,          GDK_End,    MOD_cs  },
      { ACT_CROP, 0,          GDK_Delete, MOD_cs  },
      { ACT_SWAP_AB,    0,          GDK_x,            MOD_csa },
      { DLG_PATTERN,    0,          GDK_F2,           MOD_csa },
      { DLG_BRUSH,      0,          GDK_F3,           MOD_csa },
      { ACT_TOOL, TTB_PAINT,  GDK_F4,           MOD_csa },
      { ACT_TOOL, TTB_SELECT, GDK_F9,     MOD_csa },
      { ACT_DOCK, 0,          GDK_F12,    MOD_csa },
      { ACT_SEL_MOVE,   5,          GDK_Left,   MOD_cS  },
      { ACT_SEL_MOVE,   5,          GDK_KP_Left,      MOD_cS  },
      { ACT_SEL_MOVE,   7,          GDK_Right,  MOD_cS  },
      { ACT_SEL_MOVE,   7,          GDK_KP_Right,     MOD_cS  },
      { ACT_SEL_MOVE,   3,          GDK_Down,   MOD_cS  },
      { ACT_SEL_MOVE,   3,          GDK_KP_Down,      MOD_cS  },
      { ACT_SEL_MOVE,   9,          GDK_Up,           MOD_cS  },
      { ACT_SEL_MOVE,   9,          GDK_KP_Up,  MOD_cS  },
      { ACT_SEL_MOVE,   4,          GDK_Left,   MOD_cs  },
      { ACT_SEL_MOVE,   4,          GDK_KP_Left,      MOD_cs  },
      { ACT_SEL_MOVE,   6,          GDK_Right,  MOD_cs  },
      { ACT_SEL_MOVE,   6,          GDK_KP_Right,     MOD_cs  },
      { ACT_SEL_MOVE,   2,          GDK_Down,   MOD_cs  },
      { ACT_SEL_MOVE,   2,          GDK_KP_Down,      MOD_cs  },
      { ACT_SEL_MOVE,   8,          GDK_Up,           MOD_cs  },
      { ACT_SEL_MOVE,   8,          GDK_KP_Up,  MOD_cs  },
      { ACT_OPAC, 1,          GDK_KP_1,   MOD_Cs  },
      { ACT_OPAC, 1,          GDK_1,            MOD_Cs  },
      { ACT_OPAC, 2,          GDK_KP_2,   MOD_Cs  },
      { ACT_OPAC, 2,          GDK_2,            MOD_Cs  },
      { ACT_OPAC, 3,          GDK_KP_3,   MOD_Cs  },
      { ACT_OPAC, 3,          GDK_3,            MOD_Cs  },
      { ACT_OPAC, 4,          GDK_KP_4,   MOD_Cs  },
      { ACT_OPAC, 4,          GDK_4,            MOD_Cs  },
      { ACT_OPAC, 5,          GDK_KP_5,   MOD_Cs  },
      { ACT_OPAC, 5,          GDK_5,            MOD_Cs  },
      { ACT_OPAC, 6,          GDK_KP_6,   MOD_Cs  },
      { ACT_OPAC, 6,          GDK_6,            MOD_Cs  },
      { ACT_OPAC, 7,          GDK_KP_7,   MOD_Cs  },
      { ACT_OPAC, 7,          GDK_7,            MOD_Cs  },
      { ACT_OPAC, 8,          GDK_KP_8,   MOD_Cs  },
      { ACT_OPAC, 8,          GDK_8,            MOD_Cs  },
      { ACT_OPAC, 9,          GDK_KP_9,   MOD_Cs  },
      { ACT_OPAC, 9,          GDK_9,            MOD_Cs  },
      { ACT_OPAC, 10,         GDK_KP_0,   MOD_Cs  },
      { ACT_OPAC, 10,         GDK_0,            MOD_Cs  },
      { ACT_OPAC, 0,          GDK_plus,   MOD_Cs  },
      { ACT_OPAC, 0,          GDK_KP_Add, MOD_Cs  },
      { ACT_OPAC, -1,         GDK_minus,  MOD_Cs  },
      { ACT_OPAC, -1,         GDK_KP_Subtract, MOD_Cs },
      { ACT_LR_MOVE,    5,          GDK_Left,   MOD_CS  },
      { ACT_LR_MOVE,    5,          GDK_KP_Left,      MOD_CS  },
      { ACT_LR_MOVE,    7,          GDK_Right,  MOD_CS  },
      { ACT_LR_MOVE,    7,          GDK_KP_Right,     MOD_CS  },
      { ACT_LR_MOVE,    3,          GDK_Down,   MOD_CS  },
      { ACT_LR_MOVE,    3,          GDK_KP_Down,      MOD_CS  },
      { ACT_LR_MOVE,    9,          GDK_Up,           MOD_CS  },
      { ACT_LR_MOVE,    9,          GDK_KP_Up,  MOD_CS  },
      { ACT_LR_MOVE,    4,          GDK_Left,   MOD_Cs  },
      { ACT_LR_MOVE,    4,          GDK_KP_Left,      MOD_Cs  },
      { ACT_LR_MOVE,    6,          GDK_Right,  MOD_Cs  },
      { ACT_LR_MOVE,    6,          GDK_KP_Right,     MOD_Cs  },
      { ACT_LR_MOVE,    2,          GDK_Down,   MOD_Cs  },
      { ACT_LR_MOVE,    2,          GDK_KP_Down,      MOD_Cs  },
      { ACT_LR_MOVE,    8,          GDK_Up,           MOD_Cs  },
      { ACT_LR_MOVE,    8,          GDK_KP_Up,  MOD_Cs  },
      { ACT_ESC,  0,          GDK_Escape, MOD_cs  },
      { DLG_SCALE,      0,          GDK_Page_Up,      MOD_cs  },
      { DLG_SIZE, 0,          GDK_Page_Down,    MOD_cs  },
      { ACT_COMMIT,     0,          GDK_Return, MOD_s   },
      { ACT_COMMIT,     1,          GDK_Return, MOD_S   },
      { ACT_COMMIT,     0,          GDK_KP_Enter,     MOD_s   },
      { ACT_COMMIT,     1,          GDK_KP_Enter,     MOD_S   },
      { ACT_RCLICK,     0,          GDK_BackSpace,    MOD_0   },
      { ACT_ARROW,      2,          GDK_a,            MOD_csa },
      { ACT_ARROW,      3,          GDK_s,            MOD_csa },
      { ACT_A,    -1,         GDK_bracketleft, MOD_cs },
      { ACT_A,    1,          GDK_bracketright, MOD_cs},
      { ACT_B,    -1,         GDK_bracketleft, MOD_cS },
      { ACT_B,    -1,         GDK_braceleft,    MOD_cS  },
      { ACT_B,    1,          GDK_bracketright, MOD_cS},
      { ACT_B,    1,          GDK_braceright,   MOD_cS  },
      { ACT_CHANNEL,    CHN_IMAGE,  GDK_KP_1,   MOD_cS  },
      { ACT_CHANNEL,    CHN_IMAGE,  GDK_1,            MOD_cS  },
      { ACT_CHANNEL,    CHN_ALPHA,  GDK_KP_2,   MOD_cS  },
      { ACT_CHANNEL,    CHN_ALPHA,  GDK_2,            MOD_cS  },
      { ACT_CHANNEL,    CHN_SEL,    GDK_KP_3,   MOD_cS  },
      { ACT_CHANNEL,    CHN_SEL,    GDK_3,            MOD_cS  },
      { ACT_CHANNEL,    CHN_MASK,   GDK_KP_4,   MOD_cS  },
      { ACT_CHANNEL,    CHN_MASK,   GDK_4,            MOD_cS  },
      { ACT_VWZOOM,     0,          GDK_plus,   MOD_cS  },
      { ACT_VWZOOM,     0,          GDK_KP_Add, MOD_cS  },
      { ACT_VWZOOM,     -1,         GDK_minus,  MOD_cS  },
      { ACT_VWZOOM,     -1,         GDK_KP_Subtract, MOD_cS },
      { 0, 0, 0, 0 }
};

static guint main_keycodes[sizeof(main_keys) / sizeof(key_action)];

static void fill_keycodes()
{
      int i;

      for (i = 0; main_keys[i].action; i++)
      {
            main_keycodes[i] = keyval_key(main_keys[i].key);
      }
}

/* "Tool of last resort" for when shortcuts don't work */
static void rebind_keys()
{
      fill_keycodes();
#if GTK_MAJOR_VERSION > 1
      gtk_signal_emit_by_name(GTK_OBJECT(main_window), "keys_changed", NULL);
#endif
}

int wtf_pressed(GdkEventKey *event)
{
      key_action *ap = main_keys, *cmatch = NULL;
      guint *kcd = main_keycodes;
      guint realkey = real_key(event);
      guint lowkey = low_key(event);

      for (; ap->action; kcd++ , ap++)
      {
            /* Relevant modifiers should match first */
            if ((event->state & mod_bits[ap->mod & 0xF]) !=
                  mod_bits[ap->mod >> 4]) continue;
            /* Let keyval have priority; this is also a workaround for
             * GTK2 bug #136280 */
            if (lowkey == ap->key) break;
            /* Let keycodes match when keyvals don't */
            if (realkey == *kcd) cmatch = ap;
      }
      /* If we have only a keycode match */
      if (cmatch && !ap->action) ap = cmatch;
      /* Return 0 if no match */
      if (!ap->action) return (0);
      /* Return the matching action */
      return ((ap->action << 16) + (ap->mode + 0x8000));
}

int dock_focused()
{
      GtkWidget *focus = GTK_WINDOW(main_window)->focus_widget;
      return (focus && dock_area && gtk_widget_is_ancestor(focus, dock_area));
}

static int check_smart_menu_keys(GdkEventKey *event);

static gboolean handle_keypress(GtkWidget *widget, GdkEventKey *event,
      gpointer user_data)
{
      int act_m;

      if (dock_focused())
      {
            /* Builtin key handling disabled while dock has focus;
             * pressing Escape moves focus out of dock - to nowhere */
            if (event->keyval != GDK_Escape) return (FALSE);
            gtk_window_set_focus(GTK_WINDOW(main_window), NULL);
            act_m = ACTMOD_DUMMY;
      }
      else
      {
            act_m = wtf_pressed(event);
            if (!act_m) act_m = check_smart_menu_keys(event);
            if (!act_m) return (FALSE);
      }

#if GTK_MAJOR_VERSION == 1
      /* Return value alone doesn't stop GTK1 from running other handlers */
      gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), "key_press_event");
#endif

      if (act_m != ACTMOD_DUMMY)
            action_dispatch(act_m >> 16, (act_m & 0xFFFF) - 0x8000, 0, TRUE);
      return (TRUE);
}

static void draw_arrow(int mode)
{
      int i, xa1, xa2, ya1, ya2, minx, maxx, miny, maxy, w, h;
      double uvx, uvy;  // Line length & unit vector lengths
      int oldmode = mem_undo_opacity;


      if (!((tool_type == TOOL_LINE) && (line_status > LINE_NONE) &&
            ((line_x1 != line_x2) || (line_y1 != line_y2)))) return;

      // Calculate 2 coords for arrow corners
      uvy = sqrt((line_x1 - line_x2) * (line_x1 - line_x2) +
            (line_y1 - line_y2) * (line_y1 - line_y2));
      uvx = (line_x2 - line_x1) / uvy;
      uvy = (line_y2 - line_y1) / uvy;

      xa1 = rint(line_x1 + tool_flow * (uvx - uvy * 0.5));
      xa2 = rint(line_x1 + tool_flow * (uvx + uvy * 0.5));
      ya1 = rint(line_y1 + tool_flow * (uvy + uvx * 0.5));
      ya2 = rint(line_y1 + tool_flow * (uvy - uvx * 0.5));

// !!! Call this, or let undo engine do it?
//    mem_undo_prepare();
      pen_down = 0;
      tool_action(GDK_NOTHING, line_x1, line_y1, 1, 0);
      line_status = LINE_LINE;

      // Draw arrow lines & circles
      mem_undo_opacity = TRUE;
      f_circle(xa1, ya1, tool_size);
      f_circle(xa2, ya2, tool_size);
      tline(xa1, ya1, line_x1, line_y1, tool_size);
      tline(xa2, ya2, line_x1, line_y1, tool_size);

      if (mode == 3)
      {
            // Draw 3rd line and fill arrowhead
            tline(xa1, ya1, xa2, ya2, tool_size );
            poly_points = 0;
            poly_add(line_x1, line_y1);
            poly_add(xa1, ya1);
            poly_add(xa2, ya2);
            poly_paint();
            poly_points = 0;
      }
      mem_undo_opacity = oldmode;
      mem_undo_prepare();

      // Update screen areas
      minx = xa1 < xa2 ? xa1 : xa2;
      if (minx > line_x1) minx = line_x1;
      maxx = xa1 > xa2 ? xa1 : xa2;
      if (maxx < line_x1) maxx = line_x1;

      miny = ya1 < ya2 ? ya1 : ya2;
      if (miny > line_y1) miny = line_y1;
      maxy = ya1 > ya2 ? ya1 : ya2;
      if (maxy < line_y1) maxy = line_y1;

      i = (tool_size + 1) >> 1;
      minx -= i; miny -= i; maxx += i; maxy += i;

      w = maxx - minx + 1;
      h = maxy - miny + 1;

      update_stuff(UPD_IMGP);
      main_update_area(minx, miny, w, h);
      vw_update_area(minx, miny, w, h);
}

int check_for_changes()             // 1=STOP, 2=IGNORE, 10=ESCAPE, -10=NOT CHECKED
{
      int i = -10;
      char *warning = _("This canvas/palette contains changes that have not been saved.  Do you really want to lose these changes?");

      if ( mem_changed == 1 )
            i = alert_box( _("Warning"), warning, _("Cancel Operation"), _("Lose Changes"), NULL );

      return i;
}

void var_init()
{
      inilist *ilp;

      /* Load listed settings */
      for (ilp = ini_bool; ilp->name; ilp++)
            *(ilp->var) = inifile_get_gboolean(ilp->name, ilp->defv);
      for (ilp = ini_int; ilp->name; ilp++)
            *(ilp->var) = inifile_get_gint32(ilp->name, ilp->defv);
}

void string_init()
{
      char *cnames[NUM_CHANNELS + 1] =
            { _("Image"), _("Alpha"), _("Selection"), _("Mask"), NULL };
      int i;

      for (i = 0; i < NUM_CHANNELS + 1; i++)
            allchannames[i] = channames[i] = cnames[i];
      channames[CHN_IMAGE] = "";
}

static void toggle_dock(int state, int internal);

static gboolean delete_event( GtkWidget *widget, GdkEvent *event, gpointer data )
{
      inilist *ilp;
      int i = 2, j = 0;

      if ( layers_total == 0 )
            j = check_for_changes();
      else
            j = check_layers_for_changes();

      if ( j == -10 )
      {
            if ( inifile_get_gboolean( "exitToggle", FALSE ) )
                  i = alert_box( VERSION, _("Do you really want to quit?"), _("NO"), _("YES"), NULL );
      }
      else i = j;

      if ( i==2 )
      {
            toggle_dock(FALSE, TRUE);
            win_store_pos(main_window, "window");

            // Get rid of extra windows + remember positions
            delete_layers_window();

            toolbar_exit();               // Remember the toolbar settings

            /* Store listed settings */
            for (ilp = ini_bool; ilp->name; ilp++)
                  inifile_set_gboolean(ilp->name, *(ilp->var));
            for (ilp = ini_int; ilp->name; ilp++)
                  inifile_set_gint32(ilp->name, *(ilp->var));

            gtk_main_quit ();
            return FALSE;
      }
      else return TRUE;
}

#if GTK_MAJOR_VERSION == 2
gint canvas_scroll_gtk2( GtkWidget *widget, GdkEventScroll *event )
{
      if (inifile_get_gboolean( "scrollwheelZOOM", FALSE ))
      {
            scroll_wheel(event->x / can_zoom, event->y / can_zoom,
                  event->direction == GDK_SCROLL_DOWN ? -1 : 1);
            return TRUE;
      }
      if (event->state & _C) /* Convert up-down into left-right */
      {
            if (event->direction == GDK_SCROLL_UP)
                  event->direction = GDK_SCROLL_LEFT;
            else if (event->direction == GDK_SCROLL_DOWN)
                  event->direction = GDK_SCROLL_RIGHT;
      }
      /* Normal GTK+2 scrollwheel behaviour */
      return FALSE;
}
#endif


int grad_tool(int event, int x, int y, guint state, guint button)
{
      int i, j, *xx, *yy;
      double d, stroke;
      grad_info *grad = gradient + mem_channel;

      /* Handle stroke gradients */
      if (tool_type != TOOL_GRADIENT)
      {
            /* Not a gradient stroke */
            if (!mem_gradient || (grad->status != GRAD_NONE) ||
                  (grad->wmode == GRAD_MODE_NONE) ||
                  (event == GDK_BUTTON_RELEASE) || !button)
                  return (FALSE);

            /* Limit coordinates to canvas */
            x = x < 0 ? 0 : x >= mem_width ? mem_width - 1 : x;
            y = y < 0 ? 0 : y >= mem_height ? mem_height - 1 : y;

            /* Standing still */
            if ((tool_ox == x) && (tool_oy == y)) return (FALSE);
            if (!pen_down || (tool_type > TOOL_SPRAY)) /* Begin stroke */
            {
                  grad_path = grad->xv = grad->yv = 0.0;
            }
            else /* Continue stroke */
            {
                  i = x - tool_ox; j = y - tool_oy;
                  stroke = sqrt(i * i + j * j);
                  /* First step - anchor rear end */
                  if (grad_path == 0.0)
                  {
                        d = tool_size * 0.5 / stroke;
                        grad_x0 = tool_ox - i * d;
                        grad_y0 = tool_oy - j * d;
                  }
                  /* Scalar product */
                  d = (tool_ox - grad_x0) * (x - tool_ox) +
                        (tool_oy - grad_y0) * (y - tool_oy);
                  if (d < 0.0) /* Going backward - flip rear */
                  {
                        d = tool_size * 0.5 / stroke;
                        grad_x0 = x - i * d;
                        grad_y0 = y - j * d;
                        grad_path += tool_size + stroke;
                  }
                  else /* Going forward or sideways - drag rear */
                  {
                        stroke = sqrt((x - grad_x0) * (x - grad_x0) +
                              (y - grad_y0) * (y - grad_y0));
                        d = tool_size * 0.5 / stroke;
                        grad_x0 = x + (grad_x0 - x) * d;
                        grad_y0 = y + (grad_y0 - y) * d;
                        grad_path += stroke - tool_size * 0.5;
                  }
                  d = 2.0 / (double)tool_size;
                  grad->xv = (x - grad_x0) * d;
                  grad->yv = (y - grad_y0) * d;
            }
            return (FALSE); /* Let drawing tools run */
      }

      /* Left click sets points and picks them up again */
      if ((event == GDK_BUTTON_PRESS) && (button == 1))
      {
            /* Start anew */
            if (grad->status == GRAD_NONE)
            {
                  grad->x1 = grad->x2 = x;
                  grad->y1 = grad->y2 = y;
                  grad->status = GRAD_END;
                  grad_update(grad);
                  repaint_grad(1);
            }
            /* Place starting point */
            else if (grad->status == GRAD_START)
            {
                  grad->x1 = x;
                  grad->y1 = y;
                  grad->status = GRAD_DONE;
                  grad_update(grad);
                  if (grad_opacity) gtk_widget_queue_draw(drawing_canvas);
            }
            /* Place end point */
            else if (grad->status == GRAD_END)
            {
                  grad->x2 = x;
                  grad->y2 = y;
                  grad->status = GRAD_DONE;
                  grad_update(grad);
                  if (grad_opacity) gtk_widget_queue_draw(drawing_canvas);
            }
            /* Pick up nearest end */
            else if (grad->status == GRAD_DONE)
            {
                  if (!grad_opacity) repaint_grad(0);
                  i = (x - grad->x1) * (x - grad->x1) +
                        (y - grad->y1) * (y - grad->y1);
                  j = (x - grad->x2) * (x - grad->x2) +
                        (y - grad->y2) * (y - grad->y2);
                  if (i < j)
                  {
                        grad->x1 = x;
                        grad->y1 = y;
                        grad->status = GRAD_START;
                  }
                  else
                  {
                        grad->x2 = x;
                        grad->y2 = y;
                        grad->status = GRAD_END;
                  }
                  grad_update(grad);
                  if (grad_opacity)
                  {
                        gtk_widget_queue_draw(drawing_canvas);
                        while (gtk_events_pending())
                              gtk_main_iteration();
                  }
                  repaint_grad(1);
            }
      }

      /* Everything but left click is irrelevant when no gradient */
      else if (grad->status == GRAD_NONE);

      /* Right click deletes the gradient */
      else if (event == GDK_BUTTON_PRESS) /* button != 1 */
      {
            grad->status = GRAD_NONE;
            if (grad_opacity) gtk_widget_queue_draw(drawing_canvas);
            else repaint_grad(0);
            grad_update(grad);
      }

      /* Motion is irrelevant with gradient in place */
      else if (grad->status == GRAD_DONE);

      /* Motion drags points around */
      else if (event == GDK_MOTION_NOTIFY)
      {
            if (grad->status == GRAD_START) xx = &(grad->x1) , yy = &(grad->y1);
            else xx = &(grad->x2) , yy = &(grad->y2);
            if ((*xx != x) || (*yy != y))
            {
                  repaint_grad(0);
                  *xx = x; *yy = y;
                  grad_update(grad);
                  repaint_grad(1);
            }
      }

      /* Leave hides the dragged line */
      else if (event == GDK_LEAVE_NOTIFY) repaint_grad(0);

      return (TRUE);
}

static int get_bkg(int xc, int yc, int dclick);

/* Mouse event from button/motion on the canvas */
static void mouse_event(int event, int xc, int yc, guint state, guint button,
      gdouble pressure, int mflag, int dx, int dy)
{
      static int tool_fixx = -1, tool_fixy = -1;      // Fixate on axis
      GdkCursor *temp_cursor = NULL;
      GdkCursorType pointers[] = {GDK_TOP_LEFT_CORNER, GDK_TOP_RIGHT_CORNER,
            GDK_BOTTOM_LEFT_CORNER, GDK_BOTTOM_RIGHT_CORNER};
      int new_cursor;
      int i, pixel, x0, y0, x, y, ox, oy, tox = tool_ox, toy = tool_oy;
      int zoom = 1, scale = 1;


      /* !!! This uses the fact that zoom factor is either N or 1/N !!! */
      if (can_zoom < 1.0) zoom = rint(1.0 / can_zoom);
      else scale = rint(can_zoom);

      x0 = ((xc - margin_main_x) * zoom) / scale + dx;
      y0 = ((yc - margin_main_y) * zoom) / scale + dy;

      ox = x = x0 < 0 ? 0 : x0 >= mem_width ? mem_width - 1 : x0;
      oy = y = y0 < 0 ? 0 : y0 >= mem_height ? mem_height - 1 : y0;

      /* ****** Release-event-specific code ****** */
      if (event == GDK_BUTTON_RELEASE)
      {
            tint_mode[2] = 0;
            pen_down = 0;
            if ( col_reverse )
            {
                  col_reverse = FALSE;
                  mem_swap_cols(FALSE);
            }

            if (grad_tool(event, x0, y0, state, button)) return;

            if ((tool_type == TOOL_LINE) && (button == 1) &&
                  (line_status == LINE_START))
            {
                  line_status = LINE_LINE;
                  repaint_line(1);
            }

            if (((tool_type == TOOL_SELECT) || (tool_type == TOOL_POLYGON)) &&
                  (button == 1))
            {
                  if (marq_status == MARQUEE_SELECTING)
                        marq_status = MARQUEE_DONE;
                  if (marq_status == MARQUEE_PASTE_DRAG)
                        marq_status = MARQUEE_PASTE;
                  cursor_corner = -1;
            }

            // Finish off dragged polygon selection
            if ((tool_type == TOOL_POLYGON) && (poly_status == POLY_DRAGGING))
                  tool_action(event, x, y, button, pressure);

            mem_undo_prepare();
            update_menus();

            return;
      }

      /* ****** Common click/motion handling code ****** */

      if (!mflag) /* Coordinate fixation */
      {
            if ((tool_fixy < 0) && ((state & _CS) == _CS))
            {
                  tool_fixx = -1;
                  tool_fixy = y;
            }
            if ((tool_fixx < 0) && ((state & _CS) == _S))
                  tool_fixx = x;
            if (!(state & _S)) tool_fixx = tool_fixy = -1;
            if (!(state & _C)) tool_fixy = -1;
      }
      /* No use when moving cursor by keyboard */
      else if (event == GDK_MOTION_NOTIFY) tool_fixx = tool_fixy = -1;

      if (tool_fixx > 0) x = x0 = tool_fixx;
      if (tool_fixy > 0) y = y0 = tool_fixy;

      while ((state & _CS) == _C)   // Set colour A/B
      {
            int ab = button == 3; /* A for left, B for right */

            if (button == 2) /* Auto-dither */
            {
                  if ((mem_channel == CHN_IMAGE) && (mem_img_bpp == 3))
                        pressed_dither_A();
                  break;
            }
            if ((button != 1) && (button != 3)) break;
            /* Pick color from tracing image if possible */
            pixel = get_bkg(xc + dx * scale, yc + dy * scale, event == GDK_2BUTTON_PRESS);
            /* Otherwise, average brush or selection area on Ctrl+double click */
            while ((pixel < 0) && (event == GDK_2BUTTON_PRESS) && (MEM_BPP == 3))
            {
                  int x, y, w, h;

                  /* Have brush square */
                  if (!NO_PERIM(tool_type))
                  {
                        int ts2 = tool_size >> 1;
                        x = ox - ts2; y = oy - ts2;
                        w = h = tool_size;
                  }
                  /* Have selection marquee */
                  else if ((marq_status > MARQUEE_NONE) && (marq_status < MARQUEE_PASTE))
                  {
                        x = marq_x1 < marq_x2 ? marq_x1 : marq_x2;
                        y = marq_y1 < marq_y2 ? marq_y1 : marq_y2;
                        w = abs(marq_x2 - marq_x1) + 1;
                        h = abs(marq_y2 - marq_y1) + 1;
                  }
                  else break;
                  pixel = average_pixels(mem_img[CHN_IMAGE],
                        mem_width, mem_height, x, y, w, h);
                  break;
            }
            /* Failing that, just pick color from image */
            if (pixel < 0) pixel = get_pixel(ox, oy);

            if (mem_channel != CHN_IMAGE)
            {
                  if (channel_col_[ab][mem_channel] == pixel) break;
                  channel_col_[ab][mem_channel] = pixel;
            }
            else if (mem_img_bpp == 1)
            {
                  if (mem_col_[ab] == pixel) break;
                  mem_col_[ab] = pixel;
                  mem_col_24[ab] = mem_pal[pixel];
            }
            else
            {
                  png_color *col = mem_col_24 + ab;

                  if (PNG_2_INT(*col) == pixel) break;
                  col->red = INT_2_R(pixel);
                  col->green = INT_2_G(pixel);
                  col->blue = INT_2_B(pixel);
            }
            update_stuff(UPD_CAB);
            break;
      }

      if ((state & _CS) == _C); /* Done above */

      else if ((button == 2) || ((button == 3) && (state & _S)))
            set_zoom_centre(ox, oy);

      else if (grad_tool(event, x0, y0, state, button));

      /* Pure moves are handled elsewhere */
      else if (button) tool_action(event, x, y, button, pressure);

      if ((tool_type == TOOL_SELECT) || (tool_type == TOOL_POLYGON) ||
            (tool_type == TOOL_GRADIENT)) update_sel_bar();

      /* ****** Now to mouse-move-specific part ****** */

      if (event != GDK_MOTION_NOTIFY) return;

      if ( tool_type == TOOL_CLONE )
      {
            tool_ox = x;
            tool_oy = y;
      }

      if ( poly_status == POLY_SELECTING && button == 0 )
      {
            stretch_poly_line(x, y);
      }

      if ( tool_type == TOOL_SELECT || tool_type == TOOL_POLYGON )
      {
            if ( marq_status == MARQUEE_DONE )
            {
                  if (cursor_tool)
                  {
                        i = close_to(x, y);
                        if ( i!=cursor_corner ) // Stops excessive CPU/flickering
                        {
                               cursor_corner = i;
                               temp_cursor = gdk_cursor_new(pointers[i]);
                               gdk_window_set_cursor(drawing_canvas->window, temp_cursor);
                               gdk_cursor_destroy(temp_cursor);
                        }
                  }
                  else set_cursor();
            }
            if ( marq_status >= MARQUEE_PASTE )
            {
                  new_cursor = 0;         // Cursor = normal
                  if ( x>=marq_x1 && x<=marq_x2 && y>=marq_y1 && y<=marq_y2 )
                        new_cursor = 1;         // Cursor = 4 way arrow

                  if ( new_cursor != cursor_corner ) // Stops flickering on slow hardware
                  {
                        if (!cursor_tool || !new_cursor) set_cursor();
                        else gdk_window_set_cursor(drawing_canvas->window, move_cursor);
                        cursor_corner = new_cursor;
                  }
            }
      }
      update_xy_bar(x, y);

///   TOOL PERIMETER BOX UPDATES

      if (perim_status > 0) clear_perim();      // Remove old perimeter box

      if ((tool_type == TOOL_CLONE) && (button == 0) && (state & _C))
      {
            clone_x += tox - x;
            clone_y += toy - y;
      }

      if (tool_size * can_zoom > 4)
      {
            perim_x = x - (tool_size >> 1);
            perim_y = y - (tool_size >> 1);
            perim_s = tool_size;
            repaint_perim(NULL);                // Repaint 4 sides
      }

///   LINE UPDATES

      if ((tool_type == TOOL_LINE) && (line_status != LINE_NONE) &&
            ((line_x1 != x) || (line_y1 != y)))
      {
            repaint_line(0);
            line_x1 = x;
            line_y1 = y;
            repaint_line(1);
      }
}

static gboolean canvas_button(GtkWidget *widget, GdkEventButton *event)
{
      int pflag = event->type != GDK_BUTTON_RELEASE;
      gdouble pressure = 1.0;

      if (pflag) /* For button press events only */
      {
            /* Steal focus from dock window */
            if (dock_focused())
            {
                  gtk_window_set_focus(GTK_WINDOW(main_window), NULL);
                  return (TRUE);
            }

            if (!mem_img[CHN_IMAGE]) return (TRUE);

            if (tablet_working)
#if GTK_MAJOR_VERSION == 1
                  pressure = event->pressure;
#endif
#if GTK_MAJOR_VERSION == 2
                  gdk_event_get_axis((GdkEvent *)event, GDK_AXIS_PRESSURE,
                        &pressure);
#endif
      }

      mouse_event(event->type, event->x, event->y, event->state, event->button,
            pressure, unreal_move & 1, 0, 0);

      return (pflag);
}

// Mouse enters the canvas
static gint canvas_enter(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
{
      /* !!! Have to skip grab/ungrab related events if doing something */
//    if (event->mode != GDK_CROSSING_NORMAL) return (TRUE);

      return TRUE;
}

static gint canvas_left(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
{
      /* Skip grab/ungrab related events */
      if (event->mode != GDK_CROSSING_NORMAL) return (FALSE);

      /* Only do this if we have an image */
      if (!mem_img[CHN_IMAGE]) return (FALSE);
      if ( status_on[STATUS_CURSORXY] )
            gtk_label_set_text( GTK_LABEL(label_bar[STATUS_CURSORXY]), "" );
      if ( status_on[STATUS_PIXELRGB] )
            gtk_label_set_text( GTK_LABEL(label_bar[STATUS_PIXELRGB]), "" );
      if (perim_status > 0) clear_perim();

      if (grad_tool(GDK_LEAVE_NOTIFY, 0, 0, 0, 0)) return (FALSE);

      if (((tool_type == TOOL_LINE) && (line_status != LINE_NONE)) ||
            ((tool_type == TOOL_POLYGON) && (poly_status == POLY_SELECTING)))
            repaint_line(0);

      return (FALSE);
}

static int async_bk;

static void render_background(unsigned char *rgb, int x0, int y0, int wid, int hgt, int fwid)
{
      int i, j, k, scale, dx, dy, step, ii, jj, ii0, px, py;
      int xwid = 0, xhgt = 0, wid3 = wid * 3;

      /* !!! This uses the fact that zoom factor is either N or 1/N !!! */
      if (!chequers_optimize) step = 8 , async_bk = TRUE;
      else if (can_zoom < 1.0) step = 6;
      else
      {
            scale = rint(can_zoom);
            step = scale < 4 ? 6 : scale == 4 ? 8 : scale;
      }
      dx = x0 % step;
      dy = y0 % step;
      
      py = (x0 / step + y0 / step) & 1;
      if (hgt + dy > step)
      {
            jj = step - dy;
            xhgt = (hgt + dy) % step;
            if (!xhgt) xhgt = step;
            hgt -= xhgt;
            xhgt -= step;
      }
      else jj = hgt--;
      if (wid + dx > step)
      {
            ii0 = step - dx;
            xwid = (wid + dx) % step;
            if (!xwid) xwid = step;
            wid -= xwid;
            xwid -= step;
      }
      else ii0 = wid--;

      for (j = 0; ; jj += step)
      {
            if (j >= hgt)
            {
                  if (j > hgt) break;
                  jj += xhgt;
            }
            px = py;
            ii = ii0;
            for (i = 0; ; ii += step)
            {
                  if (i >= wid)
                  {
                        if (i > wid) break;
                        ii += xwid;
                  }
                  k = (ii - i) * 3;
                  memset(rgb, greyz[px], k);
                  rgb += k;
                  px ^= 1;
                  i = ii;
            }
            rgb += fwid - wid3;
            for(j++; j < jj; j++)
            {
                  memcpy(rgb, rgb - fwid, wid3);
                  rgb += fwid;
            }
            py ^= 1;
      }
}

/// TRACING IMAGE

unsigned char *bkg_rgb;
int bkg_x, bkg_y, bkg_w, bkg_h, bkg_scale, bkg_flag;

int config_bkg(int src)
{
      image_info *img;
      int l;

      if (!src) return (TRUE); // No change

      // Remove old
      free(bkg_rgb);
      bkg_rgb = NULL;
      bkg_w = bkg_h = 0;

      img = src == 2 ? &mem_image : src == 3 ? &mem_clip : NULL;
      if (!img || !img->img[CHN_IMAGE]) return (TRUE); // No image

      l = img->width * img->height;
      bkg_rgb = malloc(l * 3);
      if (!bkg_rgb) return (FALSE);

      if (img->bpp == 1)
      {
            unsigned char *src = img->img[CHN_IMAGE], *dest = bkg_rgb;
            int i, j;

            for (i = 0; i < l; i++ , dest += 3)
            {
                  j = *src++;
                  dest[0] = mem_pal[j].red;
                  dest[1] = mem_pal[j].green;
                  dest[2] = mem_pal[j].blue;
            }
      }
      else memcpy(bkg_rgb, img->img[CHN_IMAGE], l * 3);
      bkg_w = img->width;
      bkg_h = img->height;
      return (TRUE);
}

static void render_bkg(rgbcontext *ctx)
{
      unsigned char *src, *dest;
      int i, wx0, wy0, wx1, wy1, x0, x, y, ty, w3, l3, d0, dd, adj, bs;
      int zoom = 1, scale = 1;


      /* !!! This uses the fact that zoom factor is either N or 1/N !!! */
      if (can_zoom < 1.0) zoom = rint(1.0 / can_zoom);
      else scale = rint(can_zoom);

      bs = bkg_scale * zoom;
      adj = bs - scale > 0 ? bs - scale : 0;
      wx0 = floor_div(bkg_x * scale + adj, bs) + margin_main_x;
      wy0 = floor_div(bkg_y * scale + adj, bs) + margin_main_y;
      wx1 = floor_div((bkg_x + bkg_w) * scale + adj, bs) + margin_main_x;
      wy1 = floor_div((bkg_y + bkg_h) * scale + adj, bs) + margin_main_y;
      if ((wx0 >= ctx->x1) || (wy0 >= ctx->y1) ||
            (wx1 < ctx->x0) || (wy1 < ctx->y0)) return;
      async_bk |= scale > 1;

      dest = ctx->rgb;
      w3 = (ctx->x1 - ctx->x0) * 3;
      if (wx0 < ctx->x0) wx0 = ctx->x0;
      else dest += (wx0 - ctx->x0) * 3;
      if (wy0 < ctx->y0) wy0 = ctx->y0;
      else dest += (wy0 - ctx->y0) * w3;
      if (ctx->x1 < wx1) wx1 = ctx->x1;
      if (ctx->y1 < wy1) wy1 = ctx->y1;
      l3 = (wx1 - wx0) * 3;

      d0 = (wx0 - margin_main_x) * bs;
      x0 = floor_div(d0, scale);
      d0 -= x0 * scale;
      x0 -= bkg_x;

      for (ty = -1 , i = wy0; i < wy1; i++)
      {
            y = floor_div((i - margin_main_y) * bs, scale) - bkg_y;
            if (y != ty)
            {
                  src = bkg_rgb + (y * bkg_w + x0) * 3;
                  for (dd = d0 , x = wx0; x < wx1; x++ , dest += 3)
                  {
                        dest[0] = src[0];
                        dest[1] = src[1];
                        dest[2] = src[2];
                        for (dd += bs; dd >= scale; dd -= scale)
                              src += 3;
                  }
                  ty = y;
                  dest += w3 - l3;
            }
            else
            {
                  memcpy(dest, dest - w3, l3);
                  dest += w3;
            }
      }
}

static int get_bkg(int xc, int yc, int dclick)
{
      int xb, yb, xi, yi, x, scale;

      /* No background / not RGB / wrong scale */
      if (!bkg_flag || (mem_channel != CHN_IMAGE) || (mem_img_bpp != 3) ||
            (can_zoom < 1.0)) return (-1);
      scale = rint(can_zoom);
      xi = floor_div(xc - margin_main_x, scale);
      yi = floor_div(yc - margin_main_y, scale);
      /* Inside image */
      if ((xi >= 0) && (xi < mem_width) && (yi >= 0) && (yi < mem_height))
      {
            /* Pixel must be transparent */
            x = mem_width * yi + xi;
            if (mem_img[CHN_ALPHA] && !channel_dis[CHN_ALPHA] &&
                  !mem_img[CHN_ALPHA][x]); // Alpha transparency
            else if (mem_xpm_trans < 0) return (-1);
            else if (x *= 3 , MEM_2_INT(mem_img[CHN_IMAGE], x) !=
                  PNG_2_INT(mem_pal[mem_xpm_trans])) return (-1);

            /* Double click averages background under image pixel */
            if (dclick) return (average_pixels(bkg_rgb, bkg_w, bkg_h,
                  xi * bkg_scale - bkg_x, yi * bkg_scale - bkg_y,
                  bkg_scale, bkg_scale));
      }
      xb = floor_div((xc - margin_main_x) * bkg_scale, scale) - bkg_x;
      yb = floor_div((yc - margin_main_y) * bkg_scale, scale) - bkg_y;
      /* Outside of background */
      if ((xb < 0) || (xb >= bkg_w) || (yb < 0) || (yb >= bkg_h)) return (-1);
      x = (bkg_w * yb + xb) * 3;
      return (MEM_2_INT(bkg_rgb, x));
}

/* This is for a faster way to pass parameters into render_row() */
01834 typedef struct {
      int dx;
      int width;
      int xwid;
      int zoom;
      int scale;
      int mw;
      int opac;
      int xpm;
      int bpp;
      png_color *pal;
} renderstate;

static renderstate rr;

void setup_row(int x0, int width, double czoom, int mw, int xpm, int opac,
      int bpp, png_color *pal)
{
      /* Horizontal zoom */
      /* !!! This uses the fact that zoom factor is either N or 1/N !!! */
      if (czoom <= 1.0)
      {
            rr.zoom = rint(1.0 / czoom);
            rr.scale = 1;
            x0 = 0;
      }
      else
      {
            rr.zoom = 1;
            rr.scale = rint(czoom);
            x0 %= rr.scale;
      }
      if (width + x0 > rr.scale)
      {
            rr.dx = rr.scale - x0;
            x0 = (width + x0) % rr.scale;
            if (!x0) x0 = rr.scale;
            width -= x0;
            rr.xwid = x0 - rr.scale;
      }
      else
      {
            rr.dx = width--;
            rr.xwid = 0;
      }
      rr.width = width;
      rr.mw = mw;

      if ((xpm > -1) && (bpp == 3)) xpm = PNG_2_INT(pal[xpm]);
      rr.xpm = xpm;
      rr.opac = opac;

      rr.bpp = bpp;
      rr.pal = pal;
}

void render_row(unsigned char *rgb, chanlist base_img, int x, int y,
      chanlist xtra_img)
{
      int alpha_blend = !overlay_alpha;
      unsigned char *src = NULL, *dest, *alpha = NULL, px, beta = 255;
      int i, j, k, ii, ds = rr.zoom * 3, da = 0;
      int w_bpp = rr.bpp, w_xpm = rr.xpm;

      if (xtra_img)
      {
            src = xtra_img[CHN_IMAGE];
            alpha = xtra_img[CHN_ALPHA];
      }
      if (channel_dis[CHN_ALPHA]) alpha = &beta; /* Ignore alpha if disabled */
      if (!src) src = base_img[CHN_IMAGE] + (rr.mw * y + x) * rr.bpp;
      if (!alpha) alpha = base_img[CHN_ALPHA] ? base_img[CHN_ALPHA] +
            rr.mw * y + x : &beta;
      if (alpha != &beta) da = rr.zoom;
      dest = rgb;
      ii = rr.dx;

      if (hide_image) /* Substitute non-transparent "image overlay" colour */
      {
            w_bpp = 3;
            w_xpm = -1;
            ds = 0;
            src = channel_rgb[CHN_IMAGE];
      }
      if (!da && (w_xpm < 0) && (rr.opac == 255)) alpha_blend = FALSE;

      /* Indexed fully opaque */
      if ((w_bpp == 1) && !alpha_blend)
      {
            for (i = 0; ; ii += rr.scale)
            {
                  if (i >= rr.width)
                  {
                        if (i > rr.width) break;
                        ii += rr.xwid;
                  }
                  px = *src;
                  src += rr.zoom;
                  for(; i < ii; i++)
                  {
                        dest[0] = rr.pal[px].red;
                        dest[1] = rr.pal[px].green;
                        dest[2] = rr.pal[px].blue;
                        dest += 3;
                  }
            }
      }

      /* Indexed transparent */
      else if (w_bpp == 1)
      {
            for (i = 0; ; ii += rr.scale , alpha += da)
            {
                  if (i >= rr.width)
                  {
                        if (i > rr.width) break;
                        ii += rr.xwid;
                  }
                  px = *src;
                  src += rr.zoom;
                  if (!*alpha || (px == w_xpm))
                  {
                        dest += (ii - i) * 3;
                        i = ii;
                        continue;
                  }
rr2_as:
                  if (rr.opac == 255)
                  {
                        dest[0] = rr.pal[px].red;
                        dest[1] = rr.pal[px].green;
                        dest[2] = rr.pal[px].blue;
                  }
                  else
                  {
                        j = 255 * dest[0] + rr.opac * (rr.pal[px].red - dest[0]);
                        dest[0] = (j + (j >> 8) + 1) >> 8;
                        j = 255 * dest[1] + rr.opac * (rr.pal[px].green - dest[1]);
                        dest[1] = (j + (j >> 8) + 1) >> 8;
                        j = 255 * dest[2] + rr.opac * (rr.pal[px].blue - dest[2]);
                        dest[2] = (j + (j >> 8) + 1) >> 8;
                  }
rr2_s:
                  dest += 3;
                  if (++i >= ii) continue;
                  if (async_bk) goto rr2_as;
                  dest[0] = *(dest - 3);
                  dest[1] = *(dest - 2);
                  dest[2] = *(dest - 1);
                  goto rr2_s;
            }
      }

      /* RGB fully opaque */
      else if (!alpha_blend)
      {
            for (i = 0; ; ii += rr.scale , src += ds)
            {
                  if (i >= rr.width)
                  {
                        if (i > rr.width) break;
                        ii += rr.xwid;
                  }
                  for(; i < ii; i++)
                  {
                        dest[0] = src[0];
                        dest[1] = src[1];
                        dest[2] = src[2];
                        dest += 3;
                  }
            }
      }

      /* RGB transparent */
      else
      {
            for (i = 0; ; ii += rr.scale , src += ds , alpha += da)
            {
                  if (i >= rr.width)
                  {
                        if (i > rr.width) break;
                        ii += rr.xwid;
                  }
                  if (!*alpha || (MEM_2_INT(src, 0) == w_xpm))
                  {
                        dest += (ii - i) * 3;
                        i = ii;
                        continue;
                  }
                  k = rr.opac * alpha[0];
                  k = (k + (k >> 8) + 1) >> 8;
rr4_as:
                  if (k == 255)
                  {
                        dest[0] = src[0];
                        dest[1] = src[1];
                        dest[2] = src[2];
                  }
                  else
                  {
                        j = 255 * dest[0] + k * (src[0] - dest[0]);
                        dest[0] = (j + (j >> 8) + 1) >> 8;
                        j = 255 * dest[1] + k * (src[1] - dest[1]);
                        dest[1] = (j + (j >> 8) + 1) >> 8;
                        j = 255 * dest[2] + k * (src[2] - dest[2]);
                        dest[2] = (j + (j >> 8) + 1) >> 8;
                  }
rr4_s:
                  dest += 3;
                  if (++i >= ii) continue;
                  if (async_bk) goto rr4_as;
                  dest[0] = *(dest - 3);
                  dest[1] = *(dest - 2);
                  dest[2] = *(dest - 1);
                  goto rr4_s;
            }
      }
}

void overlay_row(unsigned char *rgb, chanlist base_img, int x, int y,
      chanlist xtra_img)
{
      unsigned char *alpha, *sel, *mask, *dest;
      int i, j, k, ii, dw, opA, opS, opM, t0, t1, t2, t3;

      if (xtra_img)
      {
            alpha = xtra_img[CHN_ALPHA];
            sel = xtra_img[CHN_SEL];
            mask = xtra_img[CHN_MASK];
      }
      else alpha = sel = mask = NULL;
      j = rr.mw * y + x;
      if (!alpha && base_img[CHN_ALPHA]) alpha = base_img[CHN_ALPHA] + j;
      if (!sel && base_img[CHN_SEL]) sel = base_img[CHN_SEL] + j;
      if (!mask && base_img[CHN_MASK]) mask = base_img[CHN_MASK] + j;

      /* Prepare channel weights (256-based) */
      k = hide_image ? 256 : 256 - channel_opacity[CHN_IMAGE] -
            (channel_opacity[CHN_IMAGE] >> 7);
      opA = alpha && overlay_alpha && !channel_dis[CHN_ALPHA] ?
            channel_opacity[CHN_ALPHA] : 0;
      opS = sel && !channel_dis[CHN_SEL] ? channel_opacity[CHN_SEL] : 0;
      opM = mask && !channel_dis[CHN_MASK] ? channel_opacity[CHN_MASK] : 0;

      /* Nothing to do - don't waste time then */
      j = opA + opS + opM;
      if (!k || !j) return;

      opA = (k * opA) / j;
      opS = (k * opS) / j;
      opM = (k * opM) / j;
      if (!(opA + opS + opM)) return;

      dest = rgb;
      ii = rr.dx;
      for (i = dw = 0; ; ii += rr.scale , dw += rr.zoom)
      {
            if (i >= rr.width)
            {
                  if (i > rr.width) break;
                  ii += rr.xwid;
            }
            t0 = t1 = t2 = t3 = 0;
            if (opA)
            {
                  j = opA * (alpha[dw] ^ channel_inv[CHN_ALPHA]);
                  t0 += j;
                  t1 += j * channel_rgb[CHN_ALPHA][0];
                  t2 += j * channel_rgb[CHN_ALPHA][1];
                  t3 += j * channel_rgb[CHN_ALPHA][2];
            }
            if (opS)
            {
                  j = opS * (sel[dw] ^ channel_inv[CHN_SEL]);
                  t0 += j;
                  t1 += j * channel_rgb[CHN_SEL][0];
                  t2 += j * channel_rgb[CHN_SEL][1];
                  t3 += j * channel_rgb[CHN_SEL][2];
            }
            if (opM)
            {
                  j = opM * (mask[dw] ^ channel_inv[CHN_MASK]);
                  t0 += j;
                  t1 += j * channel_rgb[CHN_MASK][0];
                  t2 += j * channel_rgb[CHN_MASK][1];
                  t3 += j * channel_rgb[CHN_MASK][2];
            }
            j = (256 * 255) - t0;
or_as:
            k = t1 + j * dest[0];
            dest[0] = (k + (k >> 8) + 0x100) >> 16;
            k = t2 + j * dest[1];
            dest[1] = (k + (k >> 8) + 0x100) >> 16;
            k = t3 + j * dest[2];
            dest[2] = (k + (k >> 8) + 0x100) >> 16;
or_s:
            dest += 3;
            if (++i >= ii) continue;
            if (async_bk) goto or_as;
            dest[0] = *(dest - 3);
            dest[1] = *(dest - 2);
            dest[2] = *(dest - 1);
            goto or_s;
      }
}

/* Specialized renderer for irregular overlays */
void overlay_preview(unsigned char *rgb, unsigned char *map, int col, int opacity)
{
      unsigned char *dest, crgb[3] = {INT_2_R(col), INT_2_G(col), INT_2_B(col)};
      int i, j, k, ii, dw;

      dest = rgb;
      ii = rr.dx;
      for (i = dw = 0; ; ii += rr.scale , dw += rr.zoom)
      {
            if (i >= rr.width)
            {
                  if (i > rr.width) break;
                  ii += rr.xwid;
            }
            k = opacity * map[dw];
            k = (k + (k >> 8) + 1) >> 8;
op_as:
            j = 255 * dest[0] + k * (crgb[0] - dest[0]);
            dest[0] = (j + (j >> 8) + 1) >> 8;
            j = 255 * dest[1] + k * (crgb[1] - dest[1]);
            dest[1] = (j + (j >> 8) + 1) >> 8;
            j = 255 * dest[2] + k * (crgb[2] - dest[2]);
            dest[2] = (j + (j >> 8) + 1) >> 8;
op_s:
            dest += 3;
            if (++i >= ii) continue;
            if (async_bk) goto op_as;
            dest[0] = *(dest - 3);
            dest[1] = *(dest - 2);
            dest[2] = *(dest - 1);
            goto op_s;
      }
}

02176 typedef struct {
      unsigned char *wmask, *gmask, *walpha, *galpha, *talpha;
      unsigned char *wimg, *gimg, *rgb;
      int opac, len, bpp;
} grad_render_state;

static unsigned char *init_grad_render(grad_render_state *g, int len,
      chanlist tlist)
{
      int opac = 0, i1, i2, bpp = MEM_BPP;
      unsigned char *gstore, *tmp;

// !!! Only the "slow path" for now
      if (gradient[mem_channel].status != GRAD_DONE) return (NULL);

      if (!IS_INDEXED) opac = grad_opacity;

      i1 = (mem_channel == CHN_IMAGE) && RGBA_mode && mem_img[CHN_ALPHA] ? 2 : 0;
      i2 = !opac && (grad_opacity < 255) ? 3 : 0;

      gstore = malloc((2 + 2 * bpp + i1 + i2) * len);
      if (!gstore) return (NULL);
      memset(g, 0, sizeof(grad_render_state));
      g->opac = opac;
      g->len = len;
      g->bpp = bpp;

      g->wmask = gstore;                  /* Mask */
      g->gmask = tmp = gstore + len;            /* Gradient opacity */
      g->gimg = tmp = tmp + len;          /* Gradient image */
      g->wimg = tmp = tmp + bpp * len;    /* Resulting image */
      tlist[mem_channel] = g->wimg;
      if (i2) /* Indexed to RGB */
      {
            g->rgb = tmp + (bpp + i1) * len;
            tlist[CHN_IMAGE] = g->rgb;
      }
      if (i1) /* Coupled alpha */
      {
            g->galpha = tmp = tmp + bpp * len;  /* Gradient alpha */
            g->walpha = tmp + len;              /* Resulting alpha */
            g->talpha = g->galpha;              /* Transient alpha */
            tlist[CHN_ALPHA] = g->walpha;
      }
      return (gstore);
}

static void grad_render(int start, int step, int cnt, int x, int y,
      unsigned char *mask0, grad_render_state *g)
{
      int l = mem_width * y + x, li = l * mem_img_bpp;
      unsigned char *tmp = mem_img[mem_channel] + l * g->bpp;

      prep_mask(start, step, cnt, g->wmask, mask0, mem_img[CHN_IMAGE] + li);
      if (!g->opac) memset(g->gmask, 255, g->len);

      prep_grad(start, step, cnt, x, y, g->wmask, g->gmask, g->gimg, g->galpha);
      if (g->walpha) memcpy(g->walpha, mem_img[CHN_ALPHA] + l, g->len);

      process_mask(start, step, cnt, g->wmask, g->walpha, mem_img[CHN_ALPHA] + l,
            g->talpha, g->gmask, g->opac, channel_dis[CHN_ALPHA]);

      memcpy(g->wimg, tmp, g->len * g->bpp);
      process_img(start, step, cnt, g->wmask, g->wimg, tmp, g->gimg, g->bpp,
            g->opac ? g->bpp : 0);

      if (g->rgb) blend_indexed(start, step, cnt, g->rgb, mem_img[CHN_IMAGE] + l,
            g->wimg ? g->wimg : mem_img[CHN_IMAGE] + l, mem_img[CHN_ALPHA] + l,
            g->walpha, grad_opacity);
}

02247 typedef struct {
      chanlist tlist;         // Channel overrides
      unsigned char *mask0;   // Active mask channel
      int px2, py2;           // Clipped area position
      int pw2, ph2;           // Clipped area size
      int dx;                 // Image-space X offset
      int lx;                 // Allocated row length
      int pww;          // Logical row length
      int zoom;         // Decimation factor
      int scale;        // Replication factor
      int lop;          // Base opacity
      int xpm;          // Transparent color
} main_render_state;

02261 typedef struct {
      unsigned char *pvi;     // Temp image row
      unsigned char *pvm;     // Temp mask row
} xform_render_state;

02266 typedef struct {
      chanlist tlist;         // Channel overrides for rendering clipboard
      unsigned char *clip_image;    // Pasted into current channel
      unsigned char *clip_alpha;    // Pasted into alpha channel
      unsigned char *t_alpha;       // Fake pasted alpha
      unsigned char *pix, *alpha;   // Destinations for the above
      unsigned char *mask, *wmask;  // Temp mask: one we use, other we init
      unsigned char *mask0;         // Image mask channel to use
      int opacity, bpp; // Just that
      int pixf;         // Flag: need current channel override filled
      int dx;                 // Memory-space X offset
      int lx;                 // Allocated row length
      int pww;          // Logical row length
} paste_render_state;

/* !!! This function copies existing override set to build its own modified
 * !!! one, so override set must not be changed after calling it */
static unsigned char *init_paste_render(paste_render_state *p,
      main_render_state *r, unsigned char *xmask)
{
      unsigned char *temp, *tmp;
      int i, x, y, w, h, mx, my, ddx, bpp, scale = r->scale, zoom = r->zoom;
      int ti = 0, tm = 0, ta = 0, fa = 0;

      /* Clip paste area to update area */
      x = (marq_x1 * scale + zoom - 1) / zoom;
      y = (marq_y1 * scale + zoom - 1) / zoom;
      if (x < r->px2) x = r->px2;
      if (y < r->py2) y = r->py2;
      w = (marq_x2 * scale) / zoom + scale;
      h = (marq_y2 * scale) / zoom + scale;
      mx = r->px2 + r->pw2;
      w = (w < mx ? w : mx) - x;
      my = r->py2 + r->ph2;
      h = (h < my ? h : my) - y;
      if ((w <= 0) || (h <= 0)) return (NULL);

      memset(p, 0, sizeof(paste_render_state));
      memcpy(p->tlist, r->tlist, sizeof(chanlist));

// !!! Store area dimensions somewhere for other functions' use
//    xywh[0] = x;
//    xywh[1] = y;
//    xywh[2] = w;
//    xywh[3] = h;

      /* Setup row position and size */
      p->dx = (x * zoom) / scale;
      if (zoom > 1) p->lx = (w - 1) * zoom + 1 , p->pww = w;
      else p->lx = p->pww = (x + w - 1) / scale - p->dx + 1;

      /* Decide what goes where */
      if ((mem_channel == CHN_IMAGE) && !channel_dis[CHN_ALPHA])
      {
            p->clip_alpha = mem_clip_alpha;
            if (mem_img[CHN_ALPHA])
            {
                  if (!mem_clip_alpha && RGBA_mode)
                        fa = 1; /* Need fake alpha */
                  if (mem_clip_alpha || fa)
                        ta = 1; /* Need temp alpha */
            }
      }
      p->clip_image = mem_clipboard;
      ddx = p->dx - r->dx;

      /* Allocate temp area */
      bpp = p->bpp = MEM_BPP;
      tm = !xmask; /* Need temp mask if not have one ready */
      ti = p->clip_image && !p->tlist[mem_channel]; /* Same for temp image */
      i = r->lx * (ti * bpp + ta) + p->lx * (tm + fa);
      temp = tmp = malloc(i);
      if (!temp) return (NULL);

      /* Setup "image" (current) channel override */
      if (ti) p->tlist[mem_channel] = tmp , tmp += r->lx * bpp;
      p->pix = p->tlist[mem_channel] + ddx * bpp;
      p->pixf = ti; /* Need it prefilled if no override data incoming */

      /* Setup alpha channel override */
      if (ta)
      {
            p->tlist[CHN_ALPHA] = tmp;
            p->alpha = tmp + ddx;
            tmp += r->lx;
      }

      /* Setup mask */
      if (mem_channel <= CHN_ALPHA) p->mask0 = r->mask0;
      if (tm) p->mask = p->wmask = tmp , tmp += p->lx;
      else
      {
            p->mask = xmask + ddx;
            if (r->mask0 != p->mask0)
            /* Mask has wrong data - reuse memory but refill values */
                  p->wmask = xmask + ddx;
      }

      /* Setup fake alpha */
      if (fa)
      {
            p->t_alpha = tmp;
            memset(tmp, channel_col_A[CHN_ALPHA], p->lx);
      }

      /* Setup opacity mode */
      if (!IS_INDEXED) p->opacity = tool_opacity;

      return (temp);
}

static void paste_render(int start, int step, int y, paste_render_state *p)
{
      int ld = mem_width * y + p->dx;
      int dc = mem_clip_w * (y - marq_y1) + p->dx - marq_x1;
      int bpp = p->bpp;
      int cnt = p->pww;

      if (p->wmask) prep_mask(start, step, cnt, p->wmask, p->mask0 ?
            p->mask0 + ld : NULL, mem_img[CHN_IMAGE] + ld * mem_img_bpp);
      process_mask(start, step, cnt, p->mask, p->alpha, mem_img[CHN_ALPHA] + ld,
            p->clip_alpha ? p->clip_alpha + dc : p->t_alpha,
            mem_clip_mask ? mem_clip_mask + dc : NULL, p->opacity, 0);
      if (!p->pixf) /* Fill just the underlying part */
            memcpy(p->pix, mem_img[mem_channel] + ld * bpp, p->lx * bpp);
      process_img(start, step, cnt, p->mask, p->pix, mem_img[mem_channel] + ld * bpp,
            mem_clipboard + dc * mem_clip_bpp, mem_clip_bpp, p->opacity ? bpp : 0);
}

static int main_render_rgb(unsigned char *rgb, int x, int y, int w, int h, int pw)
{
      main_render_state r;
      unsigned char **tlist = r.tlist;
      int j, jj, j0, l, pw23;
      unsigned char *xtemp = NULL;
      xform_render_state xrstate;
      unsigned char *cstemp = NULL;
      unsigned char *gtemp = NULL;
      grad_render_state grstate;
      unsigned char *ptemp = NULL;
      paste_render_state prstate;


      memset(&r, 0, sizeof(r));

      /* !!! This uses the fact that zoom factor is either N or 1/N !!! */
      r.zoom = r.scale = 1;
      if (can_zoom < 1.0) r.zoom = rint(1.0 / can_zoom);
      else r.scale = rint(can_zoom);

      r.px2 = x; r.py2 = y;
      r.pw2 = w; r.ph2 = h;

      if (!channel_dis[CHN_MASK]) r.mask0 = mem_img[CHN_MASK];

      r.xpm = mem_xpm_trans;
      r.lop = 255;
      if (show_layers_main && layers_total && layer_selected)
            r.lop = (layer_table[layer_selected].opacity * 255 + 50) / 100;

      /* Setup row position and size */
      r.dx = (r.px2 * r.zoom) / r.scale;
      if (r.zoom > 1) r.lx = (r.pw2 - 1) * r.zoom + 1 , r.pww = r.pw2;
      else r.lx = r.pww = (r.px2 + r.pw2 - 1) / r.scale - r.dx + 1;

      /* Color transform preview */
      if (mem_preview && (mem_img_bpp == 3))
      {
            xtemp = xrstate.pvm = malloc(r.lx * 4);
            if (xtemp) r.tlist[CHN_IMAGE] = xrstate.pvi = xtemp + r.lx;
      }

      /* Color selective mode preview */
      else if (csel_overlay) cstemp = malloc(r.lx);

      /* Gradient preview */
      else if ((tool_type == TOOL_GRADIENT) && grad_opacity)
      {
            if (mem_channel > CHN_ALPHA) r.mask0 = NULL;
            gtemp = init_grad_render(&grstate, r.lx, r.tlist);
      }

      /* Paste preview - can only coexist with transform */
      if (show_paste && (marq_status >= MARQUEE_PASTE) && !cstemp && !gtemp)
            ptemp = init_paste_render(&prstate, &r, xtemp ? xrstate.pvm : NULL);

      /* Start rendering */
      setup_row(r.px2, r.pw2, can_zoom, mem_width, r.xpm, r.lop,
            gtemp && grstate.rgb ? 3 : mem_img_bpp, mem_pal);
      j0 = -1; pw *= 3; pw23 = r.pw2 * 3;
      for (jj = 0; jj < r.ph2; jj++ , rgb += pw)
      {
            j = ((r.py2 + jj) * r.zoom) / r.scale;
            if (j != j0)
            {
                  j0 = j;
                  l = mem_width * j + r.dx;
                  tlist = r.tlist; /* Default override */

                  /* Color transform preview */
                  if (xtemp)
                  {
                        prep_mask(0, r.zoom, r.pww, xrstate.pvm,
                              r.mask0 ? r.mask0 + l : NULL,
                              mem_img[CHN_IMAGE] + l * 3);
                        do_transform(0, r.zoom, r.pww, xrstate.pvm,
                              xrstate.pvi, mem_img[CHN_IMAGE] + l * 3);
                  }
                  /* Color selective mode preview */
                  else if (cstemp)
                  {
                        memset(cstemp, 0, r.lx);
                        csel_scan(0, r.zoom, r.pww, cstemp,
                              mem_img[CHN_IMAGE] + l * mem_img_bpp,
                              csel_data);
                  }
                  /* Gradient preview */
                  else if (gtemp) grad_render(0, r.zoom, r.pww, r.dx, j,
                        r.mask0 ? r.mask0 + l : NULL, &grstate);

                  /* Paste preview - should be after transform */
                  if (ptemp && (j >= marq_y1) && (j <= marq_y2))
                  {
                        tlist = prstate.tlist; /* Paste-area override */
                        if (prstate.alpha) memcpy(tlist[CHN_ALPHA],
                              mem_img[CHN_ALPHA] + l, r.lx);
                        if (prstate.pixf) memcpy(tlist[mem_channel],
                              mem_img[mem_channel] + l * prstate.bpp,
                              r.lx * prstate.bpp);
                        paste_render(0, r.zoom, j, &prstate);
                  }
            }
            else if (!async_bk)
            {
                  memcpy(rgb, rgb - pw, pw23);
                  continue;
            }
            render_row(rgb, mem_img, r.dx, j, tlist);
            if (!cstemp) overlay_row(rgb, mem_img, r.dx, j, tlist);
            else overlay_preview(rgb, cstemp, csel_preview, csel_preview_a);
      }
      free(xtemp);
      free(cstemp);
      free(gtemp);
      free(ptemp);

      return (!!ptemp); /* "There was paste" */
}

/// GRID

int mem_show_grid;      // Boolean show toggle
int mem_grid_min; // Minimum zoom to show it at
int color_grid;         // If to use grid coloring
int grid_rgb[4];  // Grid colors to use; index 0 is normal/image grid,
                  // 1 for border, 2 for transparency, 3 for tile grid
int show_tile_grid;     // Tile grid toggle
int tgrid_x0, tgrid_y0; // Tile grid origin
int tgrid_dx, tgrid_dy; // Tile grid spacing

/* Buffer stores interleaved transparency bits for two pixel rows; current
 * row in bits 0, 2, etc., previous one in bits 1, 3, etc. */
static void scan_trans(unsigned char *dest, int delta, int y, int x, int w)
{
      static unsigned char beta = 255;
      unsigned char *src, *srca = &beta;
      int i, ofs, bit, buf, xpm, da = 0, bpp = mem_img_bpp;


      delta += delta; dest += delta >> 3; delta &= 7;
      if (y >= mem_height) // Clear
      {
            for (i = 0; i < w; i++ , dest++) *dest = (*dest & 0x55) << 1;
            return;
      }

      xpm = mem_xpm_trans < 0 ? -1 : bpp == 1 ? mem_xpm_trans :
            PNG_2_INT(mem_pal[mem_xpm_trans]);
      ofs = y * mem_width + x;
      src = mem_img[CHN_IMAGE] + ofs * bpp;
      if (mem_img[CHN_ALPHA]) srca = mem_img[CHN_ALPHA] + ofs , da = 1;
      bit = 1 << delta;
      buf = (*dest & 0x55) << 1;
      for (i = 0; i < w; i++ , src += bpp , srca += da)
      {
            buf |= *srca && ((bpp == 1 ? *src : MEM_2_INT(src, 0)) != xpm) ?
                  bit : 0;
            if ((bit <<= 2) < 0x100) continue;
            *dest++ = buf; buf = (*dest & 0x55) << 1; bit = 1;
      }
      *dest = buf;
}

/* Draw grid on rgb memory */
static void draw_grid(unsigned char *rgb, int x, int y, int w, int h, int l)
{
/* !!! This limit IN THEORY can be violated by a sufficiently huge screen, if
 * clipping to image isn't enforced in some fashion; this code can be made to
 * detect the violation and do the clipping (on X axis) for itself - really
 * colored is only the image proper + 1 pixel to bottom & right of it */
      unsigned char lbuf[(MAX_WIDTH * 2 + 2 + 7) / 8 + 2];
      int dx, dy, step = can_zoom;
      int i, j, k, yy, wx, ww, x0, xw;


      l *= 3;
      dx = (x - margin_main_x) % step;
      if (dx <= 0) dx += step;
      dy = (y - margin_main_y) % step;
      if (dy <= 0) dy += step;

      /* Use dumb code for uncolored grid */
      if (!color_grid || (x + w <= margin_main_x) || (y + h <= margin_main_y) ||
            (x > margin_main_x + mem_width * step) ||
            (y > margin_main_y + mem_height * step))
      {
            unsigned char *tmp;
            int i, j, k, step3, tc;

            tc = grid_rgb[color_grid ? 2 : 0];
            dx = (step - dx) * 3;
            w *= 3;

            for (k = dy , i = 0; i < h; i++ , k++)
            {
                  tmp = rgb + i * l;
                  if (k == step) /* Filled line */
                  {
                        j = k = 0; step3 = 3;
                  }
                  else /* Spaced dots */
                  {
                        j = dx; step3 = step * 3;
                  }
                  for (tmp += j; j < w; j += step3 , tmp += step3)
                  {
                        tmp[0] = INT_2_R(tc);
                        tmp[1] = INT_2_G(tc);
                        tmp[2] = INT_2_B(tc);
                  }
            }
            return;
      }

      wx = floor_div(x - margin_main_x - 1, step);
      ww = (w + dx + step - 1) / step + 1;
      memset(lbuf, 0, (ww + ww + 7 + 16) >> 3); // Init to transparent

      x0 = wx < 0 ? 0 : wx;
      xw = (wx + ww < mem_width ? wx + ww : mem_width) - x0;
      wx = x0 - wx;
      yy = floor_div(y - margin_main_y, step);

      /* Initial row fill */
      if (dy == step) yy--;
      if ((yy >= 0) && (yy < mem_height)) // Else it stays cleared
            scan_trans(lbuf, wx, yy, x0, xw);

      for (k = dy , i = 0; i < h; i++ , k++)
      {
            unsigned char *tmp = rgb + i * l, *buf = lbuf + 2;

            // Horizontal span
            if (k == step)
            {
                  int nv, tc, kk;

                  yy++; k = 0;
                  // Fill one more row
                  if ((yy >= 0) && (yy <= mem_height + 1))
                        scan_trans(lbuf, wx, yy, x0, xw);
                  nv = (lbuf[0] + (lbuf[1] << 8)) ^ 0x2FFFF; // Invert
                  tc = grid_rgb[((nv & 3) + 1) >> 1];
                  for (kk = dx , j = 0; j < w; j++ , kk++ , tmp += 3)
                  {
                        if (kk != step) // Span
                        {
                              tmp[0] = INT_2_R(tc);
                              tmp[1] = INT_2_G(tc);
                              tmp[2] = INT_2_B(tc);
                              continue;
                        }
                        // Intersection
                        /* 0->0, 15->3, in-between remains between */
                        tc = grid_rgb[((nv & 0xF) * 9 + 0x79) >> 7];
                        tmp[0] = INT_2_R(tc);
                        tmp[1] = INT_2_G(tc);
                        tmp[2] = INT_2_B(tc);
                        nv >>= 2;
                        if (nv < 0x400)
                              nv ^= (*buf++ << 8) ^ 0x2FD00; // Invert
                        tc = grid_rgb[((nv & 3) + 1) >> 1];
                        kk = 0;
                  }
            }
            // Vertical spans
            else
            {
                  int nv = (lbuf[0] + (lbuf[1] << 8)) ^ 0x2FFFF; // Invert
                  j = step - dx; tmp += j * 3;
                  for (; j < w; j += step , tmp += step * 3)
                  {
                        /* 0->0, 5->3, in-between remains between */
                        int tc = grid_rgb[((nv & 5) + 3) >> 2];
                        tmp[0] = INT_2_R(tc);
                        tmp[1] = INT_2_G(tc);
                        tmp[2] = INT_2_B(tc);
                        nv >>= 2;
                        if (nv < 0x400)
                              nv ^= (*buf++ << 8) ^ 0x2FD00; // Invert
                  }
            }
      }
}

/* Draw tile grid on rgb memory */
static void draw_tgrid(unsigned char *rgb, int x, int y, int w, int h, int l)
{
      unsigned char *tmp, *tm2;
      int i, j, k, dx, dy, nx, ny, xx, yy, tc, zoom = 1, scale = 1;


      /* !!! This uses the fact that zoom factor is either N or 1/N !!! */
      if (can_zoom < 1.0) zoom = rint(1.0 / can_zoom);
      else scale = rint(can_zoom);
      if ((tgrid_dx < zoom * 2) || (tgrid_dy < zoom * 2)) return; // Too dense

      dx = tgrid_x0 ? tgrid_dx - tgrid_x0 : 0;
      nx = (x * zoom - dx) / (tgrid_dx * scale);
      if (nx < 0) nx = 0; nx++; dx++;
      dy = tgrid_y0 ? tgrid_dy - tgrid_y0 : 0;
      ny = (y * zoom - dy) / (tgrid_dy * scale);
      if (ny < 0) ny = 0; ny++; dy++;
      xx = ((nx * tgrid_dx - dx) * scale) / zoom + scale - 1;
      yy = ((ny * tgrid_dy - dy) * scale) / zoom + scale - 1;
      if ((xx >= x + w) && (yy >= y + h)) return; // Entirely inside grid cell

      l *= 3; tc = grid_rgb[3];
      for (i = 0; i < h; i++)
      {
            tmp = rgb + l * i;
            if (y + i == yy) /* Filled line */
            {
                  for (j = 0; j < w; j++ , tmp += 3)
                  {
                        tmp[0] = INT_2_R(tc);
                        tmp[1] = INT_2_G(tc);
                        tmp[2] = INT_2_B(tc);
                  }
                  yy = ((++ny * tgrid_dy - dy) * scale) / zoom + scale - 1;
                  continue;
            }
            /* Spaced dots */
            for (k = xx , j = nx + 1; k < x + w; j++)
            {
                  tm2 = tmp + (k - x) * 3;
                  tm2[0] = INT_2_R(tc);
                  tm2[1] = INT_2_G(tc);
                  tm2[2] = INT_2_B(tc);
                  k = ((j * tgrid_dx - dx) * scale) / zoom + scale - 1;
            }
      }
}

/* Redirectable RGB blitting */
void draw_rgb(int x, int y, int w, int h, unsigned char *rgb, int step, rgbcontext *ctx)
{
      if (!ctx) gdk_draw_rgb_image(drawing_canvas->window, drawing_canvas->style->black_gc,
            x, y, w, h, GDK_RGB_DITHER_NONE, rgb, step);
      else
      {
            unsigned char *dest;
            int l;

            if ((w <= 0) || (h <= 0)) return;
            w += x; h += y;
            if ((x >= ctx->x1) || (y >= ctx->y1) ||
                  (w <= ctx->x0) || (h <= ctx->y0)) return;
            if (x < ctx->x0) rgb += 3 * (ctx->x0 - x) , x = ctx->x0;
            if (y < ctx->y0) rgb += step * (ctx->y0 - y) , y = ctx->y0;
            if (w > ctx->x1) w = ctx->x1;
            if (h > ctx->y1) h = ctx->y1;
            w = (w - x) * 3;
            l = (ctx->x1 - ctx->x0) * 3;
            dest = ctx->rgb + (y - ctx->y0) * l + (x - ctx->x0) * 3;
            for (h -= y; h; h--)
            {
                  memcpy(dest, rgb, w);
                  dest += l; rgb += step;
            }
      }
}

/* Redirectable RGB fill */
void fill_rgb(int x, int y, int w, int h, int rgb, rgbcontext *ctx)
{
      if (!ctx)
      {
            static GdkGC *gc;
            static int oldrgb = -1;

            if (!gc) gc = gdk_gc_new(drawing_canvas->window);
            if (rgb != oldrgb)
                  gdk_rgb_gc_set_foreground(gc, oldrgb = rgb);
            gdk_draw_rectangle(drawing_canvas->window, gc, TRUE,
                  x, y, w, h);
      }
      else
      {
            unsigned char *dest, *tmp;
            int i, l;

            if ((w <= 0) || (h <= 0)) return;
            w += x; h += y;
            if ((x >= ctx->x1) || (y >= ctx->y1) ||
                  (w <= ctx->x0) || (h <= ctx->y0)) return;
            if (x < ctx->x0) x = ctx->x0;
            if (y < ctx->y0) y = ctx->y0;
            if (w > ctx->x1) w = ctx->x1;
            if (h > ctx->y1) h = ctx->y1;
            w = (w - x) * 3;
            l = (ctx->x1 - ctx->x0) * 3;
            tmp = dest = ctx->rgb + (y - ctx->y0) * l + (x - ctx->x0) * 3;
            *tmp++ = INT_2_R(rgb);
            *tmp++ = INT_2_G(rgb);
            *tmp++ = INT_2_B(rgb);
            for (i = w - 3; i; i-- , tmp++) *tmp = *(tmp - 3);
            tmp = dest;
            for (h -= y + 1; h; h--)
            {
                  dest += l;
                  memcpy(dest, tmp, w);
            }
      }
}

/* Redirectable polygon drawing */
void draw_poly(int *xy, int cnt, int shift, int x00, int y00, rgbcontext *ctx)
{
#define PT_BATCH 100
      GdkPoint white[PT_BATCH], black[PT_BATCH], *p;
      linedata line;
      unsigned char *rgb;
      int w = 0, nw = 0, nb = 0;
      int i, x0, y0, x1, y1, dx, dy, a0, a, vxy[4];

      if (ctx)
      {
            vxy[0] = ctx->x0; vxy[1] = ctx->y0;
            vxy[2] = ctx->x1 - 1; vxy[3] = ctx->y1 - 1;
            w = ctx->x1 - ctx->x0;
      }
      else get_visible(vxy);

      x1 = x00 + *xy++; y1 = y00 + *xy++;
      a = x1 < vxy[0] ? 1 : x1 > vxy[2] ? 2:
            y1 < vxy[1] ? 3 : y1 > vxy[3] ? 4 : 5;
      for (i = 1; i < cnt; i++)
      {
            x0 = x1; y0 = y1; a0 = a;
            x1 = x00 + *xy++; y1 = y00 + *xy++;
            dx = abs(x1 - x0); dy = abs(y1 - y0);
            if (dx < dy) dx = dy; shift += dx;
            switch (a0) // Basic clipping
            {
            // Already visible - skip if same point
            case 0: if (!dx) continue; break;
            // Left of window - skip if still there
            case 1: if (x1 < vxy[0]) continue; break;
            // Right of window
            case 2: if (x1 > vxy[2]) continue; break;
            // Top of window
            case 3: if (y1 < vxy[1]) continue; break;
            // Bottom of window
            case 4: if (y1 > vxy[3]) continue; break;
            // First point - never skip
            case 5: a0 = 0; break;
            }
            // May be visible - find where the other end goes
            a = x1 < vxy[0] ? 1 : x1 > vxy[2] ? 2 :
                  y1 < vxy[1] ? 3 : y1 > vxy[3] ? 4 : 0;
            line_init(line, x0, y0, x1, y1);
            if (a0 + a) // If both ends inside area, no clipping needed
            {
                  if (line_clip(line, vxy, &a0) < 0) continue;
                  dx -= a0;
            }
            for (dx = shift - dx; line[2] >= 0; line_step(line) , dx++)
            {
                  if (ctx) // Draw to RGB
                  {
                        rgb = ctx->rgb + ((line[1] - ctx->y0) * w +
                              (line[0] - ctx->x0)) * 3;
                        rgb[0] = rgb[1] = rgb[2] = ((~dx >> 2) & 1) * 255;
                        continue;
                  }
                  if (dx & 4) // Draw to canvas in black
                  {
                        p = black + nb++;
                        p->x = line[0]; p->y = line[1];
                        if (nb < PT_BATCH) continue;
                  }
                  else // Draw to canvas in white
                  {
                        p = white + nw++;
                        p->x = line[0]; p->y = line[1];
                        if (nw < PT_BATCH) continue;
                  }
                  // Batch drawing to canvas
                  if (nb) gdk_draw_points(drawing_canvas->window,
                        drawing_canvas->style->black_gc, black, nb);
                  if (nw)     gdk_draw_points(drawing_canvas->window,
                        drawing_canvas->style->white_gc, white, nw);
                  nb = nw = 0;
            }
      }
      // Finish drawing
      if (nb) gdk_draw_points(drawing_canvas->window,
            drawing_canvas->style->black_gc, black, nb);
      if (nw) gdk_draw_points(drawing_canvas->window,
            drawing_canvas->style->white_gc, white, nw);
#undef PT_BATCH
}

/* Clip area to image & align rgb pointer with it */
static unsigned char *clip_to_image(int *xywh, unsigned char *rgb,
      int px, int py, int pw, int ph)
{
      int mw, mh, px2, py2, pw2, ph2, nix = 0, niy = 0, zoom = 1, scale = 1;

      /* !!! This uses the fact that zoom factor is either N or 1/N !!! */
      if (can_zoom < 1.0) zoom = rint(1.0 / can_zoom);
      else scale = rint(can_zoom);

      /* Align buffer with image */
      px2 = px - margin_main_x;
      py2 = py - margin_main_y;
      if (px2 < 0) nix = -px2;
      if (py2 < 0) niy = -py2;
      rgb += (pw * niy + nix) * 3;

      /* Clip update area to image bounds */
      mw = (mem_width * scale + zoom - 1) / zoom;
      mh = (mem_height * scale + zoom - 1) / zoom;
      pw2 = pw + px2;
      ph2 = ph + py2;
      if (pw2 > mw) pw2 = mw;
      if (ph2 > mh) ph2 = mh;
      px2 += nix; py2 += niy;
      pw2 -= px2; ph2 -= py2;
      if ((pw2 < 1) || (ph2 < 1)) return (NULL);
      xywh[0] = px2; xywh[1] = py2;
      xywh[2] = pw2; xywh[3] = ph2;
      return (rgb);
}

void repaint_canvas( int px, int py, int pw, int ph )
{
      rgbcontext ctx;
      unsigned char *rgb, *irgb;
      int xywh[4], pw2, ph2, lx = 0, ly = 0, rx1, ry1, rx2, ry2, rpx, rpy;
      int i, j, lr, zoom = 1, scale = 1, paste_f = FALSE;

      if (px < 0)
      {
            pw += px; px = 0;
      }
      if (py < 0)
      {
            ph += py; py = 0;
      }

      if ((pw <= 0) || (ph <= 0)) return;
      rgb = malloc(i = pw * ph * 3);
      if (!rgb) return;
      memset(rgb, mem_background, i);

      /* Find out which part is image */
      irgb = clip_to_image(xywh, rgb, px, py, pw, ph);

      /* Init context */
      ctx.x0 = px; ctx.y0 = py; ctx.x1 = px + pw; ctx.y1 = py + ph;
      ctx.rgb = rgb;

      /* !!! This uses the fact that zoom factor is either N or 1/N !!! */
      if (can_zoom < 1.0) zoom = rint(1.0 / can_zoom);
      else scale = rint(can_zoom);

      rpx = px - margin_main_x;
      rpy = py - margin_main_y;
      pw2 = rpx + pw - 1;
      ph2 = rpy + ph - 1;

      lr = layers_total && show_layers_main;
      if (bkg_flag && bkg_rgb) render_bkg(&ctx); /* Tracing image */
      else if (!lr) /* Render default background if no layers shown */
      {
            if (irgb && ((mem_xpm_trans >= 0) ||
                  (!overlay_alpha && mem_img[CHN_ALPHA] && !channel_dis[CHN_ALPHA])))
                  render_background(irgb, xywh[0], xywh[1], xywh[2], xywh[3], pw * 3);
      }
      if (lr) /* Render underlying layers */
      {
            if (layer_selected)
            {
                  lx = layer_table[layer_selected].x;
                  ly = layer_table[layer_selected].y;
                  if (zoom > 1)
                  {
                        i = lx / zoom;
                        lx = i * zoom > lx ? i - 1 : i;
                        j = ly / zoom;
                        ly = j * zoom > ly ? j - 1 : j;
                  }
                  else
                  {
                        lx *= scale;
                        ly *= scale;
                  }
            }
            render_layers(rgb, pw * 3, rpx + lx, rpy + ly, pw, ph,
                  can_zoom, 0, layer_selected - 1, 1);
      }

      if (irgb) paste_f = main_render_rgb(irgb, xywh[0], xywh[1], xywh[2], xywh[3], pw);

      if (lr) render_layers(rgb, pw * 3, rpx + lx, rpy + ly, pw, ph,
            can_zoom, layer_selected + 1, layers_total, 1);

      /* No grid at all */
      if (!mem_show_grid || (scale < mem_grid_min));
      /* No paste - single area */
      else if (!paste_f) draw_grid(rgb, px, py, pw, ph, pw);
      /* With paste - zero to four areas */
      else
      {
            int n, x0, y0, w0, h0, xywh04[5 * 4], *p = xywh04;
            unsigned char *r;

            w0 = (marq_x2 < mem_width ? marq_x2 + 1 : mem_width) * scale;
            x0 = marq_x1 > 0 ? marq_x1 * scale : 0;
            w0 -= x0; x0 += margin_main_x;
            h0 = (marq_y2 < mem_height ? marq_y2 + 1 : mem_height) * scale;
            y0 = marq_y1 > 0 ? marq_y1 * scale : 0;
            h0 -= y0; y0 += margin_main_y;

            n = clip4(xywh04, px, py, pw, ph, x0, y0, w0, h0);
            while (n--)
            {
                  p += 4;
                  r = rgb + ((p[1] - py) * pw + (p[0] - px)) * 3;
                  draw_grid(r, p[0], p[1], p[2], p[3], pw);
            }
      }

      /* Tile grid */
      if (show_tile_grid && irgb)
            draw_tgrid(irgb, xywh[0], xywh[1], xywh[2], xywh[3], pw);

      async_bk = FALSE;

      /* Redraw gradient line if needed */
      while (gradient[mem_channel].status == GRAD_DONE)
      {
            grad_info *grad = gradient + mem_channel;

            /* Don't clutter screen needlessly */
            if (!mem_gradient && (tool_type != TOOL_GRADIENT)) break;

            /* Canvas-space endpoints */
            if (grad->x1 < grad->x2) rx1 = grad->x1 , rx2 = grad->x2;
            else rx1 = grad->x2 , rx2 = grad->x1;
            if (grad->y1 < grad->y2) ry1 = grad->y1 , ry2 = grad->y2;
            else ry1 = grad->y2 , ry2 = grad->y1;
            rx1 = (rx1 * scale) / zoom;
            ry1 = (ry1 * scale) / zoom;
            rx2 = (rx2 * scale) / zoom + scale - 1;
            ry2 = (ry2 * scale) / zoom + scale - 1;

            /* Check intersection - coarse */
            if ((rx1 > pw2) || (rx2 < rpx) || (ry1 > ph2) || (ry2 < rpy))
                  break;
            if (rx1 != rx2) /* Check intersection - fine */
            {
                  float ty1, ty2, dy;

                  if ((grad->x1 < grad->x2) ^ (grad->y1 < grad->y2))
                        i = ry2 , j = ry1;
                  else i = ry1 , j = ry2;

                  dy = (j - i) / (float)(rx2 - rx1);
                  ty1 = rx1 >= rpx ? i : i + (rpx - rx1 - 0.5) * dy;
                  ty2 = rx2 <= pw2 ? j : i + (pw2 - rx1 + 0.5) * dy;

                  if (((ty1 < rpy - scale) && (ty2 < rpy - scale)) ||
                        ((ty1 > ph2 + scale) && (ty2 > ph2 + scale)))
                        break;
            }
// !!! Wrong order - overlays clipboard !!!
            refresh_grad(&ctx);
            break;
      }

      /* Draw perimeter & marquee as we may have drawn over them */
/* !!! All other over-the-image things have to be redrawn here as well !!! */
      if (marq_status != MARQUEE_NONE) refresh_marquee(&ctx);
      if ((tool_type == TOOL_POLYGON) && poly_points)
            paint_poly_marquee(&ctx, TRUE);
      if (perim_status > 0) repaint_perim(&ctx);

      gdk_draw_rgb_image(drawing_canvas->window, drawing_canvas->style->black_gc,
            px, py, pw, ph, GDK_RGB_DITHER_NONE, rgb, pw * 3);
      free(rgb);
}

/* Update x,y,w,h area of current image */
void main_update_area(int x, int y, int w, int h)
{
      int zoom, scale;

      if (can_zoom < 1.0)
      {
            zoom = rint(1.0 / can_zoom);
            w += x;
            h += y;
            x = floor_div(x + zoom - 1, zoom);
            y = floor_div(y + zoom - 1, zoom);
            w = (w - x * zoom + zoom - 1) / zoom;
            h = (h - y * zoom + zoom - 1) / zoom;
            if ((w <= 0) || (h <= 0)) return;
      }
      else
      {
            scale = rint(can_zoom);
            x *= scale;
            y *= scale;
            w *= scale;
            h *= scale;
            if (color_grid && mem_show_grid && (scale >= mem_grid_min))
                  w++ , h++; // Redraw grid lines bordering the area
      }

      gtk_widget_queue_draw_area(drawing_canvas,
            x + margin_main_x, y + margin_main_y, w, h);
}

/* Get zoomed canvas size */
void canvas_size(int *w, int *h)
{
      int i;

      /* !!! This uses the fact that zoom factor is either N or 1/N !!! */
      if (can_zoom < 1.0)
      {
            i = rint(1.0 / can_zoom);
            *w = (mem_width + i - 1) / i;
            *h = (mem_height + i - 1) / i;
      }
      else
      {
            i = rint(can_zoom);
            *w = mem_width * i;
            *h = mem_height * i;
      }
}

void clear_perim()
{
      perim_status = 0; /* Cleared */
      /* Don't bother if tool has no perimeter */
      if (NO_PERIM(tool_type)) return;
      clear_perim_real(0, 0);
      if ( tool_type == TOOL_CLONE ) clear_perim_real(clone_x, clone_y);
}

void repaint_perim_real(int r, int g, int b, int ox, int oy, rgbcontext *ctx)
{
      int i, j, w, h, x0, y0, x1, y1, zoom = 1, scale = 1;
      unsigned char *rgb;


      /* !!! This uses the fact that zoom factor is either N or 1/N !!! */
      if (can_zoom < 1.0) zoom = rint(1.0 / can_zoom);
      else scale = rint(can_zoom);

      x0 = margin_main_x + ((perim_x + ox) * scale) / zoom;
      y0 = margin_main_y + ((perim_y + oy) * scale) / zoom;
      x1 = margin_main_x + ((perim_x + ox + perim_s - 1) * scale) / zoom + scale - 1;
      y1 = margin_main_y + ((perim_y + oy + perim_s - 1) * scale) / zoom + scale - 1;

      w = x1 - x0 + 1;
      h = y1 - y0 + 1;
      j = (w > h ? w : h) * 3;
      rgb = calloc(j + 2 * 3, 1); /* 2 extra pixels reserved for loop */
      if (!rgb) return;
      for (i = 0; i < j; i += 6 * 3)
      {
            rgb[i + 0] = rgb[i + 3] = rgb[i + 6] = r;
            rgb[i + 1] = rgb[i + 4] = rgb[i + 7] = g;
            rgb[i + 2] = rgb[i + 5] = rgb[i + 8] = b;
      }

      draw_rgb(x0, y0, 1, h, rgb, 3, ctx);
      draw_rgb(x1, y0, 1, h, rgb, 3, ctx);

      draw_rgb(x0 + 1, y0, w - 2, 1, rgb, 0, ctx);
      draw_rgb(x0 + 1, y1, w - 2, 1, rgb, 0, ctx);
      free(rgb);
}

void repaint_perim(rgbcontext *ctx)
{
      /* Don't bother if tool has no perimeter */
      if (NO_PERIM(tool_type)) return;
      repaint_perim_real(255, 255, 255, 0, 0, ctx);
      if ( tool_type == TOOL_CLONE )
            repaint_perim_real(255, 0, 0, clone_x, clone_y, ctx);
      perim_status = 1; /* Drawn */
}

static gboolean canvas_motion(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
{
      int x, y, rm, button = 0;
      GdkModifierType state;
      gdouble pressure = 1.0;

      /* Skip synthetic mouse moves */
      if (unreal_move == 3)
      {
            unreal_move = 2;
            return TRUE;
      }
      rm = unreal_move;
      unreal_move = 0;

      /* Do nothing if no image */
      if (!mem_img[CHN_IMAGE]) return (TRUE);

#if GTK_MAJOR_VERSION == 1
      if (event->is_hint)
      {
            gdk_input_window_get_pointer(event->window, event->deviceid,
                  NULL, NULL, &pressure, NULL, NULL, &state);
            gdk_window_get_pointer(event->window, &x, &y, &state);
      }
      else
      {
            x = event->x;
            y = event->y;
            pressure = event->pressure;
            state = event->state;
      }
#endif
#if GTK_MAJOR_VERSION == 2
      if (event->is_hint) gdk_device_get_state(event->device, event->window,
            NULL, &state);
      x = event->x;
      y = event->y;
      state = event->state;

      if (tablet_working) gdk_event_get_axis((GdkEvent *)event,
            GDK_AXIS_PRESSURE, &pressure);
#endif

      if ((state & (GDK_BUTTON1_MASK | GDK_BUTTON3_MASK)) ==
            (GDK_BUTTON1_MASK | GDK_BUTTON3_MASK)) button = 13;
      else if (state & GDK_BUTTON1_MASK) button = 1;
      else if (state & GDK_BUTTON3_MASK) button = 3;
      else if (state & GDK_BUTTON2_MASK) button = 2;

      mouse_event(event->type, x, y, state, button, pressure, rm & 1, 0, 0);

      return TRUE;
}

static gboolean configure_canvas( GtkWidget *widget, GdkEventConfigure *event )
{
      int w, h, new_margin_x = 0, new_margin_y = 0;

      if ( canvas_image_centre )
      {
            canvas_size(&w, &h);

            w = drawing_canvas->allocation.width - w;
            h = drawing_canvas->allocation.height - h;

            if (w > 0) new_margin_x = w >> 1;
            if (h > 0) new_margin_y = h >> 1;
      }

      if ((new_margin_x != margin_main_x) || (new_margin_y != margin_main_y))
      {
            margin_main_x = new_margin_x;
            margin_main_y = new_margin_y;
            gtk_widget_queue_draw(drawing_canvas);
                  // Force redraw of whole canvas as the margin has shifted
      }
      vw_realign();     // Update the view window as needed

      return (TRUE);
}

void force_main_configure()
{
      if (drawing_canvas) configure_canvas(drawing_canvas, NULL);
      if (view_showing && vw_drawing) vw_configure(vw_drawing, NULL);
}

static gboolean expose_canvas(GtkWidget *widget, GdkEventExpose *event,
      gpointer user_data)
{
      int px, py, pw, ph;

      /* Stops excess jerking in GTK+1 when zooming */
      if (zoom_flag) return (TRUE);

      px = event->area.x;           // Only repaint if we need to
      py = event->area.y;
      pw = event->area.width;
      ph = event->area.height;

      repaint_canvas( px, py, pw, ph );

      return (TRUE);
}

void set_cursor()             // Set mouse cursor
{
      if (!drawing_canvas->window) return; /* Do nothing if canvas hidden */
      gdk_window_set_cursor(drawing_canvas->window,
            cursor_tool ? m_cursor[tool_type] : NULL);
}



void change_to_tool(int icon)
{
      int i, t, update = CF_SELBAR | CF_MENU | CF_CURSOR;

      if (!GTK_WIDGET_SENSITIVE(icon_buttons[icon])) return; // Blocked
      if (!GTK_TOGGLE_BUTTON(icon_buttons[icon])->active) // Toggle the button
            gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(icon_buttons[icon]), TRUE);

      switch (icon)
      {
      case TTB_PAINT:
            t = brush_tool_type; break;
      case TTB_SHUFFLE:
            t = TOOL_SHUFFLE; break;
      case TTB_FLOOD:
            t = TOOL_FLOOD; break;
      case TTB_LINE:
            t = TOOL_LINE; break;
      case TTB_SMUDGE:
            t = TOOL_SMUDGE; break;
      case TTB_CLONE:
            t = TOOL_CLONE; break;
      case TTB_SELECT:
            t = TOOL_SELECT; break;
      case TTB_POLY:
            t = TOOL_POLYGON; break;
      case TTB_GRAD:
            t = TOOL_GRADIENT; break;
      default: return;
      }

      /* Tool hasn't changed (likely, recursion changed it from under us) */
      if (t == tool_type) return;

      if (perim_status) clear_perim();
      i = tool_type;
      tool_type = t;

      if (i == TOOL_LINE) stop_line();
      if ((i == TOOL_GRADIENT) &&
            (gradient[mem_channel].status != GRAD_NONE))
      {
            if (gradient[mem_channel].status != GRAD_DONE)
                  gradient[mem_channel].status = GRAD_NONE;
            else if (grad_opacity) update |= CF_DRAW;
            else if (!mem_gradient) repaint_grad(0);
      }
      if ( marq_status != MARQUEE_NONE)
      {
            if (paste_commit && (marq_status >= MARQUEE_PASTE))
            {
                  commit_paste(FALSE, NULL);
                  pen_down = 0;
                  mem_undo_prepare();
            }

            marq_status = MARQUEE_NONE;   // Marquee is on so lose it!
            update |= CF_DRAW;                  // Needed to clear selection
      }
      if ( poly_status != POLY_NONE)
      {
            poly_status = POLY_NONE;      // Marquee is on so lose it!
            poly_points = 0;
            update |= CF_DRAW;                  // Needed to clear selection
      }
      if ( tool_type == TOOL_CLONE )
      {
            clone_x = -tool_size;
            clone_y = tool_size;
      }
      /* Persistent selection frame */
// !!! To NOT show selection frame while placing gradient
//    if ((tool_type == TOOL_SELECT)
      if (((tool_type == TOOL_SELECT) || (tool_type == TOOL_GRADIENT))
            && (marq_x1 >= 0) && (marq_y1 >= 0)
            && (marq_x2 >= 0) && (marq_y2 >= 0))
      {
            marq_status = MARQUEE_DONE;
            paint_marquee(1, 0, 0);
      }
      if ((tool_type == TOOL_GRADIENT) &&
            (gradient[mem_channel].status == GRAD_DONE))
      {
            if (grad_opacity) update |= CF_DRAW;
            else repaint_grad(1);
      }
      update_stuff(update);
      if (!(update & CF_DRAW)) repaint_perim(NULL);
}

static void pressed_view_hori(int state)
{
      gboolean vs = view_showing;

      if (state)
      {
            if (main_split == main_hsplit) return;
            view_hide();
            main_split = main_hsplit;
      }
      else
      {
            if (main_split == main_vsplit) return;
            view_hide();
            main_split = main_vsplit;
      }
      if (vs) view_show();
}

void set_image(gboolean state)
{
      static int depth = 0;

      if (state ? --depth : depth++) return;

      (state ? gtk_widget_show_all : gtk_widget_hide)(view_showing ? main_split :
            scrolledwindow_canvas);
      if (state) set_cursor(); /* Canvas window is now a new one */
}

static void parse_drag( char *txt )
{
      char fname[PATHBUF], *tp, *tp2;
      int i, j, nlayer = TRUE;

      set_image(FALSE);

      tp = txt;
      while ((layers_total < MAX_LAYERS) && (tp2 = strstr(tp, "file:")))
      {
            tp = tp2 + 5;
            while ( tp[0] == '/' ) tp++;
#ifndef WIN32
            // If not windows keep a leading slash
            tp--;
            // If windows strip away all leading slashes
#endif
            i = 0;
            j = 0;
            while ((tp[j] > 31) && (j < PATHBUF - 1)) // Copy filename
            {
                  if ( tp[j] == '%' )     // Weed out those ghastly % substitutions
                  {
                        fname[i++] = read_hex_dub( tp+j+1 );
                        j += 2;
                  }
                  else fname[i++] = tp[j];
                  j++;
            }
            fname[i] = 0;
            tp = tp + j;

            j = detect_image_format(fname);
            if ((j > 0) && (j != FT_NONE) && (j != FT_LAYERS1))
            {
                  if (!nlayer || layer_add(0, 0, 1, 0, mem_pal_def, 0))
                        nlayer = load_image(fname, FS_LAYER_LOAD, j) == 1;
                  if (nlayer) set_new_filename(layers_total, fname);
            }
      }
      if (!nlayer) layer_delete(layers_total);

      layer_refresh_list();
      layer_choose(layers_total);
      layers_notify_changed();
      if (layers_total) view_show();
      set_image(TRUE);
}


static const GtkTargetEntry uri_list = { "text/uri-list", 0, 1 };

static gboolean drag_n_drop_tried(GtkWidget *widget, GdkDragContext *context,
      gint x, gint y, guint time, gpointer user_data)
{
      GdkAtom target = gdk_atom_intern("text/uri-list", FALSE);
      gpointer tp = GUINT_TO_POINTER(target);
      GList *src;

      /* Check if drop could provide a supported format */
      for (src = context->targets; src && (src->data != tp); src = src->next);
      if (!src) return (FALSE);
      /* Trigger "drag_data_received" event */
      gtk_drag_get_data(widget, context, target, time);
      return (TRUE);
}

static void drag_n_drop_received(GtkWidget *widget, GdkDragContext *context,
      gint x, gint y, GtkSelectionData *data, guint info, guint time)
{
      int success;

      if ((success = ((data->length >= 0) && (data->format == 8))))
            parse_drag((gchar *)data->data);
      /* Accept move as a copy (disallow deleting source) */
      gtk_drag_finish(context, success, FALSE, time);
}


03500 typedef struct
{
      char *path; /* Full path for now */
      signed char radio_BTS; /* -2..-5 are for BTS */
      unsigned short ID;
      int actmap;
      char *shortcut; /* Text form for now */
      short action, mode;
      char **xpm_icon_image;
} menu_item;

/* The following is main menu auto-rearrange code. If the menu is too long for
 * the window, some of its items are moved into "overflow" submenu - and moved
 * back to menubar when the window is made wider. This way, we can support
 * small-screen devices without penalizing large-screen ones. - WJ */

#define MENU_RESIZE_MAX 16

03518 typedef struct {
      GtkWidget *item, *fallback;
      guint key;
      int width;
} r_menu_slot;

int r_menu_state;
r_menu_slot r_menu[MENU_RESIZE_MAX];

/* Handle keyboard accels for overflow menu */
static int check_smart_menu_keys(GdkEventKey *event)
{
      guint lowkey;
      int i;

      /* View mode - do nothing */
      if (view_image_only) return (0);
      /* No overflow - nothing to do */
      if (r_menu_state == 0) return (0);
      /* Alt+key only */
      if ((event->state & _CSA) != _A) return (0);

      lowkey = low_key(event);
      for (i = 1; i <= r_menu_state; i++)
      {
            if (r_menu[i].key != lowkey) continue;
            /* Just popup - if we're here, overflow menu is offscreen anyway */
            gtk_menu_popup(GTK_MENU(GTK_MENU_ITEM(r_menu[i].fallback)->submenu),
                  NULL, NULL, NULL, NULL, 0, 0);

            return (ACTMOD_DUMMY);
      }
      return (0);
}

/* Invalidate width cache after width-affecting change */
static void check_width_cache(int width)
{
      r_menu_slot *slot;

      if (r_menu[r_menu_state].width == width) return;
      if (r_menu[r_menu_state].width)
            for (slot = r_menu; slot->item; slot++) slot->width = 0;
      r_menu[r_menu_state].width = width;
}

/* Show/hide widgets according to new state */
static void change_to_state(int state)
{
      int i, oldst = r_menu_state;

      if (oldst < state)
      {
            for (i = oldst + 1; i <= state; i++)
                  gtk_widget_hide(r_menu[i].item);
            if (oldst == 0) gtk_widget_show(r_menu[0].item);
      }
      else
      {
            for (i = oldst; i > state; i--)
                  gtk_widget_show(r_menu[i].item);
            if (state == 0) gtk_widget_hide(r_menu[0].item);
      }
      r_menu_state = state;
}

/* Move submenus between menubar and overflow submenu */
static void switch_states(int newstate, int oldstate)
{
      GtkWidget *submenu;
      GtkMenuItem *item;
      int i;

      if (newstate < oldstate) /* To main menu */
      {
            for (i = oldstate; i > newstate; i--)
            {
                  gtk_widget_hide(r_menu[i].fallback);
                  item = GTK_MENU_ITEM(r_menu[i].fallback);
                  gtk_widget_ref(submenu = item->submenu);
                  gtk_menu_item_remove_submenu(item);
                  item = GTK_MENU_ITEM(r_menu[i].item);
                  gtk_menu_item_set_submenu(item, submenu);
                  gtk_widget_unref(submenu);
            }
      }
      else /* To overflow submenu */
      {
            for (i = oldstate + 1; i <= newstate; i++)
            {
                  item = GTK_MENU_ITEM(r_menu[i].item);
                  gtk_widget_ref(submenu = item->submenu);
                  gtk_menu_item_remove_submenu(item);
                  item = GTK_MENU_ITEM(r_menu[i].fallback);
                  gtk_menu_item_set_submenu(item, submenu);
                  gtk_widget_unref(submenu);
                  gtk_widget_show(r_menu[i].fallback);
            }
      }
}

/* Get width request for default state */
static int smart_menu_full_width(GtkWidget *widget, int width)
{
      check_width_cache(width);
      if (!r_menu[0].width)
      {
            GtkRequisition req;
            GtkWidget *child = GTK_BIN(widget)->child;
            int oldst = r_menu_state;
            gpointer lock = toggle_updates(widget, NULL);
            change_to_state(0);
            gtk_widget_size_request(child, &req);
            r_menu[0].width = req.width;
            change_to_state(oldst);
            child->requisition.width = width;
            toggle_updates(widget, lock);
      }
      return (r_menu[0].width);
}

/* Switch to the state which best fits the allocated width */
static void smart_menu_state_to_width(GtkWidget *widget, int rwidth, int awidth)
{
      GtkWidget *child = GTK_BIN(widget)->child;
      gpointer lock = NULL;
      int state, oldst, newst;

      check_width_cache(rwidth);
      state = oldst = r_menu_state;
      while (TRUE)
      {
            newst = rwidth < awidth ? state - 1 : state + 1;
            if ((newst < 0) || !r_menu[newst].item) break;
            if (!r_menu[newst].width)
            {
                  GtkRequisition req;
                  if (!lock) lock = toggle_updates(widget, NULL);
                  change_to_state(newst);
                  gtk_widget_size_request(child, &req);
                  r_menu[newst].width = req.width;
            }
            state = newst;
            if ((rwidth < awidth) ^ (r_menu[state].width <= awidth)) break;
      }
      while ((r_menu[state].width > awidth) && r_menu[state + 1].item) state++;
      if (state != r_menu_state)
      {
            if (!lock) lock = toggle_updates(widget, NULL);
            change_to_state(state);
            child->requisition.width = r_menu[state].width;
      }
      if (state != oldst) switch_states(state, oldst);
      if (lock) toggle_updates(widget, lock);
}

static void smart_menu_size_req(GtkWidget *widget, GtkRequisition *req,
      gpointer user_data)
{
      GtkRequisition child_req;
      GtkWidget *child = GTK_BIN(widget)->child;
      int fullw;

      req->width = req->height = GTK_CONTAINER(widget)->border_width * 2;
      if (!child || !GTK_WIDGET_VISIBLE(child)) return;

      gtk_widget_size_request(child, &child_req);
      fullw = smart_menu_full_width(widget, child_req.width);

      req->width += fullw;
      req->height += child_req.height;
}

static void smart_menu_size_alloc(GtkWidget *widget, GtkAllocation *alloc,
      gpointer user_data)
{
      static int in_alloc;
      GtkRequisition child_req;
      GtkAllocation child_alloc;
      GtkWidget *child = GTK_BIN(widget)->child;
      int border = GTK_CONTAINER(widget)->border_width, border2 = border * 2;

      widget->allocation = *alloc;
      if (!child || !GTK_WIDGET_VISIBLE(child)) return;

      /* Maybe recursive calls to this cannot happen, but if they can,
       * crash would be quite spectacular - so, better safe than sorry */
      if (in_alloc) /* Postpone reaction */
      {
            in_alloc |= 2;
            return;
      }

      /* !!! Always keep child widget requisition set according to its
       * !!! mode, or this code will break down in interesting ways */
      gtk_widget_get_child_requisition(child, &child_req);
/* !!! Alternative approach - reliable but slow */
//    gtk_widget_size_request(child, &child_req);
      while (TRUE)
      {
            in_alloc = 1;
            child_alloc.x = alloc->x + border;
            child_alloc.y = alloc->y + border;
            child_alloc.width = alloc->width > border2 ?
                  alloc->width - border2 : 0;
            child_alloc.height = alloc->height > border2 ?
                  alloc->height - border2 : 0;
            if ((child_alloc.width != child->allocation.width) &&
                  (r_menu_state > 0 ? child_alloc.width != child_req.width :
                  child_alloc.width < child_req.width))
                  smart_menu_state_to_width(widget, child_req.width,
                        child_alloc.width);
            if (in_alloc < 2) break;
            alloc = &widget->allocation;
      }
      in_alloc = 0;

      gtk_widget_size_allocate(child, &child_alloc);
}

static void pressed_pal_copy();
static void pressed_pal_paste();
static void pressed_sel_ramp(int vert);

static const signed char arrow_dx[4] = { 0, -1, 1, 0 },
      arrow_dy[4] = { 1, 0, 0, -1 };

void action_dispatch(int action, int mode, int state, int kbd)
{
      int change = mode & 1 ? mem_nudge : 1, dir = (mode >> 1) - 1;

      switch (action)
      {
      case ACT_QUIT:
            quit_all(mode); break;
      case ACT_ZOOM:
            if (!mode) zoom_in();
            else if (mode == -1) zoom_out();
            else align_size(mode > 0 ? mode : -1.0 / mode);
            break;
      case ACT_VIEW:
            toggle_view(); break;
      case ACT_PAN:
            pressed_pan(); break;
      case ACT_CROP:
            pressed_crop(); break;
      case ACT_SWAP_AB:
            mem_swap_cols(TRUE); break;
      case ACT_TOOL:
            if (state || kbd) change_to_tool(mode); // Ignore DEactivating buttons
            break;
      case ACT_SEL_MOVE:
            /* Gradient tool has precedence over selection */
            if ((tool_type != TOOL_GRADIENT) && (marq_status > MARQUEE_NONE))
            {
                  paint_marquee(2, marq_x1 + change * arrow_dx[dir],
                        marq_y1 + change * arrow_dy[dir]);
                  update_stuff(UPD_SGEOM);
            }
            else move_mouse(change * arrow_dx[dir], change * arrow_dy[dir], 0);
            break;
      case ACT_OPAC:
            pressed_opacity(mode > 0 ? (255 * mode) / 10 :
                  tool_opacity + 1 + mode + mode);
            break;
      case ACT_LR_MOVE:
            /* User is selecting so allow CTRL+arrow keys to resize the
             * marquee; for consistency, gradient tool blocks this */
            if ((tool_type != TOOL_GRADIENT) && (marq_status == MARQUEE_DONE))
            {
                  paint_marquee(3, marq_x2 + change * arrow_dx[dir],
                        marq_y2 + change * arrow_dy[dir]);
                  update_stuff(UPD_SGEOM);
            }
            else if (layer_selected) move_layer_relative(layer_selected,
                  change * arrow_dx[dir], change * arrow_dy[dir]);
            else if (bkg_flag)
            {
                  /* !!! Later, maybe localize redraw to the changed part */
                  bkg_x += change * arrow_dx[dir];
                  bkg_y += change * arrow_dy[dir];
                  update_stuff(UPD_RENDER);
            }
            break;
      case ACT_ESC:
            if ((tool_type == TOOL_SELECT) || (tool_type == TOOL_POLYGON))
                  pressed_select(FALSE);
            else if (tool_type == TOOL_LINE) stop_line();
            else if ((tool_type == TOOL_GRADIENT) &&
                  (gradient[mem_channel].status != GRAD_NONE))
            {
                  gradient[mem_channel].status = GRAD_NONE;
                  if (grad_opacity) update_stuff(UPD_RENDER);
                  else repaint_grad(0);
            }
            break;
      case ACT_COMMIT:
            if (marq_status >= MARQUEE_PASTE)
            {
                  commit_paste(mode, NULL);
                  pen_down = 0;     // Ensure each press of enter is a new undo level
                  mem_undo_prepare();
            }
            else move_mouse(0, 0, 1);
            break;
      case ACT_RCLICK:
            if (marq_status < MARQUEE_PASTE) move_mouse(0, 0, 3);
            break;
      case ACT_ARROW: draw_arrow(mode); break;
      case ACT_A:
      case ACT_B:
            action = action == ACT_B;
            if (mem_channel == CHN_IMAGE)
            {
                  mode += mem_col_[action];
                  if ((mode >= 0) && (mode < mem_cols))
                        mem_col_[action] = mode;
                  mem_col_24[action] = mem_pal[mem_col_[action]];
            }
            else
            {
                  mode += channel_col_[action][mem_channel];
                  if ((mode >= 0) && (mode <= 255))
                        channel_col_[action][mem_channel] = mode;
            }
            update_stuff(UPD_CAB);
            break;
      case ACT_CHANNEL:
            if (kbd) state = TRUE;
            if (mode < 0) pressed_channel_create(mode);
            else pressed_channel_edit(state, mode);
            break;
      case ACT_VWZOOM:
            if (!mode)
            {
                  if (vw_zoom >= 1) vw_align_size(vw_zoom + 1);
                  else vw_align_size(1.0 / (rint(1.0 / vw_zoom) - 1));
            }
            else if (mode == -1)
            {
                  if (vw_zoom > 1) vw_align_size(vw_zoom - 1);
                  else vw_align_size(1.0 / (rint(1.0 / vw_zoom) + 1));
            }
//          else vw_align_size(mode > 0 ? mode : -1.0 / mode);
            break;
      case ACT_SAVE:
            pressed_save_file(); break;
      case ACT_FACTION:
            pressed_file_action(mode); break;
      case ACT_LOAD_RECENT:
            pressed_load_recent(mode); break;
      case ACT_UNDO:
            main_undo(); break;
      case ACT_REDO:
            main_redo(); break;
      case ACT_COPY:
            pressed_copy(mode); break;
      case ACT_PASTE:
            pressed_paste(mode); break;
      case ACT_COPY_PAL:
            pressed_pal_copy(); break;
      case ACT_PASTE_PAL:
            pressed_pal_paste(); break;
      case ACT_LOAD_CLIP:
            load_clip(mode); break;
      case ACT_SAVE_CLIP:
            save_clip(mode); break;
      case ACT_TBAR:
            pressed_toolbar_toggle(state, mode); break;
      case ACT_DOCK:
            if (!kbd) toggle_dock(show_dock = state, FALSE);
            else if (GTK_WIDGET_SENSITIVE(menu_widgets[MENU_DOCK]))
                  gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(
                        menu_widgets[MENU_DOCK]), !show_dock);
            break;
      case ACT_CENTER:
            pressed_centralize(state); break;
      case ACT_GRID:
            zoom_grid(state); break;
      case ACT_VWWIN:
            if (state) view_show();
            else view_hide();
            break;
      case ACT_VWSPLIT:
            pressed_view_hori(state); break;
      case ACT_VWFOCUS:
            pressed_view_focus(state); break;
      case ACT_FLIP_V:
            pressed_flip_image_v(); break;
      case ACT_FLIP_H:
            pressed_flip_image_h(); break;
      case ACT_ROTATE:
            pressed_rotate_image(mode); break;
      case ACT_SELECT:
            pressed_select(mode); break;
      case ACT_LASSO:
            pressed_lasso(mode); break;
      case ACT_OUTLINE:
            pressed_rectangle(mode); break;
      case ACT_ELLIPSE:
            pressed_ellipse(mode); break;
      case ACT_SEL_FLIP_V:
            pressed_flip_sel_v(); break;
      case ACT_SEL_FLIP_H:
            pressed_flip_sel_h(); break;
      case ACT_SEL_ROT:
            pressed_rotate_sel(mode); break;
      case ACT_RAMP:
            pressed_sel_ramp(mode); break;
      case ACT_SEL_ALPHA_AB:
            pressed_clip_alpha_scale(); break;
      case ACT_SEL_ALPHAMASK:
            pressed_clip_alphamask(); break;
      case ACT_SEL_MASK_AB:
            pressed_clip_mask(mode); break;
      case ACT_SEL_MASK:
            if (!mode) pressed_clip_mask_all();
            else pressed_clip_mask_clear();
            break;
      case ACT_PAL_DEF:
            pressed_default_pal(); break;
      case ACT_PAL_MASK:
            pressed_mask(mode); break;
      case ACT_DITHER_A:
            pressed_dither_A(); break;
      case ACT_PAL_MERGE:
            pressed_remove_duplicates(); break;
      case ACT_PAL_CLEAN:
            pressed_remove_unused(); break;
      case ACT_ISOMETRY:
            iso_trans(mode); break;
      case ACT_CHN_DIS:
            pressed_channel_disable(state, mode); break;
      case ACT_SET_RGBA:
            pressed_RGBA_toggle(state); break;
      case ACT_SET_OVERLAY:
            pressed_channel_toggle(state, mode); break;
      case ACT_LR_SAVE:
            layer_press_save(); break;
      case ACT_LR_ADD:
            if (mode == LR_NEW) generic_new_window(1);
            else if (mode == LR_DUP) layer_press_duplicate();
            else if (mode == LR_PASTE) pressed_paste_layer();
            else /* if (mode == LR_COMP) */ layer_add_composite();
            break;
      case ACT_LR_DEL:
            if (!mode) layer_press_delete();
            else layer_press_remove_all();
            break;
      case ACT_DOCS:
            show_html(inifile_get(HANDBOOK_BROWSER_INI, NULL),
                  inifile_get(HANDBOOK_LOCATION_INI, NULL));
            break;
      case ACT_REBIND_KEYS:
            rebind_keys(); break;
      case ACT_MODE:
            mode_change(mode, state); break;
      case ACT_LR_SHIFT:
            shift_layer(mode); break;
      case ACT_LR_CENTER:
            layer_press_centre(); break;
      case DLG_BRCOSA:
            pressed_brcosa(); break;
      case DLG_PATTERN:
            choose_pattern(0); break;
      case DLG_BRUSH:
            choose_pattern(1); break;
      case DLG_SCALE:
            pressed_scale_size(TRUE); break;
      case DLG_SIZE:
            pressed_scale_size(FALSE); break;
      case DLG_NEW:
            generic_new_window(0); break;
      case DLG_FSEL:
            file_selector(mode); break;
      case DLG_FACTIONS:
            pressed_file_configure(); break;
      case DLG_TEXT:
            pressed_text(); break;
      case DLG_TEXT_FT:
            pressed_mt_text(); break;
      case DLG_LAYERS:
            if (mode) gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(
                  menu_widgets[MENU_LAYER]), FALSE); // Closed by toolbar
            else if (state) pressed_layers();
            else delete_layers_window();
            break;
      case DLG_INDEXED:
            pressed_quantize(mode); break;
      case DLG_ROTATE:
            pressed_rotate_free(); break;
      case DLG_INFO:
            pressed_information(); break;
      case DLG_PREFS:
            pressed_preferences(); break;
      case DLG_COLORS:
            colour_selector(mode); break;
      case DLG_PAL_SIZE:
            pressed_add_cols(); break;
      case DLG_PAL_SORT:
            pressed_sort_pal(); break;
      case DLG_PAL_SHIFTER:
            pressed_shifter(); break;
      case DLG_CHN_DEL:
            pressed_channel_delete(); break;
      case DLG_ANI:
            pressed_animate_window(); break;
      case DLG_ANI_VIEW:
            ani_but_preview(); break;
      case DLG_ANI_KEY:
            pressed_set_key_frame(); break;
      case DLG_ANI_KILLKEY:
            pressed_remove_key_frames(); break;
      case DLG_ABOUT:
            pressed_help(); break;
      case DLG_SKEW:
            pressed_skew(); break;
      case DLG_FLOOD:
            flood_settings(); break;
      case DLG_SMUDGE:
            smudge_settings(); break;
      case DLG_GRAD:
            gradient_setup(mode); break;
      case DLG_STEP:
            step_settings(); break;
      case DLG_FILT:
            blend_settings(); break;
      case DLG_TRACE:
            bkg_setup(); break;
      case FILT_2RGB:
            pressed_convert_rgb(); break;
      case FILT_INVERT:
            pressed_invert(); break;
      case FILT_GREY:
            pressed_greyscale(mode); break;
      case FILT_EDGE:
            pressed_edge_detect(); break;
      case FILT_DOG:
            pressed_dog(); break;
      case FILT_SHARPEN:
            pressed_sharpen(); break;
      case FILT_UNSHARP:
            pressed_unsharp(); break;
      case FILT_SOFTEN:
            pressed_soften(); break;
      case FILT_GAUSS:
            pressed_gauss(); break;
      case FILT_FX:
            pressed_fx(mode); break;
      case FILT_BACT:
            pressed_bacteria(); break;
      case FILT_THRES:
            pressed_threshold(); break;
      case FILT_UALPHA:
            pressed_unassociate(); break;
      case FILT_KUWAHARA:
            pressed_kuwahara(); break;
      }
}

static void menu_action(GtkMenuItem *widget, gpointer user_data, gint data)
{
      menu_item *item = user_data;

      action_dispatch(item->action, item->mode, item->radio_BTS < 0 ? TRUE :
            GTK_CHECK_MENU_ITEM(widget)->active, FALSE);
}

static GtkWidget *fill_menu(menu_item *items, GtkAccelGroup *accel_group)
{
      static char *bts[6] = { "<CheckItem>", NULL, "<Branch>", "<Tearoff>",
            "<Separator>", "<LastBranch>" };
      GtkItemFactoryEntry wf;
      GtkItemFactory *factory;
      GtkWidget *widget, *wrap, *rwidgets[MENU_RESIZE_MAX];
      char *radio[32], *rnames[MENU_RESIZE_MAX];
      int i, j, rn = 0;
#if GTK_MAJOR_VERSION == 1
      GSList *en;
#endif

      memset(&wf, 0, sizeof(wf));
      memset(radio, 0, sizeof(radio));
      rnames[0] = NULL;
      factory = gtk_item_factory_new(GTK_TYPE_MENU_BAR, "<main>", accel_group);
      for (; items->path; items++)
      {
            wf.path = _(items->path);
            wf.accelerator = items->shortcut;
            wf.callback = items->action ? menu_action : NULL;
//          wf.callback_action = 0;
            wf.item_type = items->radio_BTS < 1 ? bts[-items->radio_BTS & 15] :
                  radio[items->radio_BTS] ? radio[items->radio_BTS] :
                  "<RadioItem>";
            if ((items->radio_BTS > 0) && !radio[items->radio_BTS])
                  radio[items->radio_BTS] = wf.path;
#if GTK_MAJOR_VERSION == 2
            if (show_menu_icons && items->xpm_icon_image)
            {
                  wf.item_type = "<ImageItem>";
                  wf.extra_data = NULL;
                  gtk_item_factory_create_item(factory, &wf, items, 2);

                  widget = gtk_item_factory_get_item(factory,
                        ((GtkItemFactoryItem *)factory->items->data)->path);

                  gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(widget),
                        xpm_image(items->xpm_icon_image));
            }
            else
#endif
            gtk_item_factory_create_item(factory, &wf, items, 2);
            /* !!! Workaround - internal path may differ from input path */
            widget = gtk_item_factory_get_item(factory,
                  ((GtkItemFactoryItem *)factory->items->data)->path);
            mapped_dis_add(widget, items->actmap);
            /* For now, remember only requested widgets */
            if (items->ID) menu_widgets[items->ID] = widget;
            /* Remember what is size-aware */
            if (items->radio_BTS > -16) continue;
            rnames[rn] = wf.path;
            rwidgets[rn++] = widget;
      }

      /* Setup overflow submenu */
      r_menu[0].item = rwidgets[--rn];
      memset(&wf, 0, sizeof(wf));
      for (i = 0; i < rn; i++)
      {
            j = rn - i;
            widget = r_menu[j].item = rwidgets[i];
#if GTK_MAJOR_VERSION == 1
            en = gtk_accel_group_entries_from_object(GTK_OBJECT(widget));
      /* !!! This'll get confused if both underline and normal accelerators
       * are defined for the item */
            r_menu[j].key = en ? ((GtkAccelEntry *)en->data)->accelerator_key :
                  GDK_VoidSymbol;
#else
            r_menu[j].key = gtk_label_get_mnemonic_keyval(GTK_LABEL(
                  GTK_BIN(widget)->child));
#endif
            wf.path = g_strconcat(rnames[rn], rnames[i], NULL);
            wf.item_type = "<Branch>";
            gtk_item_factory_create_item(factory, &wf, NULL, 2);
            /* !!! Workaround - internal path may differ from input path */
            widget = gtk_item_factory_get_item(factory,
                  ((GtkItemFactoryItem *)factory->items->data)->path);
            g_free(wf.path);
            r_menu[j].fallback = widget;
            gtk_widget_hide(widget);
      }
      gtk_widget_hide(r_menu[0].item);

      /* Wrap menubar with resize-controller widget */
      widget = gtk_item_factory_get_widget(factory, "<main>");
      gtk_widget_show(widget);
      wrap = wj_size_bin();
      gtk_container_add(GTK_CONTAINER(wrap), widget);
      gtk_signal_connect(GTK_OBJECT(wrap), "size_request",
            GTK_SIGNAL_FUNC(smart_menu_size_req), NULL);
      gtk_signal_connect(GTK_OBJECT(wrap), "size_allocate",
            GTK_SIGNAL_FUNC(smart_menu_size_alloc), NULL);

      return (wrap);
}

static void pressed_pal_copy()
{
      png_color tpal[256];
      unsigned char *img, *tm2, *alpha = NULL, *mask = NULL, *mask2 = NULL;
      int i, j, x, y, w, h, step, bpp, n = 0;

      /* Source is selection */
      if ((marq_status == MARQUEE_DONE) || (poly_status == POLY_DONE))
      {
            x = marq_x1 < marq_x2 ? marq_x1 : marq_x2;
            y = marq_y1 < marq_y2 ? marq_y1 : marq_y2;
            w = abs(marq_x1 - marq_x2) + 1;
            h = abs(marq_y1 - marq_y2) + 1;
            bpp = MEM_BPP;
            step = mem_width;
            i = y * step + x;
            img = mem_img[mem_channel] + i * bpp;
            if ((mem_channel == CHN_IMAGE) && mem_img[CHN_ALPHA])
                  alpha = mem_img[CHN_ALPHA] + i;
            if ((mem_channel <= CHN_ALPHA) && mem_img[CHN_SEL])
                  mask = mem_img[CHN_SEL] + i;
            if (poly_status == POLY_DONE)
            {
                  mask2 = calloc(1, w * h);
                  if (mask2) poly_draw(TRUE, mask2, w);
            }
      }
      /* Source is clipboard */
      else if (mem_clipboard)
      {
            w = mem_clip_w;
            h = mem_clip_h;
            bpp = mem_clip_bpp;
            step = w;
            img = mem_clipboard;
            alpha = mem_clip_alpha;
            mask = mem_clip_mask;
      }
      /* No source available */
      else return;

      mem_pal_copy(tpal, mem_pal);
      tm2 = mask2;
      for (i = 0; i < h; i++)
      {
            for (j = 0; j < w; j++ , img += bpp)
            {
                  /* Skip empty parts */
                  if ((tm2 && !tm2[j]) || (mask && !mask[j]) ||
                        (alpha && !alpha[j])) continue;
                  if (bpp == 1) tpal[n] = mem_pal[*img];
                  else
                  {
                        tpal[n].red = img[0];
                        tpal[n].green = img[1];
                        tpal[n].blue = img[2];
                  }
                  if (++n >= 256) break;
            }
            if (n >= 256) break;
            img += (step - w) * bpp;
            if (alpha) alpha += step;
            if (mask) mask += step;
            if (tm2) tm2 += w;
      }
      if (mask2) free(mask2);
      if (!n) return; // Empty set - ignore

      spot_undo(UNDO_PAL);
      mem_pal_copy(mem_pal, tpal);
      mem_cols = n;
      update_stuff(UPD_PAL);
}

static void pressed_pal_paste()
{
      unsigned char *dest;
      int i, h, w = mem_cols;

      // If selection exists, use it to set the width of the clipboard
      if ((tool_type == TOOL_SELECT) && (marq_status == MARQUEE_DONE))
      {
            w = abs(marq_x1 - marq_x2) + 1;
            if (w > mem_cols) w = mem_cols;
      }
      h = (mem_cols + w - 1) / w;

      mem_clip_new(w, h, 3, CMASK_IMAGE, FALSE);
      if (!mem_clipboard)
      {
            memory_errors(1);
            return;
      }

      memset(dest = mem_clipboard, 0, w * h * 3);
      for (i = 0; i < mem_cols; i++ , dest += 3)
      {
            dest[0] = mem_pal[i].red;
            dest[1] = mem_pal[i].green;
            dest[2] = mem_pal[i].blue;
      }

      update_stuff(UPD_XCOPY);
      if (MEM_BPP == 3) pressed_paste(TRUE);
}

///   DOCK AREA

static void toggle_dock(int state, int internal)
{
#if 0 /* !!! Only commandline pane for now */
      static char **tab_buttons[DOCK_TOTAL] =
            { xpm_cline_xpm, xpm_config_xpm, xpm_layers_xpm };
      GtkWidget *notebook, *dock_page[DOCK_TOTAL];
      int i;
#endif
      GtkWidget *vbox, *pane = dock_pane;
      int w2;

      if (!pane ^ state) return;
      gdk_window_get_size(main_window->window, &w2, NULL);
      gtk_widget_ref(vbox_main);
      if (state)
      {
            /* First, create the dock pane */
            pane = gtk_hpaned_new();
            paned_mouse_fix(pane);
            /* Window size isn't yet valid */
            if (internal) gtk_object_get(GTK_OBJECT(main_window),
                  "default_width", &w2, NULL);
            w2 -= inifile_get_gint32("dockSize", 200);
            gtk_paned_set_position(GTK_PANED(pane), w2);

            vbox = gtk_vbox_new(FALSE, 0);      // New vbox for pane on the right
            gtk_paned_pack2(GTK_PANED(pane), vbox, FALSE, TRUE);

#if 0 /* !!! Only commandline pane for now */
            notebook = xpack(vbox, gtk_notebook_new());
            gtk_notebook_set_tab_pos(GTK_NOTEBOOK(notebook), GTK_POS_TOP);

            for (i = 0; i < DOCK_TOTAL; i++)
            {
                  if ((i == DOCK_CLINE) && (files_passed <= 1)) continue;
                  dock_page[i] = gtk_vbox_new(FALSE, 0);
                  gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
                        dock_page[i], xpm_image(tab_buttons[i]));
            }
#endif

            gtk_widget_show_all(pane);
            /* if (files_passed > 1) */ create_cline_area(vbox);

            /* Now, let's juggle the widgets */
            gtk_container_remove(GTK_CONTAINER(main_window), vbox_main);
            gtk_paned_pack1(GTK_PANED(pane), vbox_main, FALSE, TRUE);
            dock_pane = pane;
            dock_area = vbox;
            gtk_container_add(GTK_CONTAINER(main_window), pane);
      }
      else
      {
            /* Destroy dock pane */
            inifile_set_gint32("dockSize", w2 - GTK_PANED(pane)->child1_size);
            if (!internal) /* Else, don't bother destroying */
            {
                  dock_pane = dock_area = NULL;
                  gtk_widget_ref(pane);
                  gtk_container_remove(GTK_CONTAINER(main_window), pane);
                  gtk_container_remove(GTK_CONTAINER(pane), vbox_main);
                  gtk_widget_unref(pane);
                  gtk_container_add(GTK_CONTAINER(main_window), vbox_main);
            }
      }
      set_cursor(); /* Because canvas window is now a new one */
      gtk_widget_unref(vbox_main);
}

static void pressed_sel_ramp(int vert)
{
      unsigned char *c0, *c1, *img, *dest;
      int i, j, k, l, s1, s2, x, y, w, h, bpp = MEM_BPP;

      if (marq_status != MARQUEE_DONE) return;

      w = abs(marq_x1 - marq_x2) + 1;
      h = abs(marq_y1 - marq_y2) + 1;

      if (vert)         // Vertical ramp
      {
            k = h - 1; l = w;
            s1 = mem_width * bpp; s2 = bpp;
      }
      else              // Horizontal ramp
      {
            k = w - 1; l = h;
            s1 = bpp; s2 = mem_width * bpp;
      }

      spot_undo(UNDO_FILT);
      x = marq_x1 < marq_x2 ? marq_x1 : marq_x2;
      y = marq_y1 < marq_y2 ? marq_y1 : marq_y2;
      img = mem_img[mem_channel] + (y * mem_width + x) * bpp;
      for (i = 0; i < l; i++)
      {
            c0 = img; c1 = img + k * s1;
            dest = img;
            for (j = 1; j < k; j++)
            {
                  dest += s1;
                  dest[0] = (c0[0] * (k - j) + c1[0] * j) / k;
                  if (bpp == 1) continue;
                  dest[1] = (c0[1] * (k - j) + c1[1] * j) / k;
                  dest[2] = (c0[2] * (k - j) + c1[2] * j) / k;
            }
            img += s2;
      }

      update_stuff(UPD_IMG);
}

#undef _
#define _(X) X

/* !!! Keep MENU_RESIZE_MAX larger than number of resize-enabled items */

static menu_item main_menu[] = {
      { _("/_File"), -2 -16 },
      { _("/File/tear"), -3 },
      { _("/File/New"), -1, 0, 0, "<control>N", DLG_NEW, 0, xpm_new_xpm },
      { _("/File/Open ..."), -1, 0, 0, "<control>O", DLG_FSEL, FS_PNG_LOAD, xpm_open_xpm },
      { _("/File/Save"), -1, 0, 0, "<control>S", ACT_SAVE, 0, xpm_save_xpm },
      { _("/File/Save As ..."), -1, 0, 0, NULL, DLG_FSEL, FS_PNG_SAVE },
      { _("/File/sep1"), -4 },
      { _("/File/Export Undo Images ..."), -1, 0, NEED_UNDO, NULL, DLG_FSEL, FS_EXPORT_UNDO },
      { _("/File/Export Undo Images (reversed) ..."), -1, 0, NEED_UNDO, NULL, DLG_FSEL, FS_EXPORT_UNDO2 },
      { _("/File/Export ASCII Art ..."), -1, 0, NEED_IDX, NULL, DLG_FSEL, FS_EXPORT_ASCII },
      { _("/File/Export Animated GIF ..."), -1, 0, NEED_IDX, NULL, DLG_FSEL, FS_EXPORT_GIF },
      { _("/File/sep2"), -4 },
      { _("/File/Actions"), -2 },
      { _("/File/Actions/tear"), -3 },
      { _("/File/Actions/1"), -1, MENU_FACTION1, 0, NULL, ACT_FACTION, 1 },
      { _("/File/Actions/2"), -1, MENU_FACTION2, 0, NULL, ACT_FACTION, 2 },
      { _("/File/Actions/3"), -1, MENU_FACTION3, 0, NULL, ACT_FACTION, 3 },
      { _("/File/Actions/4"), -1, MENU_FACTION4, 0, NULL, ACT_FACTION, 4 },
      { _("/File/Actions/5"), -1, MENU_FACTION5, 0, NULL, ACT_FACTION, 5 },
      { _("/File/Actions/6"), -1, MENU_FACTION6, 0, NULL, ACT_FACTION, 6 },
      { _("/File/Actions/7"), -1, MENU_FACTION7, 0, NULL, ACT_FACTION, 7 },
      { _("/File/Actions/8"), -1, MENU_FACTION8, 0, NULL, ACT_FACTION, 8 },
      { _("/File/Actions/9"), -1, MENU_FACTION9, 0, NULL, ACT_FACTION, 9 },
      { _("/File/Actions/10"), -1, MENU_FACTION10, 0, NULL, ACT_FACTION, 10 },
      { _("/File/Actions/11"), -1, MENU_FACTION11, 0, NULL, ACT_FACTION, 11 },
      { _("/File/Actions/12"), -1, MENU_FACTION12, 0, NULL, ACT_FACTION, 12 },
      { _("/File/Actions/13"), -1, MENU_FACTION13, 0, NULL, ACT_FACTION, 13 },
      { _("/File/Actions/14"), -1, MENU_FACTION14, 0, NULL, ACT_FACTION, 14 },
      { _("/File/Actions/15"), -1, MENU_FACTION15, 0, NULL, ACT_FACTION, 15 },
      { _("/File/Actions/sepC"), -4, MENU_FACTION_S },
      { _("/File/Actions/Configure"), -1, 0, 0, NULL, DLG_FACTIONS, 0 },
      { _("/File/sepR"), -4, MENU_RECENT_S },
      { _("/File/1"), -1, MENU_RECENT1, 0, "<shift><control>F1", ACT_LOAD_RECENT, 1 },
      { _("/File/2"), -1, MENU_RECENT2, 0, "<shift><control>F2", ACT_LOAD_RECENT, 2 },
      { _("/File/3"), -1, MENU_RECENT3, 0, "<shift><control>F3", ACT_LOAD_RECENT, 3 },
      { _("/File/4"), -1, MENU_RECENT4, 0, "<shift><control>F4", ACT_LOAD_RECENT, 4 },
      { _("/File/5"), -1, MENU_RECENT5, 0, "<shift><control>F5", ACT_LOAD_RECENT, 5 },
      { _("/File/6"), -1, MENU_RECENT6, 0, "<shift><control>F6", ACT_LOAD_RECENT, 6 },
      { _("/File/7"), -1, MENU_RECENT7, 0, "<shift><control>F7", ACT_LOAD_RECENT, 7 },
      { _("/File/8"), -1, MENU_RECENT8, 0, "<shift><control>F8", ACT_LOAD_RECENT, 8 },
      { _("/File/9"), -1, MENU_RECENT9, 0, "<shift><control>F9", ACT_LOAD_RECENT, 9 },
      { _("/File/10"), -1, MENU_RECENT10, 0, "<shift><control>F10", ACT_LOAD_RECENT, 10 },
      { _("/File/11"), -1, MENU_RECENT11, 0, NULL, ACT_LOAD_RECENT, 11 },
      { _("/File/12"), -1, MENU_RECENT12, 0, NULL, ACT_LOAD_RECENT, 12 },
      { _("/File/13"), -1, MENU_RECENT13, 0, NULL, ACT_LOAD_RECENT, 13 },
      { _("/File/14"), -1, MENU_RECENT14, 0, NULL, ACT_LOAD_RECENT, 14 },
      { _("/File/15"), -1, MENU_RECENT15, 0, NULL, ACT_LOAD_RECENT, 15 },
      { _("/File/16"), -1, MENU_RECENT16, 0, NULL, ACT_LOAD_RECENT, 16 },
      { _("/File/17"), -1, MENU_RECENT17, 0, NULL, ACT_LOAD_RECENT, 17 },
      { _("/File/18"), -1, MENU_RECENT18, 0, NULL, ACT_LOAD_RECENT, 18 },
      { _("/File/19"), -1, MENU_RECENT19, 0, NULL, ACT_LOAD_RECENT, 19 },
      { _("/File/20"), -1, MENU_RECENT20, 0, NULL, ACT_LOAD_RECENT, 20 },
      { _("/File/sep3"), -4 },
      { _("/File/Quit"), -1, 0, 0, "<control>Q", ACT_QUIT, 1 },

      { _("/_Edit"), -2 -16 },
      { _("/Edit/tear"), -3 },
      { _("/Edit/Undo"), -1, 0, NEED_UNDO, "<control>Z", ACT_UNDO, 0, xpm_undo_xpm },
      { _("/Edit/Redo"), -1, 0, NEED_REDO, "<control>R", ACT_REDO, 0, xpm_redo_xpm },
      { _("/Edit/sep1"), -4 },
      { _("/Edit/Cut"), -1, 0, NEED_SEL2, "<control>X", ACT_COPY, 1, xpm_cut_xpm },
      { _("/Edit/Copy"), -1, 0, NEED_SEL2, "<control>C", ACT_COPY, 0, xpm_copy_xpm },
      { _("/Edit/Copy To Palette"), -1, 0, NEED_PSEL, NULL, ACT_COPY_PAL, 0 },
      { _("/Edit/Paste To Centre"), -1, 0, NEED_CLIP, "<control>V", ACT_PASTE, 1, xpm_paste_xpm },
      { _("/Edit/Paste To New Layer"), -1, 0, NEED_PCLIP, "<control><shift>V", ACT_LR_ADD, LR_PASTE },
      { _("/Edit/Paste"), -1, 0, NEED_CLIP, "<control>K", ACT_PASTE, 0 },
      { _("/Edit/Paste Text"), -1, 0, 0, "<control>T", DLG_TEXT, 0, xpm_text_xpm },
#ifdef U_FREETYPE
      { _("/Edit/Paste Text (FreeType)"), -1, 0, 0, "T", DLG_TEXT_FT, 0 },
#endif
      { _("/Edit/Paste Palette"), -1, 0, 0, NULL, ACT_PASTE_PAL, 0 },
      { _("/Edit/sep2"), -4 },
      { _("/Edit/Load Clipboard"), -2 },
      { _("/Edit/Load Clipboard/tear"), -3 },
      { _("/Edit/Load Clipboard/1"), -1, 0, 0, "<shift>F1", ACT_LOAD_CLIP, 1 },
      { _("/Edit/Load Clipboard/2"), -1, 0, 0, "<shift>F2", ACT_LOAD_CLIP, 2 },
      { _("/Edit/Load Clipboard/3"), -1, 0, 0, "<shift>F3", ACT_LOAD_CLIP, 3 },
      { _("/Edit/Load Clipboard/4"), -1, 0, 0, "<shift>F4", ACT_LOAD_CLIP, 4 },
      { _("/Edit/Load Clipboard/5"), -1, 0, 0, "<shift>F5", ACT_LOAD_CLIP, 5 },
      { _("/Edit/Load Clipboard/6"), -1, 0, 0, "<shift>F6", ACT_LOAD_CLIP, 6 },
      { _("/Edit/Load Clipboard/7"), -1, 0, 0, "<shift>F7", ACT_LOAD_CLIP, 7 },
      { _("/Edit/Load Clipboard/8"), -1, 0, 0, "<shift>F8", ACT_LOAD_CLIP, 8 },
      { _("/Edit/Load Clipboard/9"), -1, 0, 0, "<shift>F9", ACT_LOAD_CLIP, 9 },
      { _("/Edit/Load Clipboard/10"), -1, 0, 0, "<shift>F10", ACT_LOAD_CLIP, 10 },
      { _("/Edit/Load Clipboard/11"), -1, 0, 0, "<shift>F11", ACT_LOAD_CLIP, 11 },
      { _("/Edit/Load Clipboard/12"), -1, 0, 0, "<shift>F12", ACT_LOAD_CLIP, 12 },
      { _("/Edit/Save Clipboard"), -2 },
      { _("/Edit/Save Clipboard/tear"), -3 },
      { _("/Edit/Save Clipboard/1"), -1, 0, NEED_CLIP, "<control>F1", ACT_SAVE_CLIP, 1 },
      { _("/Edit/Save Clipboard/2"), -1, 0, NEED_CLIP, "<control>F2", ACT_SAVE_CLIP, 2 },
      { _("/Edit/Save Clipboard/3"), -1, 0, NEED_CLIP, "<control>F3", ACT_SAVE_CLIP, 3 },
      { _("/Edit/Save Clipboard/4"), -1, 0, NEED_CLIP, "<control>F4", ACT_SAVE_CLIP, 4 },
      { _("/Edit/Save Clipboard/5"), -1, 0, NEED_CLIP, "<control>F5", ACT_SAVE_CLIP, 5 },
      { _("/Edit/Save Clipboard/6"), -1, 0, NEED_CLIP, "<control>F6", ACT_SAVE_CLIP, 6 },
      { _("/Edit/Save Clipboard/7"), -1, 0, NEED_CLIP, "<control>F7", ACT_SAVE_CLIP, 7 },
      { _("/Edit/Save Clipboard/8"), -1, 0, NEED_CLIP, "<control>F8", ACT_SAVE_CLIP, 8 },
      { _("/Edit/Save Clipboard/9"), -1, 0, NEED_CLIP, "<control>F9", ACT_SAVE_CLIP, 9 },
      { _("/Edit/Save Clipboard/10"), -1, 0, NEED_CLIP, "<control>F10", ACT_SAVE_CLIP, 10 },
      { _("/Edit/Save Clipboard/11"), -1, 0, NEED_CLIP, "<control>F11", ACT_SAVE_CLIP, 11 },
      { _("/Edit/Save Clipboard/12"), -1, 0, NEED_CLIP, "<control>F12", ACT_SAVE_CLIP, 12 },
      { _("/Edit/Import Clipboard from System"), -1, 0, 0, NULL, ACT_LOAD_CLIP, -1 },
      { _("/Edit/Export Clipboard to System"), -1, 0, NEED_CLIP, NULL, ACT_SAVE_CLIP, -1 },
      { _("/Edit/sep3"), -4 },
      { _("/Edit/Choose Pattern ..."), -1, 0, 0, "F2", DLG_PATTERN, 0 },
      { _("/Edit/Choose Brush ..."), -1, 0, 0, "F3", DLG_BRUSH, 0 },

      { _("/_View"), -2 -16 },
      { _("/View/tear"), -3 },
      { _("/View/Show Main Toolbar"), 0, MENU_TBMAIN, 0, "F5", ACT_TBAR, TOOLBAR_MAIN },
      { _("/View/Show Tools Toolbar"), 0, MENU_TBTOOLS, 0, "F6", ACT_TBAR, TOOLBAR_TOOLS },
      { _("/View/Show Settings Toolbar"), 0, MENU_TBSET, 0, "F7", ACT_TBAR, TOOLBAR_SETTINGS },
      { _("/View/Show Dock"), 0, MENU_DOCK, 0, "F12", ACT_DOCK, 0 },
      { _("/View/Show Palette"), 0, MENU_SHOWPAL, 0, "F8", ACT_TBAR, TOOLBAR_PALETTE },
      { _("/View/Show Status Bar"), 0, MENU_SHOWSTAT, 0, NULL, ACT_TBAR, TOOLBAR_STATUS },
      { _("/View/sep1"), -4 },
      { _("/View/Toggle Image View"), -1, 0, 0, "Home", ACT_VIEW, 0 },
      { _("/View/Centralize Image"), 0, MENU_CENTER, 0, NULL, ACT_CENTER, 0 },
      { _("/View/Show Zoom Grid"), 0, MENU_SHOWGRID, 0, NULL, ACT_GRID, 0 },
      { _("/View/Configure Grid ..."), -1, 0, 0, NULL, DLG_COLORS, COLSEL_GRID },
      { _("/View/Tracing Image ..."), -1, 0, 0, NULL, DLG_TRACE, 0 },
      { _("/View/sep2"), -4 },
      { _("/View/View Window"), 0, MENU_VIEW, 0, "V", ACT_VWWIN, 0 },
      { _("/View/Horizontal Split"), 0, 0, 0, "H", ACT_VWSPLIT, 0 },
      { _("/View/Focus View Window"), 0, MENU_VWFOCUS, 0, NULL, ACT_VWFOCUS, 0 },
      { _("/View/sep3"), -4 },
      { _("/View/Pan Window"), -1, 0, 0, "End", ACT_PAN, 0, xpm_pan_xpm },
      { _("/View/Layers Window"), 0, MENU_LAYER, 0, "L", DLG_LAYERS, 0 },

      { _("/_Image"), -2 -16 },
      { _("/Image/tear"), -3 },
      { _("/Image/Convert To RGB"), -1, 0, NEED_IDX, NULL, FILT_2RGB, 0 },
      { _("/Image/Convert To Indexed ..."), -1, 0, NEED_24, NULL, DLG_INDEXED, 0 },
      { _("/Image/sep1"), -4 },
      { _("/Image/Scale Canvas ..."), -1, 0, 0, NULL, DLG_SCALE, 0 },
      { _("/Image/Resize Canvas ..."), -1, 0, 0, NULL, DLG_SIZE, 0 },
      { _("/Image/Crop"), -1, 0, NEED_CROP, "<control><shift>X", ACT_CROP, 0 },
      { _("/Image/sep2"), -4 },
      { _("/Image/Flip Vertically"), -1, 0, 0, NULL, ACT_FLIP_V, 0 },
      { _("/Image/Flip Horizontally"), -1, 0, 0, "<control>M", ACT_FLIP_H, 0 },
      { _("/Image/Rotate Clockwise"), -1, 0, 0, NULL, ACT_ROTATE, 0 },
      { _("/Image/Rotate Anti-Clockwise"), -1, 0, 0, NULL, ACT_ROTATE, 1 },
      { _("/Image/Free Rotate ..."), -1, 0, 0, NULL, DLG_ROTATE, 0 },
      { _("/Image/Skew ..."), -1, 0, 0, NULL, DLG_SKEW, 0 },
      { _("/Image/sep3"), -4 },
      { _("/Image/Information ..."), -1, 0, 0, "<control>I", DLG_INFO, 0 },
      { _("/Image/Preferences ..."), -1, MENU_PREFS, 0, "<control>P", DLG_PREFS, 0 },

      { _("/_Selection"), -2 -16 },
      { _("/Selection/tear"), -3 },
      { _("/Selection/Select All"), -1, 0, 0, "<control>A", ACT_SELECT, 1 },
      { _("/Selection/Select None (Esc)"), -1, 0, NEED_MARQ, "<shift><control>A", ACT_SELECT, 0 },
      { _("/Selection/Lasso Selection"), -1, 0, NEED_LAS2, NULL, ACT_LASSO, 0, xpm_lasso_xpm },
      { _("/Selection/Lasso Selection Cut"), -1, 0, NEED_LASSO, NULL, ACT_LASSO, 1 },
      { _("/Selection/sep1"), -4 },
      { _("/Selection/Outline Selection"), -1, 0, NEED_SEL2, "<control>T", ACT_OUTLINE, 0, xpm_rect1_xpm },
      { _("/Selection/Fill Selection"), -1, 0, NEED_SEL2, "<shift><control>T", ACT_OUTLINE, 1, xpm_rect2_xpm },
      { _("/Selection/Outline Ellipse"), -1, 0, NEED_SEL, "<control>L", ACT_ELLIPSE, 0, xpm_ellipse2_xpm },
      { _("/Selection/Fill Ellipse"), -1, 0, NEED_SEL, "<shift><control>L", ACT_ELLIPSE, 1, xpm_ellipse_xpm },
      { _("/Selection/sep2"), -4 },
      { _("/Selection/Flip Vertically"), -1, 0, NEED_CLIP, NULL, ACT_SEL_FLIP_V, 0, xpm_flip_vs_xpm },
      { _("/Selection/Flip Horizontally"), -1, 0, NEED_CLIP, NULL, ACT_SEL_FLIP_H, 0, xpm_flip_hs_xpm },
      { _("/Selection/Rotate Clockwise"), -1, 0, NEED_CLIP, NULL, ACT_SEL_ROT, 0, xpm_rotate_cs_xpm },
      { _("/Selection/Rotate Anti-Clockwise"), -1, 0, NEED_CLIP, NULL, ACT_SEL_ROT, 1, xpm_rotate_as_xpm },
      { _("/Selection/sep3"), -4 },
      { _("/Selection/Horizontal Ramp"), -1, 0, NEED_SEL, NULL, ACT_RAMP, 0 },
      { _("/Selection/Vertical Ramp"), -1, 0, NEED_SEL, NULL, ACT_RAMP, 1 },
      { _("/Selection/sep4"), -4 },
      { _("/Selection/Alpha Blend A,B"), -1, 0, NEED_ACLIP, NULL, ACT_SEL_ALPHA_AB, 0 },
      { _("/Selection/Move Alpha to Mask"), -1, 0, NEED_CLIP, NULL, ACT_SEL_ALPHAMASK, 0 },
      { _("/Selection/Mask Colour A,B"), -1, 0, NEED_CLIP, NULL, ACT_SEL_MASK_AB, 0 },
      { _("/Selection/Unmask Colour A,B"), -1, 0, NEED_CLIP, NULL, ACT_SEL_MASK_AB, 255 },
      { _("/Selection/Mask All Colours"), -1, 0, NEED_CLIP, NULL, ACT_SEL_MASK, 0 },
      { _("/Selection/Clear Mask"), -1, 0, NEED_CLIP, NULL, ACT_SEL_MASK, 255 },

      { _("/_Palette"), -2 -16 },
      { _("/Palette/tear"), -3 },
      { _("/Palette/Open ..."), -1, 0, 0, NULL, DLG_FSEL, FS_PALETTE_LOAD, xpm_open_xpm },
      { _("/Palette/Save As ..."), -1, 0, 0, NULL, DLG_FSEL, FS_PALETTE_SAVE, xpm_save_xpm },
      { _("/Palette/Load Default"), -1, 0, 0, NULL, ACT_PAL_DEF, 0 },
      { _("/Palette/sep1"), -4 },
      { _("/Palette/Mask All"), -1, 0, 0, NULL, ACT_PAL_MASK, 255 },
      { _("/Palette/Mask None"), -1, 0, 0, NULL, ACT_PAL_MASK, 0 },
      { _("/Palette/sep2"), -4 },
      { _("/Palette/Swap A & B"), -1, 0, 0, "X", ACT_SWAP_AB, 0 },
      { _("/Palette/Edit Colour A & B ..."), -1, 0, 0, "<control>E", DLG_COLORS, COLSEL_EDIT_AB },
      { _("/Palette/Dither A"), -1, 0, NEED_24, NULL, ACT_DITHER_A, 0 },
      { _("/Palette/Palette Editor ..."), -1, 0, 0, "<control>W", DLG_COLORS, COLSEL_EDIT_ALL },
      { _("/Palette/Set Palette Size ..."), -1, 0, 0, NULL, DLG_PAL_SIZE, 0 },
      { _("/Palette/Merge Duplicate Colours"), -1, 0, NEED_IDX, NULL, ACT_PAL_MERGE, 0 },
      { _("/Palette/Remove Unused Colours"), -1, 0, NEED_IDX, NULL, ACT_PAL_CLEAN, 0 },
      { _("/Palette/sep3"), -4 },
      { _("/Palette/Create Quantized ..."), -1, 0, NEED_24, NULL, DLG_INDEXED, 1 },
      { _("/Palette/sep4"), -4 },
      { _("/Palette/Sort Colours ..."), -1, 0, 0, NULL, DLG_PAL_SORT, 0 },
      { _("/Palette/Palette Shifter ..."), -1, 0, 0, NULL, DLG_PAL_SHIFTER, 0 },

      { _("/Effe_cts"), -2 -16 },
      { _("/Effects/tear"), -3 },
      { _("/Effects/Transform Colour ..."), -1, 0, 0, "<control><shift>C", DLG_BRCOSA, 0, xpm_brcosa_xpm },
      { _("/Effects/Invert"), -1, 0, 0, "<control><shift>I", FILT_INVERT, 0 },
      { _("/Effects/Greyscale"), -1, 0, 0, "<control>G", FILT_GREY, 0 },
      { _("/Effects/Greyscale (Gamma corrected)"), -1, 0, 0, "<control><shift>G", FILT_GREY, 1 },
      { _("/Effects/Isometric Transformation"), -2 },
      { _("/Effects/Isometric Transformation/tear"), -3 },
      { _("/Effects/Isometric Transformation/Left Side Down"), -1, 0, 0, NULL, ACT_ISOMETRY, 0 },
      { _("/Effects/Isometric Transformation/Right Side Down"), -1, 0, 0, NULL, ACT_ISOMETRY, 1 },
      { _("/Effects/Isometric Transformation/Top Side Right"), -1, 0, 0, NULL, ACT_ISOMETRY, 2 },
      { _("/Effects/Isometric Transformation/Bottom Side Right"), -1, 0, 0, NULL, ACT_ISOMETRY, 3 },
      { _("/Effects/sep1"), -4 },
      { _("/Effects/Edge Detect ..."), -1, 0, NEED_NOIDX, NULL, FILT_EDGE, 0 },
      { _("/Effects/Difference of Gaussians ..."), -1, 0, NEED_NOIDX, NULL, FILT_DOG, 0 },
      { _("/Effects/Sharpen ..."), -1, 0, NEED_NOIDX, NULL, FILT_SHARPEN, 0 },
      { _("/Effects/Unsharp Mask ..."), -1, 0, NEED_NOIDX, NULL, FILT_UNSHARP, 0 },
      { _("/Effects/Soften ..."), -1, 0, NEED_NOIDX, NULL, FILT_SOFTEN, 0 },
      { _("/Effects/Gaussian Blur ..."), -1, 0, NEED_NOIDX, NULL, FILT_GAUSS, 0 },
      { _("/Effects/Kuwahara-Nagao Blur ..."), -1, 0, NEED_24, NULL, FILT_KUWAHARA, 0 },
      { _("/Effects/Emboss"), -1, 0, NEED_NOIDX, NULL, FILT_FX, FX_EMBOSS },
      { _("/Effects/Dilate"), -1, 0, NEED_NOIDX, NULL, FILT_FX, FX_DILATE },
      { _("/Effects/Erode"), -1, 0, NEED_NOIDX, NULL, FILT_FX, FX_ERODE },
      { _("/Effects/sep2"), -4 },
      { _("/Effects/Bacteria ..."), -1, 0, NEED_IDX, NULL, FILT_BACT, 0 },

      { _("/Cha_nnels"), -2 -16 },
      { _("/Channels/tear"), -3 },
      { _("/Channels/New ..."), -1, 0, 0, NULL, ACT_CHANNEL, -1 },
      { _("/Channels/Load ..."), -1, 0, 0, NULL, DLG_FSEL, FS_CHANNEL_LOAD, xpm_open_xpm },
      { _("/Channels/Save As ..."), -1, 0, 0, NULL, DLG_FSEL, FS_CHANNEL_SAVE, xpm_save_xpm },
      { _("/Channels/Delete ..."), -1, 0, NEED_CHAN, NULL, DLG_CHN_DEL, -1 },
      { _("/Channels/sep1"), -4 },
      { _("/Channels/Edit Image"), 1, MENU_CHAN0, 0, NULL, ACT_CHANNEL, CHN_IMAGE },
      { _("/Channels/Edit Alpha"), 1, MENU_CHAN1, 0, NULL, ACT_CHANNEL, CHN_ALPHA },
      { _("/Channels/Edit Selection"), 1, MENU_CHAN2, 0, NULL, ACT_CHANNEL, CHN_SEL },
      { _("/Channels/Edit Mask"), 1, MENU_CHAN3, 0, NULL, ACT_CHANNEL, CHN_MASK },
      { _("/Channels/sep2"), -4 },
      { _("/Channels/Hide Image"), 0, MENU_DCHAN0, 0, NULL, ACT_SET_OVERLAY, 1 },
      { _("/Channels/Disable Alpha"), 0, MENU_DCHAN1, 0, NULL, ACT_CHN_DIS, CHN_ALPHA },
      { _("/Channels/Disable Selection"), 0, MENU_DCHAN2, 0, NULL, ACT_CHN_DIS, CHN_SEL },
      { _("/Channels/Disable Mask"), 0, MENU_DCHAN3, 0, NULL, ACT_CHN_DIS, CHN_MASK },
      { _("/Channels/sep3"), -4 },
      { _("/Channels/Couple RGBA Operations"), 0, MENU_RGBA, 0, NULL, ACT_SET_RGBA, 0 },
      { _("/Channels/Threshold ..."), -1, 0, 0, NULL, FILT_THRES, 0 },
      { _("/Channels/Unassociate Alpha"), -1, 0, NEED_RGBA, NULL, FILT_UALPHA, 0 },
      { _("/Channels/sep4"), -4 },
      { _("/Channels/View Alpha as an Overlay"), 0, 0, 0, NULL, ACT_SET_OVERLAY, 0 },
      { _("/Channels/Configure Overlays ..."), -1, 0, 0, NULL, DLG_COLORS, COLSEL_OVERLAYS },

      { _("/_Layers"), -2 -16 },
      { _("/Layers/tear"), -3 },
      { _("/Layers/New Layer"), -1, 0, 0, NULL, ACT_LR_ADD, LR_NEW, xpm_new_xpm },
      { _("/Layers/Save"), -1, 0, 0, "<shift><control>S", ACT_LR_SAVE, 0, xpm_save_xpm },
      { _("/Layers/Save As ..."), -1, 0, 0, NULL, DLG_FSEL, FS_LAYER_SAVE },
      { _("/Layers/Save Composite Image ..."), -1, 0, 0, NULL, DLG_FSEL, FS_COMPOSITE_SAVE },
      { _("/Layers/Composite to New Layer"), -1, 0, 0, NULL, ACT_LR_ADD, LR_COMP },
      { _("/Layers/Remove All Layers"), -1, 0, 0, NULL, ACT_LR_DEL, 1 },
      { _("/Layers/sep1"), -4 },
      { _("/Layers/Configure Animation ..."), -1, 0, 0, NULL, DLG_ANI, 0 },
      { _("/Layers/Preview Animation ..."), -1, 0, 0, NULL, DLG_ANI_VIEW, 0 },
      { _("/Layers/Set Key Frame ..."), -1, 0, 0, NULL, DLG_ANI_KEY, 0 },
      { _("/Layers/Remove All Key Frames ..."), -1, 0, 0, NULL, DLG_ANI_KILLKEY, 0 },

      { _("/More..."), -2 -16 }, /* This will hold overflow submenu */

      { _("/_Help"), -5 },
      { _("/Help/Documentation"), -1, 0, 0, NULL, ACT_DOCS, 0 },
      { _("/Help/About"), -1, MENU_HELP, 0, "F1", DLG_ABOUT, 0 },
      { _("/Help/sep1"), -4 },
      { _("/Help/Rebind Shortcut Keycodes"), -1, 0, 0, NULL, ACT_REBIND_KEYS, 0 },

      { NULL, 0 }
      };

#undef _
#define _(X) __(X)

void main_init()
{
      GtkRequisition req;
      GdkPixmap *icon_pix = NULL;
      GtkAdjustment *adj;
      GtkWidget *menubar1, *hbox_bar, *hbox_bottom;
      GtkAccelGroup *accel_group;
      char txt[PATHBUF];
      int i;


      gdk_rgb_init();
      init_tablet();                            // Set up the tablet

      toolbar_boxes[TOOLBAR_MAIN] = NULL;       // Needed as test to avoid segfault in toolbar.c

      accel_group = gtk_accel_group_new ();


///   MAIN WINDOW

      main_window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
      gtk_widget_set_usize(main_window, 100, 100);          // Set minimum width/height
      win_restore_pos(main_window, "window", 0, 0, 630, 400);
      gtk_window_set_title (GTK_WINDOW (main_window), VERSION );

      /* !!! Konqueror needs GDK_ACTION_MOVE to do a drop; we never accept
       * move as a move, so have to do some non-default processing - WJ */
      gtk_drag_dest_set(main_window, GTK_DEST_DEFAULT_HIGHLIGHT |
            GTK_DEST_DEFAULT_MOTION, &uri_list, 1, GDK_ACTION_COPY |
            GDK_ACTION_MOVE);
      gtk_signal_connect(GTK_OBJECT(main_window), "drag_data_received",
            GTK_SIGNAL_FUNC(drag_n_drop_received), NULL);
      gtk_signal_connect(GTK_OBJECT(main_window), "drag_drop",
            GTK_SIGNAL_FUNC(drag_n_drop_tried), NULL);

      vbox_main = gtk_vbox_new (FALSE, 0);
      gtk_widget_show (vbox_main);
      gtk_container_add (GTK_CONTAINER (main_window), vbox_main);

// we need to realize the window because we use pixmaps for 
// items on the toolbar & menus in the context of it
      gtk_widget_realize( main_window );

///   MENU

      menubar1 = fill_menu(main_menu, accel_group);

      gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_widgets[MENU_RGBA]), RGBA_mode);
      gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_widgets[MENU_VWFOCUS]), vw_focus_on);

      gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_widgets[MENU_CENTER]),
            canvas_image_centre);
      gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_widgets[MENU_SHOWGRID]),
            mem_show_grid);

      pack(vbox_main, menubar1);

      gtk_accel_group_lock( accel_group );      // Stop dynamic allocation of accelerators during runtime
      gtk_window_add_accel_group(GTK_WINDOW(main_window), accel_group);

///   TOOLBARS

      toolbar_init(vbox_main);

///   PALETTE

      hbox_bottom = xpack(vbox_main, gtk_hbox_new(FALSE, 0));
      gtk_widget_show(hbox_bottom);

      toolbar_palette_init(hbox_bottom);

      vbox_right = xpack(hbox_bottom, gtk_vbox_new(FALSE, 0));
      gtk_widget_show(vbox_right);


///   DRAWING AREA

      main_vsplit = gtk_hpaned_new ();
      paned_mouse_fix(main_vsplit);
      gtk_widget_show (main_vsplit);
      gtk_widget_ref(main_vsplit);
      gtk_object_sink(GTK_OBJECT(main_vsplit));

      main_hsplit = gtk_vpaned_new ();
      paned_mouse_fix(main_hsplit);
      gtk_widget_show (main_hsplit);
      gtk_widget_ref(main_hsplit);
      gtk_object_sink(GTK_OBJECT(main_hsplit));

      main_split = main_vsplit;

//    VIEW WINDOW

      vw_scrolledwindow = gtk_scrolled_window_new (NULL, NULL);
      gtk_widget_show (vw_scrolledwindow);
      gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(vw_scrolledwindow),
            GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
      gtk_widget_ref(vw_scrolledwindow);
      gtk_object_sink(GTK_OBJECT(vw_scrolledwindow));

      vw_drawing = gtk_drawing_area_new ();
      gtk_widget_set_usize( vw_drawing, 1, 1 );
      gtk_widget_show( vw_drawing );
      gtk_scrolled_window_add_with_viewport( GTK_SCROLLED_WINDOW(vw_scrolledwindow), vw_drawing);
      fix_scroll(vw_scrolledwindow);

      init_view();

//    MAIN WINDOW

      drawing_canvas = gtk_drawing_area_new ();
      gtk_widget_set_usize( drawing_canvas, 48, 48 );
      gtk_widget_show( drawing_canvas );

      scrolledwindow_canvas = xpack(vbox_right, gtk_scrolled_window_new(NULL, NULL));
      gtk_widget_show (scrolledwindow_canvas);

      /* Handle "changed" signal only in GTK+2, because in GTK+1 resizes are
       * tracked by configure handler, and forced realign from there looks
       * better than idle-time realign from here - WJ */
      gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwindow_canvas),
            GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
      adj = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(scrolledwindow_canvas));
#if GTK_MAJOR_VERSION == 2
      gtk_signal_connect(GTK_OBJECT(adj), "changed",
            GTK_SIGNAL_FUNC(vw_focus_idle), NULL);
#endif
      gtk_signal_connect(GTK_OBJECT(adj), "value_changed",
            GTK_SIGNAL_FUNC(vw_focus_idle), NULL);
      adj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(scrolledwindow_canvas));
#if GTK_MAJOR_VERSION == 2
      gtk_signal_connect(GTK_OBJECT(adj), "changed",
            GTK_SIGNAL_FUNC(vw_focus_idle), NULL);
#endif
      gtk_signal_connect(GTK_OBJECT(adj), "value_changed",
            GTK_SIGNAL_FUNC(vw_focus_idle), NULL);

      gtk_scrolled_window_add_with_viewport( GTK_SCROLLED_WINDOW(scrolledwindow_canvas),
            drawing_canvas);
      fix_scroll(scrolledwindow_canvas);

      gtk_signal_connect( GTK_OBJECT(drawing_canvas), "configure_event",
            GTK_SIGNAL_FUNC (configure_canvas), NULL );
      gtk_signal_connect( GTK_OBJECT(drawing_canvas), "expose_event",
            GTK_SIGNAL_FUNC (expose_canvas), NULL );
      gtk_signal_connect( GTK_OBJECT(drawing_canvas), "button_press_event",
            GTK_SIGNAL_FUNC (canvas_button), NULL );
      gtk_signal_connect( GTK_OBJECT(drawing_canvas), "button_release_event",
            GTK_SIGNAL_FUNC (canvas_button), NULL );
      gtk_signal_connect( GTK_OBJECT(drawing_canvas), "motion_notify_event",
            GTK_SIGNAL_FUNC (canvas_motion), NULL );
      gtk_signal_connect( GTK_OBJECT(drawing_canvas), "enter_notify_event",
            GTK_SIGNAL_FUNC (canvas_enter), NULL );
      gtk_signal_connect( GTK_OBJECT(drawing_canvas), "leave_notify_event",
            GTK_SIGNAL_FUNC (canvas_left), NULL );
#if GTK_MAJOR_VERSION == 2
      gtk_signal_connect( GTK_OBJECT(drawing_canvas), "scroll_event",
            GTK_SIGNAL_FUNC (canvas_scroll_gtk2), NULL );
#endif

      gtk_widget_set_events (drawing_canvas, GDK_ALL_EVENTS_MASK);
      gtk_widget_set_extension_events (drawing_canvas, GDK_EXTENSION_EVENTS_CURSOR);

////  STATUS BAR

      hbox_bar = pack_end(vbox_right, gtk_hbox_new(FALSE, 0));
      if ( toolbar_status[TOOLBAR_STATUS] ) gtk_widget_show (hbox_bar);


      for (i = 0; i < STATUS_ITEMS; i++)
      {
            label_bar[i] = gtk_label_new("");
            gtk_misc_set_alignment(GTK_MISC(label_bar[i]),
                  (i == STATUS_CURSORXY) || (i == STATUS_UNDOREDO) ? 0.5 : 0.0, 0.0);
            gtk_widget_show(label_bar[i]);
      }
      for (i = 0; i < STATUS_ITEMS; i++)
      {
            if (i < 3) pack(hbox_bar, label_bar[i]);
            else pack_end(hbox_bar, label_bar[(STATUS_ITEMS - 1) + 3 - i]);
      }
      if ( status_on[STATUS_CURSORXY] ) gtk_widget_set_usize(label_bar[STATUS_CURSORXY], 90, -2);
      if ( status_on[STATUS_UNDOREDO] ) gtk_widget_set_usize(label_bar[STATUS_UNDOREDO], 70, -2);
      gtk_label_set_text( GTK_LABEL(label_bar[STATUS_UNDOREDO]), "0+0" );



      /* To prevent statusbar wobbling */
      gtk_widget_size_request(hbox_bar, &req);
      gtk_widget_set_usize(hbox_bar, -1, req.height);


/////////   End of main window widget setup

      gtk_signal_connect( GTK_OBJECT (main_window), "delete_event",
            GTK_SIGNAL_FUNC (delete_event), NULL );
      gtk_signal_connect( GTK_OBJECT(main_window), "key_press_event",
            GTK_SIGNAL_FUNC (handle_keypress), NULL );

      mapped_item_state(0);

      recent_files = recent_files < 0 ? 0 : recent_files > 20 ? 20 : recent_files;
      update_recent_files();
      toolbar_boxes[TOOLBAR_STATUS] = hbox_bar; // Hide status bar
      main_hidden[0] = menubar1;                // Hide menu bar

      view_hide();                              // Hide paned view initially

      // Display dock area if requested
      show_dock = (files_passed > 1);
      if (show_dock)
      {
            toggle_dock(show_dock, TRUE);
            gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(
                  menu_widgets[MENU_DOCK]), show_dock);
            // !!! Filelist in the dock should have focus now
      }
      else
      {
            /* !!! Dock has no other function for now */
            gtk_widget_set_sensitive(menu_widgets[MENU_DOCK], FALSE);
            // Stops first icon in toolbar being selected
            gtk_widget_grab_focus(scrolledwindow_canvas);
      }

      gtk_widget_show(main_window);

      /* !!! Have to wait till canvas is displayed, to init keyboard */
      fill_keycodes(main_keys);

      gdk_window_raise( main_window->window );

      icon_pix = gdk_pixmap_create_from_xpm_d( main_window->window, NULL, NULL, icon_xpm );
      gdk_window_set_icon( main_window->window, NULL, icon_pix, NULL );
//    gdk_pixmap_unref(icon_pix);

      set_cursor();
      init_status_bar();
      init_factions();                    // Initialize file action menu

      snprintf(txt, PATHBUF, "%s%c.clipboard", get_home_directory(), DIR_SEP);
      strncpy0(mem_clip_file, inifile_get("clipFilename", txt), PATHBUF);

      change_to_tool(DEFAULT_TOOL_ICON);

      toolbar_showhide();
      if (viewer_mode) toggle_view();
}

void spot_undo(int mode)
{
      mem_undo_next(mode);          // Do memory stuff for undo
      update_menus();               // Update menu undo issues
}

#ifdef U_NLS
void setup_language()               // Change language
{
      char *txt = inifile_get( "languageSETTING", "system" ), txt2[64];

      if ( strcmp( "system", txt ) != 0 )
      {
            snprintf( txt2, 60, "LANGUAGE=%s", txt );
            putenv( txt2 );
            snprintf( txt2, 60, "LANG=%s", txt );
            putenv( txt2 );
            snprintf( txt2, 60, "LC_ALL=%s", txt );
            putenv( txt2 );
      }
#if GTK_MAJOR_VERSION == 1
      else  txt="";

      setlocale(LC_ALL, txt);
#endif
      /* !!! Slow or not, but NLS is *really* broken on GTK+1 without it - WJ */
      gtk_set_locale(); // GTK+1 hates this - it really slows things down
}
#endif

void update_titlebar()        // Update filename in titlebar
{
      char txt[300], txt2[PATHTXT], *extra;


      if (!main_window) return;

      gtkuncpy(txt2, mem_filename, PATHTXT);
      extra = mem_changed ? _("(Modified)") : "-";

      snprintf( txt, 290, "%s %s %s", VERSION, extra, txt2[0] ? txt2 :
            _("Untitled"));

      gtk_window_set_title (GTK_WINDOW (main_window), txt );
}

void notify_changed()         // Image/palette has just changed - update vars as needed
{
      mem_tempname = NULL;
      if ( mem_changed != 1 )
      {
            mem_changed = 1;
            update_titlebar();
      }
}

void notify_unchanged()       // Image/palette has just been unchanged (saved) - update vars as needed
{
      if ( mem_changed != 0 )
      {
            mem_changed = 0;
            update_titlebar();
      }
}


Generated by  Doxygen 1.6.0   Back to index