/* The GIMP -- an image manipulation program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "appenv.h"
#include "actionarea.h"
#include "brushes.h"
#include "brush_select.h"
#include "buildmenu.h"
#include "colormaps.h"
#include "disp_callbacks.h"
#include "errors.h"
#include "paint_funcs.h"


#define STD_CELL_WIDTH    24
#define STD_CELL_HEIGHT   24

#define STD_BRUSH_COLUMNS 5
#define STD_BRUSH_ROWS    5

#define MAX_WIN_WIDTH     (STD_CELL_WIDTH * NUM_BRUSH_COLUMNS)
#define MAX_WIN_HEIGHT    (STD_CELL_HEIGHT * NUM_BRUSH_ROWS)
#define MARGIN_WIDTH      3
#define MARGIN_HEIGHT     3

#define BRUSH_EVENT_MASK  GDK_EXPOSURE_MASK | \
                          GDK_BUTTON_PRESS_MASK | \
			  GDK_BUTTON_RELEASE_MASK | \
                          GDK_BUTTON1_MOTION_MASK | \
			  GDK_ENTER_NOTIFY_MASK

/*  local function prototypes  */
static void display_brush                (BrushSelectP, GBrushP, int, int);
static void display_brushes              (BrushSelectP);
static void display_setup                (BrushSelectP);
static void preview_calc_scrollbar       (BrushSelectP);
static void brush_select_show_selected   (BrushSelectP, int, int);
static void update_active_brush_field    (BrushSelectP);
static void brush_select_close_callback  (GtkWidget *, gpointer);
static void brush_select_refresh_callback(GtkWidget *, gpointer);
static void paint_mode_menu_callback     (GtkWidget *, gpointer);
static gint brush_select_events          (GtkWidget *, GdkEvent *, BrushSelectP);
static gint brush_select_resize		 (GtkWidget *, GdkEvent *, BrushSelectP);

static gint brush_select_delete_callback        (GtkWidget *, GdkEvent *, gpointer);
static void preview_scroll_update        (GtkAdjustment *, gpointer);
static void opacity_scale_update         (GtkAdjustment *, gpointer);
static void spacing_scale_update         (GtkAdjustment *, gpointer);

/*  the option menu items -- the paint modes  */
static MenuItem option_items[] =
{
  { "Normal", 0, 0, paint_mode_menu_callback, (gpointer) NORMAL_MODE, NULL, NULL },
  { "Dissolve", 0, 0, paint_mode_menu_callback, (gpointer) DISSOLVE_MODE, NULL, NULL },
  { "Behind", 0, 0, paint_mode_menu_callback, (gpointer) BEHIND_MODE, NULL, NULL },
  { "Multiply", 0, 0, paint_mode_menu_callback, (gpointer) MULTIPLY_MODE, NULL, NULL },
  { "Screen", 0, 0, paint_mode_menu_callback, (gpointer) SCREEN_MODE, NULL, NULL },
  { "Overlay", 0, 0, paint_mode_menu_callback, (gpointer) OVERLAY_MODE, NULL, NULL },
  { "Difference", 0, 0, paint_mode_menu_callback, (gpointer) DIFFERENCE_MODE, NULL, NULL },
  { "Addition", 0, 0, paint_mode_menu_callback, (gpointer) ADDITION_MODE, NULL, NULL },
  { "Subtract", 0, 0, paint_mode_menu_callback, (gpointer) SUBTRACT_MODE, NULL, NULL },
  { "Darken Only", 0, 0, paint_mode_menu_callback, (gpointer) DARKEN_ONLY_MODE, NULL, NULL },
  { "Lighten Only", 0, 0, paint_mode_menu_callback, (gpointer) LIGHTEN_ONLY_MODE, NULL, NULL },
  { "Hue", 0, 0, paint_mode_menu_callback, (gpointer) HUE_MODE, NULL, NULL },
  { "Saturation", 0, 0, paint_mode_menu_callback, (gpointer) SATURATION_MODE, NULL, NULL },
  { "Color", 0, 0, paint_mode_menu_callback, (gpointer) COLOR_MODE, NULL, NULL },
  { "Value", 0, 0, paint_mode_menu_callback, (gpointer) VALUE_MODE, NULL, NULL },
  { NULL, 0, 0, NULL, NULL, NULL, NULL }
};

/*  the action area structure  */
static ActionAreaItem action_items[] =
{
  { "Close", brush_select_close_callback, NULL, NULL },
  { "Refresh", brush_select_refresh_callback, NULL, NULL },
};

static double old_opacity;
static int old_spacing;
static int old_paint_mode;
static int last_x, last_y;
static int scroll_row, scroll_col, scroll_index;

int NUM_BRUSH_COLUMNS=5;
int NUM_BRUSH_ROWS=5;


GtkWidget *
create_paint_mode_menu (MenuItemCallback callback)
{
  GtkWidget *menu;
  int i;

  for (i = 0; i <= VALUE_MODE; i++)
    option_items[i].callback = callback;

  menu = build_menu (option_items, NULL);

  return menu;
}


BrushSelectP
brush_select_new ()
{
  BrushSelectP bsp;
  GtkWidget *vbox;
  GtkWidget *hbox;
  GtkWidget *sbar;
  GtkWidget *label;
  GtkWidget *menu;
  GtkWidget *util_box;
  GtkWidget *option_menu;
  GtkWidget *slider;
  GBrushP active;

  bsp = g_malloc (sizeof (_BrushSelect));
  bsp->redraw = TRUE;

  /*  The shell and main vbox  */
  bsp->shell = gtk_dialog_new ();
  gtk_window_set_title (GTK_WINDOW (bsp->shell), "Brush Selection");
  gtk_window_set_policy(GTK_WINDOW(bsp->shell), TRUE, TRUE, FALSE);
  vbox = gtk_vbox_new (FALSE, 1);
  gtk_container_border_width (GTK_CONTAINER (vbox), 2);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (bsp->shell)->vbox), vbox, TRUE, TRUE, 0);

  /* handle the wm close signal */
  gtk_signal_connect (GTK_OBJECT (bsp->shell), "delete_event",
		      GTK_SIGNAL_FUNC (brush_select_delete_callback),
		      bsp);

  /*  The horizontal box containing preview & scrollbar & options box */
  hbox = gtk_hbox_new (FALSE, 2);
  gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0);
  bsp->frame = gtk_frame_new (NULL);
  gtk_frame_set_shadow_type (GTK_FRAME (bsp->frame), GTK_SHADOW_IN);
  gtk_box_pack_start (GTK_BOX (hbox), bsp->frame, TRUE, TRUE, 0);
  bsp->sbar_data = GTK_ADJUSTMENT (gtk_adjustment_new (0, 0, MAX_WIN_HEIGHT, 1, 1, MAX_WIN_HEIGHT));
  sbar = gtk_vscrollbar_new (bsp->sbar_data);
  gtk_signal_connect (GTK_OBJECT (bsp->sbar_data), "value_changed",
		      (GtkSignalFunc) preview_scroll_update, bsp);
  gtk_box_pack_start (GTK_BOX (hbox), sbar, FALSE, FALSE, 0);


  /*  Create the brush preview window and the underlying image  */
  /*  Get the maximum brush extents  */

  bsp->cell_width = STD_CELL_WIDTH;
  bsp->cell_height = STD_CELL_HEIGHT;

  bsp->width = MAX_WIN_WIDTH;
  bsp->height = MAX_WIN_HEIGHT;

  bsp->preview = gtk_preview_new (GTK_PREVIEW_GRAYSCALE);
  gtk_preview_size (GTK_PREVIEW (bsp->preview), bsp->width, bsp->height);
  gtk_widget_set_events (bsp->preview, BRUSH_EVENT_MASK);
  
  gtk_signal_connect (GTK_OBJECT (bsp->preview), "event",
		      (GtkSignalFunc) brush_select_events,
		      bsp);
  gtk_signal_connect_after (GTK_OBJECT(bsp->frame), "size_allocate",
		       (GtkSignalFunc) brush_select_resize,
		       bsp);

  gtk_container_add (GTK_CONTAINER (bsp->frame), bsp->preview);
  gtk_widget_show (bsp->preview);

  gtk_widget_show (sbar);
  gtk_widget_show (bsp->frame);

  /*  options box  */
  bsp->options_box = gtk_vbox_new (TRUE, 4);
  gtk_box_pack_start (GTK_BOX (hbox), bsp->options_box, TRUE, TRUE, 0);

  /*  Create the active brush label  */
  util_box = gtk_hbox_new (FALSE, 5);
  gtk_box_pack_start (GTK_BOX (bsp->options_box), util_box, FALSE, FALSE, 0);

  bsp->brush_name = gtk_label_new ("Active");
  gtk_box_pack_start (GTK_BOX (util_box), bsp->brush_name, FALSE, FALSE, 2);
  bsp->brush_size = gtk_label_new ("(0x0)");
  gtk_box_pack_start (GTK_BOX (util_box), bsp->brush_size, FALSE, FALSE, 2);

  gtk_widget_show (bsp->brush_name);
  gtk_widget_show (bsp->brush_size);
  gtk_widget_show (util_box);

  /*  Create the paint mode option menu  */
  old_paint_mode = get_brush_paint_mode ();

  util_box = gtk_hbox_new (FALSE, 2);
  gtk_box_pack_start (GTK_BOX (bsp->options_box), util_box, FALSE, FALSE, 0);
  label = gtk_label_new ("Mode:");
  gtk_box_pack_start (GTK_BOX (util_box), label, FALSE, FALSE, 2);
  menu = create_paint_mode_menu (paint_mode_menu_callback);
  option_menu = gtk_option_menu_new ();
  gtk_box_pack_start (GTK_BOX (util_box), option_menu, FALSE, FALSE, 2);

  gtk_widget_show (label);
  gtk_widget_show (option_menu);
  gtk_widget_show (util_box);
  gtk_option_menu_set_menu (GTK_OPTION_MENU (option_menu), menu);

  /*  Create the opacity scale widget  */
  old_opacity = get_brush_opacity ();

  util_box = gtk_hbox_new (FALSE, 2);
  gtk_box_pack_start (GTK_BOX (bsp->options_box), util_box, FALSE, FALSE, 0);
  label = gtk_label_new ("Opacity:");
  gtk_box_pack_start (GTK_BOX (util_box), label, FALSE, FALSE, 2);
  bsp->opacity_data = GTK_ADJUSTMENT (gtk_adjustment_new (100.0, 0.0, 100.0, 1.0, 1.0, 0.0));
  slider = gtk_hscale_new (bsp->opacity_data);
  gtk_box_pack_start (GTK_BOX (util_box), slider, TRUE, TRUE, 0);
  gtk_scale_set_value_pos (GTK_SCALE (slider), GTK_POS_TOP);
  gtk_range_set_update_policy (GTK_RANGE (slider), GTK_UPDATE_DELAYED);
  gtk_signal_connect (GTK_OBJECT (bsp->opacity_data), "value_changed",
		      (GtkSignalFunc) opacity_scale_update, bsp);

  gtk_widget_show (label);
  gtk_widget_show (slider);
  gtk_widget_show (util_box);

  /*  Create the brush spacing scale widget  */
  old_spacing = get_brush_spacing ();

  util_box = gtk_hbox_new (FALSE, 2);
  gtk_box_pack_start (GTK_BOX (bsp->options_box), util_box, FALSE, FALSE, 0);
  label = gtk_label_new ("Spacing:");
  gtk_box_pack_start (GTK_BOX (util_box), label, FALSE, FALSE, 2);
  bsp->spacing_data = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 1000.0, 1.0, 1.0, 0.0));
  slider = gtk_hscale_new (bsp->spacing_data);
  gtk_box_pack_start (GTK_BOX (util_box), slider, TRUE, TRUE, 0);
  gtk_scale_set_value_pos (GTK_SCALE (slider), GTK_POS_TOP);
  gtk_range_set_update_policy (GTK_RANGE (slider), GTK_UPDATE_DELAYED);
  gtk_signal_connect (GTK_OBJECT (bsp->spacing_data), "value_changed",
		      (GtkSignalFunc) spacing_scale_update, bsp);

  gtk_widget_show (label);
  gtk_widget_show (slider);
  gtk_widget_show (util_box);

  /*  The action area  */
  action_items[0].user_data = bsp;
  action_items[1].user_data = bsp;
  build_action_area (GTK_DIALOG (bsp->shell), action_items, 2, 0);

  gtk_widget_show (bsp->options_box);
  gtk_widget_show (hbox);
  gtk_widget_show (vbox);
  gtk_widget_show (bsp->shell);

  /* calculate the scrollbar */
  preview_calc_scrollbar (bsp);

  /*  render the brushes into the newly created image structure  */
  display_brushes (bsp);

  /*  update the active selection  */
  active = get_active_brush ();
  if (active)
    {
      int old_value = bsp->redraw;
      bsp->redraw = FALSE;
      brush_select_select (bsp, active->index);
      bsp->redraw = old_value;
    }

  return bsp;
}


void
brush_select_select (BrushSelectP bsp,
		     int          index)
{
  int row, col;

  update_active_brush_field (bsp);
  row = index / NUM_BRUSH_COLUMNS;
  col = index - row * NUM_BRUSH_COLUMNS;

  brush_select_show_selected (bsp, row, col);
}


void
brush_select_free (BrushSelectP bsp)
{
  if (bsp)
    g_free (bsp);
}


static void
clear_brush (BrushSelectP bsp,
	     int          col,
	     int          row)
{
  int i;
  unsigned char * buf;
  int offset_x, offset_y;
  int yend;
  int ystart;

  buf = (unsigned char *) g_malloc (sizeof (char) * bsp->cell_width);
  memset (buf, 255, sizeof (char) * bsp->cell_width);

  offset_x = col * bsp->cell_width;
  offset_y = row * bsp->cell_height - bsp->scroll_offset;

  ystart = BOUNDS (offset_y, 0, bsp->preview->requisition.height);
  yend = BOUNDS (offset_y + bsp->cell_height, 0, bsp->preview->requisition.height);

  for (i = ystart; i < yend; i++)
    gtk_preview_draw_row (GTK_PREVIEW (bsp->preview), buf, offset_x, i, bsp->cell_width);

  free (buf);
}


static void
display_brush (BrushSelectP bsp,
	       GBrushP      brush,
	       int          col,
	       int          row)
{
  TempBuf * brush_buf;
  unsigned char * src, *s;
  unsigned char * buf, *b;
  int width, height;
  int offset_x, offset_y;
  int yend;
  int ystart;
  int i, j;

  buf = (unsigned char *) g_malloc (sizeof (char) * bsp->cell_width);

  brush_buf = brush->mask;

  /*  calculate the offset into the image  */
  width = (brush_buf->width > bsp->cell_width) ? bsp->cell_width :
    brush_buf->width;
  height = (brush_buf->height > bsp->cell_height) ? bsp->cell_height :
    brush_buf->height;

  offset_x = col * bsp->cell_width + ((bsp->cell_width - width) >> 1);
  offset_y = row * bsp->cell_height + ((bsp->cell_height - height) >> 1)
    - bsp->scroll_offset;

  ystart = BOUNDS (offset_y, 0, bsp->preview->requisition.height);
  yend = BOUNDS (offset_y + height, 0, bsp->preview->requisition.height);

  /*  Get the pointer into the brush mask data  */
  src = mask_buf_data (brush_buf) +
    (brush->off_y + ystart - offset_y) * brush_buf->width +
    brush->off_x * brush_buf->bytes;

  for (i = ystart; i < yend; i++)
    {
      /*  Invert the mask for display.  We're doing this because
       *  a value of 255 in the  mask means it is full intensity.
       *  However, it makes more sense for full intensity to show
       *  up as black in this brush preview window...
       */
      s = src;
      b = buf;
      for (j = 0; j < width; j++)
	*b++ = 255 - *s++;

      gtk_preview_draw_row (GTK_PREVIEW (bsp->preview), buf, offset_x, i, width);

      src += brush_buf->width;
    }

  g_free (buf);
}


static void
display_setup (BrushSelectP bsp)
{
  unsigned char * buf;
  int i;

  buf = (unsigned char *) g_malloc (sizeof (char) * bsp->preview->requisition.width);

  /*  Set the buffer to white  */
  memset (buf, 255, bsp->preview->requisition.width);

  /*  Set the image buffer to white  */
  for (i = 0; i < bsp->preview->requisition.height; i++)
    gtk_preview_draw_row (GTK_PREVIEW (bsp->preview), buf, 0, i, bsp->preview->requisition.width);

  g_free (buf);
}


static void
display_brushes (BrushSelectP bsp)
{
  link_ptr list = brush_list;    /*  the global brush list  */
  int row, col;
  GBrushP brush;

  /*  If there are no brushes, insensitize widgets  */
  if (brush_list == NULL)
    {
      gtk_widget_set_sensitive (bsp->options_box, FALSE);
      return;
    }
  /*  Else, sensitize widgets  */
  else
    gtk_widget_set_sensitive (bsp->options_box, TRUE);

  /*  setup the display area  */
  display_setup (bsp);

  row = col = 0;
  while (list)
    {
      brush = (GBrushP) list->data;

      /*  Display the brush  */
      display_brush (bsp, brush, col, row);

      /*  increment the counts  */
      if (++col == NUM_BRUSH_COLUMNS)
	{
	  row ++;
	  col = 0;
	}

      list = next_item (list);
    }
}


static void
brush_select_show_selected (BrushSelectP bsp,
			    int          row,
			    int          col)
{
  GdkRectangle area;
  static int old_row = 0;
  static int old_col = 0;
  unsigned char * buf;
  int yend;
  int ystart;
  int offset_x, offset_y;
  int i;

  buf = (unsigned char *) g_malloc (sizeof (char) * bsp->cell_width);

  if (old_col != col || old_row != row)
    {
      /*  remove the old selection  */
      offset_x = old_col * bsp->cell_width;
      offset_y = old_row * bsp->cell_height - bsp->scroll_offset;

      ystart = BOUNDS (offset_y , 0, bsp->preview->requisition.height);
      yend = BOUNDS (offset_y + bsp->cell_height, 0, bsp->preview->requisition.height);

      /*  set the buf to white  */
      memset (buf, 255, bsp->cell_width);

      for (i = ystart; i < yend; i++)
	{
	  if (i == offset_y || i == (offset_y + bsp->cell_height - 1))
	    gtk_preview_draw_row (GTK_PREVIEW (bsp->preview), buf, offset_x, i, bsp->cell_width);
	  else
	    {
	      gtk_preview_draw_row (GTK_PREVIEW (bsp->preview), buf, offset_x, i, 1);
	      gtk_preview_draw_row (GTK_PREVIEW (bsp->preview), buf, offset_x + bsp->cell_width - 1, i, 1);
	    }
	}

      if (bsp->redraw)
	{
	  area.x = offset_x;
	  area.y = ystart;
	  area.width = bsp->cell_width;
	  area.height = (yend - ystart);
	  gtk_widget_draw (bsp->preview, &area);
	}
    }

  /*  make the new selection  */
  offset_x = col * bsp->cell_width;
  offset_y = row * bsp->cell_height - bsp->scroll_offset;

  ystart = BOUNDS (offset_y , 0, bsp->preview->requisition.height);
  yend = BOUNDS (offset_y + bsp->cell_height, 0, bsp->preview->requisition.height);

  /*  set the buf to black  */
  memset (buf, 0, bsp->cell_width);

  for (i = ystart; i < yend; i++)
    {
      if (i == offset_y || i == (offset_y + bsp->cell_height - 1))
	gtk_preview_draw_row (GTK_PREVIEW (bsp->preview), buf, offset_x, i, bsp->cell_width);
      else
	{
	  gtk_preview_draw_row (GTK_PREVIEW (bsp->preview), buf, offset_x, i, 1);
	  gtk_preview_draw_row (GTK_PREVIEW (bsp->preview), buf, offset_x + bsp->cell_width - 1, i, 1);
	}
    }

  if (bsp->redraw)
    {
      area.x = offset_x;
      area.y = ystart;
      area.width = bsp->cell_width;
      area.height = (yend - ystart);
      gtk_widget_draw (bsp->preview, &area);
    }

  old_row = row;
  old_col = col;

  g_free (buf);
}


static void
preview_calc_scrollbar (BrushSelectP bsp)
{
  int num_rows;
  int page_size;
  int max;

  bsp->scroll_offset = 0;
  num_rows = (num_brushes + NUM_BRUSH_COLUMNS - 1) / NUM_BRUSH_COLUMNS;
  max = num_rows * bsp->cell_width;
  if (!num_rows) num_rows = 1;
  page_size = bsp->preview->allocation.height;

  bsp->sbar_data->value = bsp->scroll_offset;
  bsp->sbar_data->upper = max;
  bsp->sbar_data->page_size = (page_size < max) ? page_size : max;
  bsp->sbar_data->page_increment = (page_size >> 1);
  bsp->sbar_data->step_increment = bsp->cell_width;

  gtk_signal_emit_by_name (GTK_OBJECT (bsp->sbar_data), "changed");
}

static gint 
brush_select_resize (GtkWidget *widget, 
		     GdkEvent *event, 
		     BrushSelectP bsp)
{
   NUM_BRUSH_COLUMNS = (gint)((widget->allocation.width - 4) / STD_CELL_WIDTH);
   NUM_BRUSH_ROWS = (num_brushes + NUM_BRUSH_COLUMNS - 1) / NUM_BRUSH_COLUMNS;
   
   bsp->width = widget->allocation.width - 4;
   bsp->height = widget->allocation.height - 4;
 
   gtk_preview_size (GTK_PREVIEW (bsp->preview), bsp->width, bsp->height);
   
   /*  recalculate scrollbar extents  */
   preview_calc_scrollbar (bsp);
 
   /*  render the patterns into the newly created image structure  */
   display_brushes (bsp);
 
   /*  update the display  */   
   if (bsp->redraw)
	gtk_widget_draw (bsp->preview, NULL);
   
   return FALSE;
}
 
static void
update_active_brush_field (BrushSelectP bsp)
{
  GBrushP brush;
  char buf[32];

  brush = get_active_brush ();

  if (!brush)
    return;

  /*  Set brush name  */
  gtk_label_set (GTK_LABEL (bsp->brush_name), brush->name);

  /*  Set brush size  */
  sprintf (buf, "(%d X %d)", brush->mask->width, brush->mask->height);
  gtk_label_set (GTK_LABEL (bsp->brush_size), buf);

  /*  Set brush spacing  */
  bsp->spacing_data->value = get_brush_spacing ();
  gtk_signal_emit_by_name (GTK_OBJECT (bsp->spacing_data), "value_changed");
}


static gint
brush_select_events (GtkWidget    *widget,
		     GdkEvent     *event,
		     BrushSelectP  bsp)
{
  GdkEventButton *bevent;
  GdkEventMotion *mevent;
  GBrushP brush;
  int row, col, index;
  int x_extent, y_extent;
  int tx, ty;

  switch (event->type)
    {
    case GDK_EXPOSE:
      break;

    case GDK_BUTTON_PRESS:
      bevent = (GdkEventButton *) event;

      if (bevent->button == 1)
	{
	  col = bevent->x / bsp->cell_width;
	  row = (bevent->y + bsp->scroll_offset) / bsp->cell_height;
	  index = row * NUM_BRUSH_COLUMNS + col;

	  /*  Update these variables for scrolling in the brush subwindows  */
	  last_x = bevent->x;
	  last_y = bevent->y;
	  scroll_row = row;
	  scroll_col = col;
	  scroll_index = index;

	  if ((brush = get_brush_by_index (index)))
	    {
	      gdk_pointer_grab (bsp->preview->window, FALSE,
				(GDK_POINTER_MOTION_HINT_MASK |
				 GDK_BUTTON1_MOTION_MASK |
				 GDK_BUTTON_RELEASE_MASK),
				NULL, NULL, bevent->time);

	      /*  Make this brush the active brush  */
	      select_brush (brush);

	      brush->off_x = BOUNDS (brush->mask->width - bsp->cell_width,
				     0, brush->mask->width) >> 2;
	      brush->off_y = BOUNDS (brush->mask->height - bsp->cell_height,
				     0, brush->mask->height) >> 2;
	    }
	}
      break;

    case GDK_BUTTON_RELEASE:
      bevent = (GdkEventButton *) event;

      if ((brush = get_brush_by_index (scroll_index)))
	{
	  brush->off_x = brush->off_y = 0;
	  clear_brush (bsp, scroll_col, scroll_row);
	  display_brush (bsp, brush, scroll_col, scroll_row);
	  brush_select_show_selected (bsp, scroll_row, scroll_col);
	  gdk_pointer_ungrab (bevent->time);
	}
      break;

    case GDK_MOTION_NOTIFY:
      mevent = (GdkEventMotion *) event;
      if (mevent->is_hint)
	{
	  gdk_window_get_pointer (widget->window, &tx, &ty, NULL);
	  mevent->x = tx;
	  mevent->y = ty;
	}

      if ((brush = get_brush_by_index (scroll_index)))
	{
	  /*  What are the brush subwindow scrolling extents?  */
	  x_extent = brush->mask->width - bsp->cell_width;
	  if (x_extent < 0) x_extent = 0;
	  y_extent = brush->mask->height - bsp->cell_height;
	  if (y_extent < 0) y_extent = 0;

	  /*  Update the subwindow offsets for the brush  */
	  brush->off_x = BOUNDS (brush->off_x - (mevent->x - last_x), 0, x_extent);
	  brush->off_y = BOUNDS (brush->off_y - (mevent->y - last_y), 0, y_extent);

	  last_x = mevent->x;
	  last_y = mevent->y;

	  display_brush (bsp, brush, scroll_col, scroll_row);
	  brush_select_show_selected (bsp, scroll_row, scroll_col);
	}
      break;
    case GDK_DELETE:
      g_warning ("test");
      break;

    default:
      break;
    }

  return FALSE;
}

static gint
brush_select_delete_callback (GtkWidget *w, GdkEvent *e, gpointer data)
{
  brush_select_close_callback (w, data);

  return FALSE;
}

static void
brush_select_close_callback (GtkWidget *w,
			     gpointer   client_data)
{
  BrushSelectP bsp;

  bsp = (BrushSelectP) client_data;

  old_paint_mode = get_brush_paint_mode ();
  old_opacity = get_brush_opacity ();
  old_spacing = get_brush_spacing ();

  if (GTK_WIDGET_VISIBLE (bsp->shell))
    gtk_widget_hide (bsp->shell);
}


static void
brush_select_refresh_callback (GtkWidget *w,
			       gpointer   client_data)
{
  BrushSelectP bsp;
  GBrushP active;

  bsp = (BrushSelectP) client_data;

  /*  re-init the brush list  */
  brushes_init ();

  /*  recalculate scrollbar extents  */
  preview_calc_scrollbar (bsp);

  /*  render the brushes into the newly created image structure  */
  display_brushes (bsp);

  /*  update the active selection  */
  active = get_active_brush ();
  if (active)
    brush_select_select (bsp, active->index);

  /*  update the display  */
  if (bsp->redraw)
    gtk_widget_draw (bsp->preview, NULL);
}


static void
preview_scroll_update (GtkAdjustment *adjustment,
		       gpointer       data)
{
  BrushSelectP bsp;
  GBrushP active;
  int row, col;

  bsp = data;

  if (bsp)
    {
      bsp->scroll_offset = adjustment->value;
      display_brushes (bsp);

      active = get_active_brush ();
      if (active)
	{
	  row = active->index / NUM_BRUSH_COLUMNS;
	  col = active->index - row * NUM_BRUSH_COLUMNS;
	  brush_select_show_selected (bsp, row, col);
	}

      if (bsp->redraw)
	gtk_widget_draw (bsp->preview, NULL);
    }
}

static void
paint_mode_menu_callback (GtkWidget *w,
			  gpointer   client_data)
{
  set_brush_paint_mode ((long) client_data);
}


static void
opacity_scale_update (GtkAdjustment *adjustment,
		      gpointer       data)
{
  set_brush_opacity (adjustment->value / 100.0);
}


static void
spacing_scale_update (GtkAdjustment *adjustment,
		      gpointer       data)
{
  set_brush_spacing ((int) adjustment->value);
}


