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

#include "appenv.h"
#include "brush_scale.h"
#include "brush_edit.h"
#include "brush_select.h"
#include "dialog_handler.h"
#include "gimpbrushgenerated.h"
#include "gimpbrushlist.h"
#include "gimpbrushpipe.h"
#include "gimpbrushpipeP.h"
#include "gimpcontext.h"
#include "gimpdnd.h"
#include "gimplist.h"
#include "gimprc.h"
#include "gimpui.h"
#include "paint_options.h"
#include "session.h"

#include "config.h"
#include "libgimp/gimpintl.h"

#define MIN_CELL_SIZE     25
#define MAX_CELL_SIZE     25  /*  disable variable brush preview size  */ 

#define STD_BRUSH_COLUMNS 5
#define STD_BRUSH_ROWS    5

/* how long to wait after mouse-down before showing brush popup */
#define POPUP_DELAY_MS      150

#define MAX_WIN_WIDTH(bsp)     (MIN_CELL_SIZE * ((bsp)->NUM_BRUSH_COLUMNS))
#define MAX_WIN_HEIGHT(bsp)    (MIN_CELL_SIZE * ((bsp)->NUM_BRUSH_ROWS))
#define MARGIN_WIDTH      1
#define MARGIN_HEIGHT     1

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

/*  the pixmaps for the [scale|pipe]_indicators  */
#define indicator_width  7
#define indicator_height 7

#define WHT {255, 255, 255}
#define BLK {  0,   0,   0}
#define RED {255, 127, 127}

/*  local function prototypes  */
static void     brush_change_callbacks             (BrushSelect      *bsp,
						    gboolean          closing);

static GimpBrush * brush_select_drag_brush         (GtkWidget        *widget,
						    gpointer          data);
static void     brush_select_drop_brush            (GtkWidget        *widget,
						    GimpBrush        *brush,
						    gpointer          data);
static void     brush_select_brush_changed         (GimpContext      *context,
						    GimpBrush        *brush,
						    BrushSelect      *bsp);
static void     brush_select_opacity_changed       (GimpContext      *context,
						    gdouble           opacity,
						    BrushSelect      *bsp);
static void     brush_select_paint_mode_changed    (GimpContext      *context,
						    LayerModeEffects  paint_mode,
						    BrushSelect      *bsp);
static void     brush_select_select                (BrushSelect      *bsp,
						    GimpBrush        *brush);

static void     brush_select_brush_dirty_callback  (GimpBrush        *brush,
						    BrushSelect      *bsp);
static void     connect_signals_to_brush           (GimpBrush        *brush, 
						    BrushSelect      *bsp);
static void     disconnect_signals_from_brush      (GimpBrush        *brush,
						    BrushSelect      *bsp);
static void     brush_added_callback               (GimpBrushList    *list,
						    GimpBrush        *brush,
						    BrushSelect      *bsp);
static void     brush_removed_callback             (GimpBrushList    *list,
						    GimpBrush        *brush,
						    BrushSelect      *bsp);

static void     draw_brush_popup                   (GtkPreview       *preview,
						    GimpBrush        *brush,
						    gint              width,
						    gint              height);
static gint     brush_popup_anim_timeout           (gpointer          data);
static gboolean brush_popup_timeout                (gpointer          data);
static void     brush_popup_open                   (BrushSelect *,
						    gint, gint, GimpBrush *);
static void     brush_popup_close                  (BrushSelect *);

static void     display_setup                      (BrushSelect *);
static void     display_brush                      (BrushSelect *,
						    GimpBrush *, gint, gint);
static void     do_display_brush                   (GimpBrush   *brush,
						    BrushSelect *bsp);
static void     display_brushes                    (BrushSelect *);

static void     brush_select_show_selected         (BrushSelect *, gint, gint);
static void     update_active_brush_field          (BrushSelect *);
static void     preview_calc_scrollbar             (BrushSelect *);

static gint     brush_select_resize		   (GtkWidget *, GdkEvent *,
						    BrushSelect *);
static gint     brush_select_events                (GtkWidget *, GdkEvent *,
						    BrushSelect *);

/* static void  brush_select_map_callback          (GtkWidget *,
                                                    BrushSelect *); */
static void     brush_select_scroll_update         (GtkAdjustment *, gpointer);
static void     opacity_scale_update               (GtkAdjustment *, gpointer);
static void     paint_mode_menu_callback           (GtkWidget *, gpointer);
static void     spacing_scale_update               (GtkAdjustment *, gpointer);

static void     brush_select_close_callback        (GtkWidget *, gpointer);
static void     brush_select_refresh_callback      (GtkWidget *, gpointer);
static void     brush_select_new_brush_callback    (GtkWidget *, gpointer);
static void     brush_select_edit_brush_callback   (GtkWidget *, gpointer);
static void     brush_select_delete_brush_callback (GtkWidget *, gpointer);

static unsigned char scale_indicator_bits[7][7][3] = 
{
  { WHT, WHT, WHT, WHT, WHT, WHT, WHT },
  { WHT, WHT, WHT, BLK, WHT, WHT, WHT },
  { WHT, WHT, WHT, BLK, WHT, WHT, WHT },
  { WHT, BLK, BLK, BLK, BLK, BLK, WHT },
  { WHT, WHT, WHT, BLK, WHT, WHT, WHT },
  { WHT, WHT, WHT, BLK, WHT, WHT, WHT },
  { WHT, WHT, WHT, WHT, WHT, WHT, WHT }
};

static unsigned char pipe_indicator_bits[7][7][3] = 
{
  { WHT, WHT, WHT, WHT, WHT, WHT, WHT },
  { WHT, WHT, WHT, WHT, WHT, WHT, RED },
  { WHT, WHT, WHT, WHT, WHT, RED, RED },
  { WHT, WHT, WHT, WHT, RED, RED, RED },
  { WHT, WHT, WHT, RED, RED, RED, RED },
  { WHT, WHT, RED, RED, RED, RED, RED },
  { WHT, RED, RED, RED, RED, RED, RED }
};

static unsigned char scale_pipe_indicator_bits[7][7][3] = 
{
  { WHT, WHT, WHT, WHT, WHT, WHT, WHT },
  { WHT, WHT, WHT, BLK, WHT, WHT, RED },
  { WHT, WHT, WHT, BLK, WHT, RED, RED },
  { WHT, BLK, BLK, BLK, BLK, BLK, RED },
  { WHT, WHT, WHT, BLK, RED, RED, RED },
  { WHT, WHT, RED, BLK, RED, RED, RED },
  { WHT, RED, RED, RED, RED, RED, RED }
};

/*  dnd stuff  */
static GtkTargetEntry preview_target_table[] =
{
  GIMP_TARGET_BRUSH
};
static guint preview_n_targets = (sizeof (preview_target_table) /
                                  sizeof (preview_target_table[0]));

/*  The main brush selection dialog  */
BrushSelect *brush_select_dialog = NULL;

/*  local variables  */

/*  List of active dialogs  */
GSList *brush_active_dialogs = NULL;

/*  Brush editor dialog  */
static BrushEditGeneratedWindow *brush_edit_generated_dialog;

void
brush_dialog_create (void)
{
  if (! brush_select_dialog)
    {
      brush_select_dialog = brush_select_new (NULL, NULL, 0.0, 0, 0);
    }
  else
    {
      if (!GTK_WIDGET_VISIBLE (brush_select_dialog->shell))
	gtk_widget_show (brush_select_dialog->shell);
      else
	gdk_window_raise (brush_select_dialog->shell->window);
    }
}

void
brush_dialog_free ()
{
  if (brush_select_dialog)
    {
      session_get_window_info (brush_select_dialog->shell,
			       &brush_select_session_info);

      /*  save the size of the preview  */
      brush_select_session_info.width =
	brush_select_dialog->preview->allocation.width;
      brush_select_session_info.height =
	brush_select_dialog->preview->allocation.height;

      brush_select_free (brush_select_dialog);
      brush_select_dialog = NULL;
    }
}

/*  If title == NULL then it is the main brush dialog  */
BrushSelect *
brush_select_new (gchar   *title,
		  /*  These are the required initial vals
		   *  If init_name == NULL then use current brush
		   */
		  gchar   *init_name,
		  gdouble  init_opacity,
		  gint     init_spacing,
		  gint     init_mode)
{
  BrushSelect *bsp;
  GtkWidget *vbox;
  GtkWidget *hbox;
  GtkWidget *frame;
  GtkWidget *sbar;
  GtkWidget *sep;
  GtkWidget *table;
  GtkWidget *util_box;
  GtkWidget *option_menu;
  GtkWidget *menu;
  GtkWidget *slider;
  GtkWidget *button;

  GimpBrush *active = NULL;

  static gboolean first_call = TRUE;

  bsp = g_new (BrushSelect, 1);
  bsp->callback_name          = NULL;
  bsp->dnd_brush              = NULL;
  bsp->brush_popup            = NULL;
  bsp->popup_timeout_tag      = 0;
  bsp->popup_anim_timeout_tag = 0;
  bsp->scroll_offset          = 0;
  bsp->old_row                = 0;
  bsp->old_col                = 0;
  bsp->NUM_BRUSH_COLUMNS      = STD_BRUSH_COLUMNS;
  bsp->NUM_BRUSH_ROWS         = STD_BRUSH_ROWS;
  bsp->redraw                 = TRUE;
  bsp->freeze                 = FALSE;

  /*  The shell  */
  bsp->shell = gimp_dialog_new (title ? title : _("Brush Selection"),
				"brush_selection",
				gimp_standard_help_func,
				"dialogs/brush_selection.html",
				title ? GTK_WIN_POS_MOUSE : GTK_WIN_POS_NONE,
				FALSE, TRUE, FALSE,

				_("Refresh"), brush_select_refresh_callback,
				bsp, NULL, NULL, FALSE, FALSE,
				_("Close"), brush_select_close_callback,
				bsp, NULL, NULL, TRUE, TRUE,

				NULL);

  if (title)
    {
      bsp->context = gimp_context_new (title, NULL);
    }
  else
    {
      bsp->context = gimp_context_get_user ();

      session_set_window_geometry (bsp->shell, &brush_select_session_info,
				   FALSE);
      dialog_register (bsp->shell);
    }

  if (no_data && first_call)
    brushes_init (FALSE);

  first_call = FALSE;

  if (title && init_name && strlen (init_name))
    {
      active = gimp_brush_list_get_brush (brush_list, init_name);
    }
  else
    {
      active = gimp_context_get_brush (gimp_context_get_user ());
    }

  if (!active)
    {
      active = gimp_context_get_brush (gimp_context_get_standard ());
    }

  if (title)
    {
      gimp_context_set_brush (bsp->context, active);
      gimp_context_set_paint_mode (bsp->context, init_mode);
      gimp_context_set_opacity (bsp->context, init_opacity);
      bsp->spacing_value = init_spacing;
    }

  /*  The main vbox  */
  vbox = gtk_vbox_new (FALSE, 0);
  gtk_container_set_border_width (GTK_CONTAINER (vbox), 2);
  gtk_container_add (GTK_CONTAINER (GTK_DIALOG (bsp->shell)->vbox), vbox);

  /*  The horizontal box containing the brush list & options box */
  hbox = gtk_hbox_new (FALSE, 2);
  gtk_container_add (GTK_CONTAINER (vbox), hbox);

  /*  A place holder for paint mode switching  */
  bsp->left_box = gtk_hbox_new (FALSE, 0);
  gtk_box_pack_start (GTK_BOX (hbox), bsp->left_box, TRUE, TRUE, 0);

  /*  The horizontal box containing preview & scrollbar  */
  bsp->brush_selection_box = gtk_hbox_new (FALSE, 2);
  gtk_container_add (GTK_CONTAINER (bsp->left_box), bsp->brush_selection_box);

  frame = gtk_frame_new (NULL);
  gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
  gtk_box_pack_start (GTK_BOX (bsp->brush_selection_box), frame,
		      TRUE, TRUE, 0);

  bsp->sbar_data =
    GTK_ADJUSTMENT (gtk_adjustment_new (0, 0, MAX_WIN_HEIGHT (bsp),
					1, 1, MAX_WIN_HEIGHT (bsp)));
  sbar = gtk_vscrollbar_new (bsp->sbar_data);
  gtk_signal_connect (GTK_OBJECT (bsp->sbar_data), "value_changed",
		      GTK_SIGNAL_FUNC (brush_select_scroll_update),
		      bsp);
  gtk_box_pack_start (GTK_BOX (bsp->brush_selection_box), sbar, FALSE, FALSE, 0);

  /*  Create the brush preview window and the underlying image  */

  /*  Get the maximum brush extents  */
  bsp->cell_width  = MIN_CELL_SIZE;
  bsp->cell_height = MIN_CELL_SIZE;

  bsp->preview = gtk_preview_new (GTK_PREVIEW_COLOR);
  gtk_preview_size (GTK_PREVIEW (bsp->preview),
		    MAX_WIN_WIDTH (bsp), MAX_WIN_HEIGHT (bsp));
  gtk_widget_set_usize (bsp->preview,
			MAX_WIN_WIDTH (bsp), MAX_WIN_HEIGHT (bsp));
  gtk_preview_set_expand (GTK_PREVIEW (bsp->preview), TRUE);
  gtk_widget_set_events (bsp->preview, BRUSH_EVENT_MASK);

  gtk_signal_connect (GTK_OBJECT (bsp->preview), "event",
		      GTK_SIGNAL_FUNC (brush_select_events),
		      bsp);
  gtk_signal_connect (GTK_OBJECT(bsp->preview), "size_allocate",
		      GTK_SIGNAL_FUNC (brush_select_resize),
		      bsp);

  /*  dnd stuff  */
  gtk_drag_source_set (bsp->preview,
		       GDK_BUTTON2_MASK,
		       preview_target_table, preview_n_targets,
		       GDK_ACTION_COPY);
  gimp_dnd_brush_source_set (bsp->preview, brush_select_drag_brush, bsp);

  gtk_drag_dest_set (bsp->preview,
                     GTK_DEST_DEFAULT_ALL,
                     preview_target_table, preview_n_targets,
                     GDK_ACTION_COPY);
  gimp_dnd_brush_dest_set (bsp->preview, brush_select_drop_brush, bsp);

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

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

  /*  Options box  */
  bsp->options_box = gtk_vbox_new (FALSE, 0);
  gtk_box_pack_start (GTK_BOX (hbox), bsp->options_box, FALSE, FALSE, 0);

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

  bsp->brush_name = gtk_label_new (_("No Brushes available"));
  gtk_box_pack_start (GTK_BOX (util_box), bsp->brush_name, FALSE, FALSE, 4);
  bsp->brush_size = gtk_label_new ("(0 x 0)");
  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);

  /*  A place holder for paint mode switching  */
  bsp->right_box = gtk_vbox_new (FALSE, 0);
  gtk_box_pack_start (GTK_BOX (bsp->options_box), bsp->right_box, TRUE, TRUE, 0);

  /*  The vbox for the paint options  */
  bsp->paint_options_box = gtk_vbox_new (FALSE, 0);
  gtk_box_pack_start (GTK_BOX (bsp->right_box), bsp->paint_options_box,
		      FALSE, FALSE, 0);

  /*  A separator before the paint options  */
  sep = gtk_hseparator_new ();
  gtk_box_pack_start (GTK_BOX (bsp->paint_options_box), sep, FALSE, FALSE, 0);
  gtk_widget_show (sep);

  /*  Create the frame and the table for the options  */
  table = gtk_table_new (2, 2, FALSE);
  gtk_table_set_col_spacing (GTK_TABLE (table), 0, 4);
  gtk_table_set_row_spacings (GTK_TABLE (table), 2);
  gtk_box_pack_start (GTK_BOX (bsp->paint_options_box), table, FALSE, FALSE, 2);

  /*  Create the opacity scale widget  */
  bsp->opacity_data = 
    GTK_ADJUSTMENT (gtk_adjustment_new
		    (gimp_context_get_opacity (bsp->context) * 100.0,
		     0.0, 100.0, 1.0, 1.0, 0.0));
  slider = gtk_hscale_new (bsp->opacity_data);
  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",
		      GTK_SIGNAL_FUNC (opacity_scale_update),
		      bsp);
  gimp_table_attach_aligned (GTK_TABLE (table), 0, 0,
			     _("Opacity:"), 1.0, 1.0,
			     slider, 1, FALSE);

  /*  Create the paint mode option menu  */
  menu = paint_mode_menu_new (paint_mode_menu_callback, (gpointer) bsp);
  bsp->option_menu = option_menu = gtk_option_menu_new ();
  gtk_option_menu_set_menu (GTK_OPTION_MENU (option_menu), menu);
  gtk_option_menu_set_history (GTK_OPTION_MENU (option_menu),
			       gimp_context_get_paint_mode (bsp->context));
  gimp_table_attach_aligned (GTK_TABLE (table), 0, 1,
			     _("Mode:"), 1.0, 0.5,
			     option_menu, 1, TRUE);

  gtk_widget_show (table);
  gtk_widget_show (bsp->paint_options_box);
  gtk_widget_show (bsp->right_box);

  /*  Create the edit/new buttons  */
  util_box = gtk_hbox_new (FALSE, 0);
  gtk_box_pack_end (GTK_BOX (bsp->options_box), util_box, FALSE, FALSE, 4);

  button =  gtk_button_new_with_label (_("New"));
  gtk_signal_connect (GTK_OBJECT (button), "clicked",
		      GTK_SIGNAL_FUNC (brush_select_new_brush_callback),
		      bsp);
  gtk_box_pack_start (GTK_BOX (util_box), button, TRUE, TRUE, 6);

  bsp->edit_button =  gtk_button_new_with_label (_("Edit"));
  gtk_signal_connect (GTK_OBJECT (bsp->edit_button), "clicked",
		      GTK_SIGNAL_FUNC (brush_select_edit_brush_callback),
		      bsp);
  gtk_box_pack_start (GTK_BOX (util_box), bsp->edit_button, TRUE, TRUE, 5);

  bsp->delete_button =  gtk_button_new_with_label (_("Delete"));
  gtk_signal_connect (GTK_OBJECT (bsp->delete_button), "clicked",
		      GTK_SIGNAL_FUNC (brush_select_delete_brush_callback),
		      bsp);
  gtk_box_pack_start (GTK_BOX (util_box), bsp->delete_button, TRUE, TRUE, 5);

  gtk_widget_show (bsp->edit_button);    
  gtk_widget_show (button);
  gtk_widget_show (bsp->delete_button);    
  gtk_widget_show (util_box);

  /*  Create the spacing scale widget  */
  table = gtk_table_new (1, 2, FALSE);
  gtk_table_set_col_spacing (GTK_TABLE (table), 0, 4);
  gtk_box_pack_end (GTK_BOX (bsp->options_box), table, FALSE, FALSE, 2);

  bsp->spacing_data =
    GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 1.0, 1000.0, 1.0, 1.0, 0.0));
  slider = gtk_hscale_new (bsp->spacing_data);
  gtk_scale_set_value_pos (GTK_SCALE (slider), GTK_POS_TOP);
  gtk_range_set_update_policy (GTK_RANGE (slider), GTK_UPDATE_DELAYED);
  if (title && init_spacing >= 0)
    {
      /*  Use passed spacing instead of brushes default  */
      gtk_adjustment_set_value (GTK_ADJUSTMENT (bsp->spacing_data),
				init_spacing);
    }
  gtk_signal_connect (GTK_OBJECT (bsp->spacing_data), "value_changed",
		      GTK_SIGNAL_FUNC (spacing_scale_update),
		      bsp);
  gimp_table_attach_aligned (GTK_TABLE (table), 0, 0,
			     _("Spacing:"), 1.0, 1.0,
			     slider, 1, FALSE);

  gtk_widget_show (table);

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

  /*  add callbacks to keep the display area current  */
  gimp_list_foreach (GIMP_LIST (brush_list),
		     (GFunc) connect_signals_to_brush,
		     bsp);
  gtk_signal_connect (GTK_OBJECT (brush_list), "add",
		      GTK_SIGNAL_FUNC (brush_added_callback),
		      bsp);
  gtk_signal_connect (GTK_OBJECT (brush_list), "remove",
		      GTK_SIGNAL_FUNC (brush_removed_callback),
		      bsp);

  /*  Only for main dialog  */
  if (!title)
    {
      /*  set the preview's size in the callback
      gtk_signal_connect (GTK_OBJECT (bsp->shell), "map",
			  GTK_SIGNAL_FUNC (brush_select_map_callback),
			  bsp);
      */

      /*  if we are in per-tool paint options mode, hide the paint options  */
      brush_select_show_paint_options (bsp, global_paint_options);
    }

  gtk_widget_show (bsp->shell);

  preview_calc_scrollbar (bsp);

  gtk_signal_connect (GTK_OBJECT (bsp->context), "brush_changed",
		      GTK_SIGNAL_FUNC (brush_select_brush_changed),
		      bsp);
  gtk_signal_connect (GTK_OBJECT (bsp->context), "opacity_changed",
		      GTK_SIGNAL_FUNC (brush_select_opacity_changed),
		      bsp);
  gtk_signal_connect (GTK_OBJECT (bsp->context), "paint_mode_changed",
		      GTK_SIGNAL_FUNC (brush_select_paint_mode_changed),
		      bsp);

  if (active)
    brush_select_select (bsp, active);

  /*  Add to active brush dialogs list  */
  brush_active_dialogs = g_slist_append (brush_active_dialogs, bsp);

  return bsp;
}

void
brush_select_free (BrushSelect *bsp)
{
  if (!bsp)
    return;

  /* remove from active list */
  brush_active_dialogs = g_slist_remove (brush_active_dialogs, bsp);

  gtk_signal_disconnect_by_data (GTK_OBJECT (bsp->context), bsp);

  if (bsp->brush_popup != NULL)
    gtk_widget_destroy (bsp->brush_popup);

  if (bsp->callback_name)
    {
      g_free (bsp->callback_name);
      gtk_object_unref (GTK_OBJECT (bsp->context));
    }

  gimp_list_foreach (GIMP_LIST (brush_list),
		     (GFunc) disconnect_signals_from_brush,
		     bsp);
  gtk_signal_disconnect_by_data (GTK_OBJECT (brush_list), bsp);

  g_free (bsp);
}

void
brush_select_freeze_all (void)
{
  BrushSelect *bsp;
  GSList *list;

  for (list = brush_active_dialogs; list; list = g_slist_next (list))
    {
      bsp = (BrushSelect *) list->data;

      bsp->freeze = TRUE;
    }
}

void
brush_select_thaw_all (void)
{
  BrushSelect *bsp;
  GSList *list;

  for (list = brush_active_dialogs; list; list = g_slist_next (list))
    {
      bsp = (BrushSelect *) list->data;

      bsp->freeze = FALSE;

      preview_calc_scrollbar (bsp);
    }
}

void
brush_select_show_paint_options (BrushSelect *bsp,
				 gboolean     show)
{
  if ((bsp == NULL) && ((bsp = brush_select_dialog) == NULL))
    return;

  if (show)
    {
      if (! GTK_WIDGET_VISIBLE (bsp->paint_options_box))
	gtk_widget_show (bsp->paint_options_box);

      if (bsp->brush_selection_box->parent != bsp->left_box)
	gtk_widget_reparent (bsp->brush_selection_box, bsp->left_box);
      gtk_box_set_child_packing (GTK_BOX (bsp->options_box->parent),
				 bsp->options_box,
				 FALSE, FALSE, 0, GTK_PACK_START);
      gtk_box_set_child_packing (GTK_BOX (bsp->left_box->parent),
				 bsp->left_box,
				 TRUE, TRUE, 0, GTK_PACK_START);
      gtk_box_set_spacing (GTK_BOX (bsp->left_box->parent), 2);
    }
  else
    {
      if (GTK_WIDGET_VISIBLE (bsp->paint_options_box))
	gtk_widget_hide (bsp->paint_options_box);

      if (bsp->brush_selection_box->parent != bsp->right_box)
	gtk_widget_reparent (bsp->brush_selection_box, bsp->right_box);
      gtk_box_set_child_packing (GTK_BOX (bsp->left_box->parent),
				 bsp->left_box,
				 FALSE, FALSE, 0, GTK_PACK_START);
      gtk_box_set_child_packing (GTK_BOX (bsp->options_box->parent),
				 bsp->options_box,
				 TRUE, TRUE, 0, GTK_PACK_START);
      gtk_box_set_spacing (GTK_BOX (bsp->left_box->parent), 0);
    }
}

/*  call this dialog's PDB callback  */

static void
brush_change_callbacks (BrushSelect *bsp,
			gboolean     closing)
{
  gchar *name;
  ProcRecord *prec = NULL;
  GimpBrush *brush;
  gint nreturn_vals;
  static gboolean busy = FALSE;

  /* Any procs registered to callback? */
  Argument *return_vals; 

  if (!bsp || !bsp->callback_name || busy)
    return;

  busy = TRUE;
  name = bsp->callback_name;
  brush = gimp_context_get_brush (bsp->context);

  /* If its still registered run it */
  prec = procedural_db_lookup (name);

  if (prec && brush)
    {
      return_vals =
	procedural_db_run_proc (name,
				&nreturn_vals,
				PDB_STRING,    brush->name,
				PDB_FLOAT,     gimp_context_get_opacity (bsp->context),
				PDB_INT32,     bsp->spacing_value,
				PDB_INT32,     (gint) gimp_context_get_paint_mode (bsp->context),
				PDB_INT32,     brush->mask->width,
				PDB_INT32,     brush->mask->height,
				PDB_INT32,     brush->mask->width * brush->mask->height,
				PDB_INT8ARRAY, temp_buf_data (brush->mask),
				PDB_INT32,     (gint) closing,
				PDB_END);
 
      if (!return_vals || return_vals[0].value.pdb_int != PDB_SUCCESS)
	g_message ("failed to run brush callback function");
      
      procedural_db_destroy_args (return_vals, nreturn_vals);
    }
  busy = FALSE;
}

/* Close active dialogs that no longer have PDB registered for them */

void
brushes_check_dialogs (void)
{
  BrushSelect *bsp;
  GSList *list;
  gchar *name;
  ProcRecord *prec = NULL;

  list = brush_active_dialogs;

  while (list)
    {
      bsp = (BrushSelect *) list->data;
      list = g_slist_next (list);

      name = bsp->callback_name;

      if (name)
	{
	  prec = procedural_db_lookup (name);

	  if (!prec)
	    {
	      /*  Can alter brush_active_dialogs list  */
	      brush_select_close_callback (NULL, bsp);
	    }
	}
    }
}

/*
 *  Local functions
 */

static GimpBrush *
brush_select_drag_brush (GtkWidget *widget,
			 gpointer   data)
{
  BrushSelect *bsp;

  bsp = (BrushSelect *) data;

  return bsp->dnd_brush;
}

static void
brush_select_drop_brush (GtkWidget *widget,
			 GimpBrush *brush,
			 gpointer   data)
{
  BrushSelect *bsp;

  bsp = (BrushSelect *) data;

  gimp_context_set_brush (bsp->context, brush);
}

static void
brush_select_brush_changed (GimpContext *context,
			    GimpBrush   *brush,
			    BrushSelect *bsp)
{
  if (brush)
    {
      brush_select_select (bsp, brush);

      if (bsp->callback_name)
	brush_change_callbacks (bsp, FALSE);
    }
}

static void
brush_select_opacity_changed (GimpContext *context,
			      gdouble      opacity,
			      BrushSelect *bsp)
{
  gtk_signal_handler_block_by_data (GTK_OBJECT (bsp->opacity_data), bsp);

  gtk_adjustment_set_value (GTK_ADJUSTMENT (bsp->opacity_data),
			    opacity * 100.0);

  gtk_signal_handler_unblock_by_data (GTK_OBJECT (bsp->opacity_data), bsp);

  if (bsp->callback_name)
    brush_change_callbacks (bsp, FALSE);
}

static void
brush_select_paint_mode_changed (GimpContext      *context,
				 LayerModeEffects  paint_mode,
				 BrushSelect      *bsp)
{
  gtk_option_menu_set_history (GTK_OPTION_MENU (bsp->option_menu), paint_mode);

  if (bsp->callback_name)
    brush_change_callbacks (bsp, FALSE);
}

static void
brush_select_select (BrushSelect *bsp,
		     GimpBrush   *brush)
{
  gint index;
  gint row, col;
  gint scroll_offset = 0;

  index = gimp_brush_list_get_brush_index (brush_list, brush); 

  if (index < 0)
    return;

  if (GIMP_IS_BRUSH_GENERATED (brush))
    {
      gtk_widget_set_sensitive (bsp->edit_button, TRUE);
      gtk_widget_set_sensitive (bsp->delete_button, TRUE);
    }
  else
    {
      gtk_widget_set_sensitive (bsp->edit_button, FALSE);
      gtk_widget_set_sensitive (bsp->delete_button, FALSE);
    }

  update_active_brush_field (bsp);

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

  /*  check if the new active brush is already in the preview  */
  if (((row + 1) * bsp->cell_height) >
      (bsp->preview->allocation.height + bsp->scroll_offset))
    {
      scroll_offset = (((row + 1) * bsp->cell_height) -
		       (bsp->scroll_offset + bsp->preview->allocation.height));
    }
  else if ((row * bsp->cell_height) < bsp->scroll_offset)
    {
      scroll_offset = (row * bsp->cell_height) - bsp->scroll_offset;
    }
  else
    {
      brush_select_show_selected (bsp, row, col);
    }

  gtk_adjustment_set_value (bsp->sbar_data, bsp->scroll_offset + scroll_offset);
}

static void
brush_select_brush_dirty_callback (GimpBrush   *brush,
				   BrushSelect *bsp)
{
  gint index;

  if (!bsp && bsp->freeze)
    return;

  index = gimp_brush_list_get_brush_index (brush_list, brush);

  display_brush (bsp, brush,
		 index % (bsp->NUM_BRUSH_COLUMNS),
		 index / (bsp->NUM_BRUSH_COLUMNS));
}

static void
connect_signals_to_brush (GimpBrush   *brush,
			  BrushSelect *bsp)
{
  gtk_signal_connect (GTK_OBJECT (brush), "dirty",
		      GTK_SIGNAL_FUNC (brush_select_brush_dirty_callback),
		      bsp);
  gtk_signal_connect (GTK_OBJECT (brush), "rename",
		      GTK_SIGNAL_FUNC (brush_select_brush_dirty_callback),
		      bsp);
}

static void
disconnect_signals_from_brush (GimpBrush   *brush,
			       BrushSelect *bsp)
{
  if (!GTK_OBJECT_DESTROYED (brush))
    gtk_signal_disconnect_by_data (GTK_OBJECT (brush), bsp);
}

static void
brush_added_callback (GimpBrushList *list,
		      GimpBrush     *brush, 
		      BrushSelect   *bsp)
{
  connect_signals_to_brush (brush, bsp);

  if (bsp->freeze)
    return;

  preview_calc_scrollbar (bsp);
}

static void
brush_removed_callback (GimpBrushList *list,
			GimpBrush     *brush,
			BrushSelect   *bsp)
{
  disconnect_signals_from_brush (brush, bsp);

  if (bsp->freeze)
    return;

  preview_calc_scrollbar (bsp);
}

static void
draw_brush_popup (GtkPreview *preview,
		  GimpBrush  *brush,
		  gint        width,
		  gint        height)
{
  gint x, y;
  gint brush_width, brush_height;
  gint offset_x, offset_y;
  guchar *mask, *buf, *b;
  guchar bg;
  
  brush_width = brush->mask->width;
  brush_height = brush->mask->height;
  offset_x = (width - brush_width) / 2;
  offset_y = (height - brush_height) / 2;

  mask = temp_buf_data (brush->mask);
  buf = g_new (guchar, 3 * width);
  memset (buf, 255, 3 * width);

  if (GIMP_IS_BRUSH_PIXMAP (brush)) 
    {
      guchar *pixmap = temp_buf_data (GIMP_BRUSH_PIXMAP (brush)->pixmap_mask);

      for (y = 0; y < offset_y; y++)
	gtk_preview_draw_row (preview, buf, 0, y, width); 
      for (y = offset_y; y < brush_height + offset_y; y++)
	{
	  b = buf + 3 * offset_x;
	  for (x = 0; x < brush_width ; x++)
	    {
	      bg = (255 - *mask);
	      *b++ = bg + (*mask * *pixmap++) / 255;
	      *b++ = bg + (*mask * *pixmap++) / 255; 
	      *b++ = bg + (*mask * *pixmap++) / 255;
	      mask++;
	    }
	  gtk_preview_draw_row (preview, buf, 0, y, width); 
	}
      memset (buf, 255, 3 * width);
      for (y = brush_height + offset_y; y < height; y++)
	gtk_preview_draw_row (preview, buf, 0, y, width); 
    }
  else
    {
      for (y = 0; y < offset_y; y++)
	gtk_preview_draw_row (preview, buf, 0, y, width); 
      for (y = offset_y; y < brush_height + offset_y; y++)
	{
	  b = buf + 3 * offset_x;
	  for (x = 0; x < brush_width ; x++)
	    {
	      bg = 255 - *mask++;
	      memset (b, bg, 3);
	      b += 3;
	    }   
	  gtk_preview_draw_row (preview, buf, 0, y, width); 
	}
      memset (buf, 255, 3 * width);
      for (y = brush_height + offset_y; y < height; y++)
	gtk_preview_draw_row (preview, buf, 0, y, width);    
    }

  g_free (buf);
}

typedef struct
{
  BrushSelect *bsp;
  gint         x;
  gint         y;
  GimpBrush   *brush;
} popup_timeout_args_t;

static gint
brush_popup_anim_timeout (gpointer data)
{
  popup_timeout_args_t *args = data;
  BrushSelect *bsp = args->bsp;
  GimpBrushPipe *pipe;
  GimpBrush *brush;

  if (bsp->brush_popup != NULL && !GTK_WIDGET_VISIBLE (bsp->brush_popup))
    {
      bsp->popup_anim_timeout_tag = 0;
      return (FALSE);
    }

  pipe = GIMP_BRUSH_PIPE (args->brush);
  if (++bsp->popup_pipe_index >= pipe->nbrushes)
    bsp->popup_pipe_index = 0;
  brush = GIMP_BRUSH (pipe->brushes[bsp->popup_pipe_index]);

  draw_brush_popup (GTK_PREVIEW (bsp->brush_preview), brush, args->x, args->y);
  gtk_widget_queue_draw (bsp->brush_preview);

  return (TRUE);
}

static gboolean
brush_popup_timeout (gpointer data)
{
  popup_timeout_args_t *args = data;
  BrushSelect *bsp = args->bsp;
  GimpBrush *brush = args->brush;
  gint x, y;
  gint x_org, y_org;
  gint scr_w, scr_h;
  gint width, height;

  /* timeout has gone off so our tag is now invalid  */
  bsp->popup_timeout_tag = 0;

  /* make sure the popup exists and is not visible */
  if (bsp->brush_popup == NULL)
    {
      GtkWidget *frame;

      bsp->brush_popup = gtk_window_new (GTK_WINDOW_POPUP);
      gtk_window_set_policy (GTK_WINDOW (bsp->brush_popup),
			     FALSE, FALSE, TRUE);
      frame = gtk_frame_new (NULL);
      gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT);
      gtk_container_add (GTK_CONTAINER (bsp->brush_popup), frame);
      gtk_widget_show (frame);
      bsp->brush_preview = gtk_preview_new (GTK_PREVIEW_COLOR);
      gtk_container_add (GTK_CONTAINER (frame), bsp->brush_preview);
      gtk_widget_show (bsp->brush_preview);
    }
  else
    {
      gtk_widget_hide (bsp->brush_popup);
    }

  /* decide where to put the popup */
  width = brush->mask->width;
  height = brush->mask->height;
  if (GIMP_IS_REALLY_A_BRUSH_PIPE (brush))
    {      
      GimpBrushPipe *pipe = GIMP_BRUSH_PIPE (brush);
      GimpBrush *tmp_brush;
      gint i;
      
      for (i = 1; i < pipe->nbrushes; i++)
	{
	  tmp_brush = GIMP_BRUSH (pipe->brushes[i]);
	  width = MAX (width, tmp_brush->mask->width);
	  height = MAX (height, tmp_brush->mask->height);
	}
    }
  gdk_window_get_origin (bsp->preview->window, &x_org, &y_org);
  scr_w = gdk_screen_width ();
  scr_h = gdk_screen_height ();
  x = x_org + args->x - width * 0.5;
  y = y_org + args->y - height * 0.5;
  x = (x < 0) ? 0 : x;
  y = (y < 0) ? 0 : y;
  x = (x + width > scr_w) ? scr_w - width : x;
  y = (y + height > scr_h) ? scr_h - height : y;
  gtk_preview_size (GTK_PREVIEW (bsp->brush_preview), width, height);

  gtk_widget_popup (bsp->brush_popup, x, y);

  /*  Draw the brush preview  */
  draw_brush_popup (GTK_PREVIEW (bsp->brush_preview), brush, width, height);
  gtk_widget_queue_draw (bsp->brush_preview);

  if (GIMP_IS_REALLY_A_BRUSH_PIPE (brush) && bsp->popup_anim_timeout_tag == 0)
    {
      static popup_timeout_args_t timeout_args;
      
      timeout_args.bsp = bsp;
      timeout_args.x = width;
      timeout_args.y = height;
      timeout_args.brush = brush;

      bsp->popup_pipe_index = 0;
      bsp->popup_anim_timeout_tag =
	gtk_timeout_add (300, brush_popup_anim_timeout, &timeout_args);
    }

  return FALSE;  /* don't repeat */
}

static void
brush_popup_open (BrushSelect *bsp,
		  gint         x,
		  gint         y,
		  GimpBrush   *brush)
{
  static popup_timeout_args_t popup_timeout_args;

  /* if we've already got a timeout scheduled, then we complain */
  g_return_if_fail (bsp->popup_timeout_tag == 0);

  popup_timeout_args.bsp = bsp;
  popup_timeout_args.x = x;
  popup_timeout_args.y = y;
  popup_timeout_args.brush = brush;
  bsp->popup_timeout_tag = gtk_timeout_add (POPUP_DELAY_MS,
					    brush_popup_timeout,
					    &popup_timeout_args);
}

static void
brush_popup_close (BrushSelect *bsp)
{
  if (bsp->popup_timeout_tag != 0)
    gtk_timeout_remove (bsp->popup_timeout_tag);
  bsp->popup_timeout_tag = 0;

  if (bsp->popup_anim_timeout_tag != 0)
    gtk_timeout_remove (bsp->popup_anim_timeout_tag);
  bsp->popup_anim_timeout_tag = 0;

  if (bsp->brush_popup != NULL)
    gtk_widget_hide (bsp->brush_popup);
}

static void
display_setup (BrushSelect *bsp)
{
  guchar * buf;
  gint i;

  buf = g_new (guchar, 3 * bsp->preview->allocation.width);

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

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

  g_free (buf);
}

static void
display_brush (BrushSelect *bsp,
	       GimpBrush   *brush,
	       gint         col,
	       gint         row)
{
  TempBuf *mask_buf, *pixmap_buf = NULL;
  guchar *mask, *buf, *b;
  guchar bg;
  gboolean scale = FALSE;
  gint cell_width, cell_height;
  gint width, height;
  gint offset_x, offset_y;
  gint yend;
  gint ystart;
  gint i, j;

  cell_width  = bsp->cell_width  - 2 * MARGIN_WIDTH;
  cell_height = bsp->cell_height - 2 * MARGIN_HEIGHT;

  mask_buf = brush->mask;
  if (GIMP_IS_BRUSH_PIXMAP (brush))
    pixmap_buf = GIMP_BRUSH_PIXMAP (brush)->pixmap_mask;

  if (mask_buf->width > cell_width || mask_buf->height > cell_height)
    {
      gdouble ratio_x = (gdouble)mask_buf->width / cell_width;
      gdouble ratio_y = (gdouble)mask_buf->height / cell_height;
   
      mask_buf = brush_scale_mask (mask_buf, 
				   (gdouble)(mask_buf->width) / MAX (ratio_x, ratio_y) + 0.5, 
				   (gdouble)(mask_buf->height) / MAX (ratio_x, ratio_y) + 0.5);
      if (GIMP_IS_BRUSH_PIXMAP (brush))
	{
	  /*  TODO: the scale function should scale the pixmap 
	            and the mask in one run                     */
 	  pixmap_buf = brush_scale_pixmap (pixmap_buf,
					   mask_buf->width, mask_buf->height);
	}
      scale = TRUE;
    }

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

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

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

  mask = temp_buf_data (mask_buf) + (ystart - offset_y) * mask_buf->width;
  buf = g_new (guchar, 3 * cell_width);

  if (GIMP_IS_BRUSH_PIXMAP (brush)) 
    { 
      guchar *pixmap = temp_buf_data (pixmap_buf) + (ystart - offset_y) * mask_buf->width * 3;
      for (i = ystart; i < yend; i++)
	{
	  b = buf;
	  for (j = 0; j < width ; j++)
	    {
	      bg = (255 - *mask);
	      *b++ = bg + (*mask * *pixmap++) / 255;
	      *b++ = bg + (*mask * *pixmap++) / 255; 
	      *b++ = bg + (*mask * *pixmap++) / 255;
	      mask++;
	    }
	  gtk_preview_draw_row (GTK_PREVIEW (bsp->preview), buf,
				offset_x, i, width);
	}
    }
  else 
    {
      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...
	   */
	  b = buf;
	  for (j = 0; j < width; j++) 
	    {
	      bg = 255 - *mask++;
	      memset (b, bg, 3);
	      b += 3;
	    }
	  gtk_preview_draw_row (GTK_PREVIEW (bsp->preview), buf,
				offset_x, i, width);
	}
    }
  g_free (buf);

  offset_x = (col + 1) * bsp->cell_width  - indicator_width - MARGIN_WIDTH;
  offset_y = (row + 1) * bsp->cell_height - indicator_height - bsp->scroll_offset - MARGIN_HEIGHT;

  if (scale)
    {
      temp_buf_free (mask_buf);
      if (GIMP_IS_BRUSH_PIXMAP (brush))
	temp_buf_free (pixmap_buf);
      for (i = 0; i < indicator_height; i++, offset_y++)
	{ 
	  if (offset_y > 0 && offset_y < bsp->preview->allocation.height)
	    (GIMP_IS_REALLY_A_BRUSH_PIPE (brush)) ?
	      gtk_preview_draw_row (GTK_PREVIEW (bsp->preview),
				    scale_pipe_indicator_bits[i][0],
				    offset_x, offset_y, indicator_width) :
	      gtk_preview_draw_row (GTK_PREVIEW (bsp->preview),
				    scale_indicator_bits[i][0],
				    offset_x, offset_y, indicator_width);
	}
    }
  else if (GIMP_IS_REALLY_A_BRUSH_PIPE (brush))
    {
      for (i = 0; i < indicator_height; i++, offset_y++)
	{
	  if (offset_y > 0 && offset_y < bsp->preview->allocation.height)
	    gtk_preview_draw_row (GTK_PREVIEW (bsp->preview),
				  pipe_indicator_bits[i][0],
				  offset_x, offset_y, indicator_width);
	}
    }
}

static gint brush_counter = 0;

static void
do_display_brush (GimpBrush   *brush,
		  BrushSelect *bsp)
{
  display_brush (bsp, brush, brush_counter % (bsp->NUM_BRUSH_COLUMNS),
		 brush_counter / (bsp->NUM_BRUSH_COLUMNS));
  brush_counter++;
}

static void
display_brushes (BrushSelect *bsp)
{
  if (brush_list == NULL || gimp_brush_list_length (brush_list) == 0)
    {
      gtk_widget_set_sensitive (bsp->options_box, FALSE);
      return;
    }
  else
    {
      gtk_widget_set_sensitive (bsp->options_box, TRUE);
    }

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

  brush_counter = 0;
  gimp_list_foreach (GIMP_LIST (brush_list), (GFunc) do_display_brush, bsp);
}

static void
brush_select_show_selected (BrushSelect *bsp,
			    gint         row,
			    gint         col)
{
  GdkRectangle area;
  guchar *buf;
  gint yend;
  gint ystart;
  gint offset_x, offset_y;
  gint i;

  buf = g_new (guchar, 3 * bsp->cell_width);

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

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

      /*  set the buf to white  */
      memset (buf, 255, 3 * 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 = CLAMP (offset_y , 0, bsp->preview->allocation.height);
  yend = CLAMP (offset_y + bsp->cell_height, 0, bsp->preview->allocation.height);

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

  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);
    }

  bsp->old_row = row;
  bsp->old_col = col;

  g_free (buf);
}

static void
update_active_brush_field (BrushSelect *bsp)
{
  GimpBrush *brush;
  gchar buf[32];

  brush = gimp_context_get_brush (bsp->context);

  if (!brush)
    return;

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

  /*  Set brush size  */
  g_snprintf (buf, sizeof (buf), "(%d x %d)",
	      brush->mask->width, brush->mask->height);
  gtk_label_set_text (GTK_LABEL (bsp->brush_size), buf);

  /*  Set brush spacing  */
  gtk_adjustment_set_value (GTK_ADJUSTMENT (bsp->spacing_data),
			    gimp_brush_get_spacing (brush));
}

static void
preview_calc_scrollbar (BrushSelect *bsp)
{
  gint num_rows;
  gint page_size;
  gint max;

  bsp->scroll_offset = 0;
  num_rows = ((gimp_brush_list_length (brush_list) +
	       (bsp->NUM_BRUSH_COLUMNS) - 1)
	      / (bsp->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");
  gtk_signal_emit_by_name (GTK_OBJECT (bsp->sbar_data), "value_changed");
}

static gint 
brush_select_resize (GtkWidget   *widget, 
		     GdkEvent    *event, 
		     BrushSelect *bsp)
{
  /*  calculate the best-fit approximation...  */  
  gint wid;
  gint now;
  gint cell_size;

  wid = widget->allocation.width;

  for (now = cell_size = MIN_CELL_SIZE;
       now < MAX_CELL_SIZE; ++now)
    {
      if ((wid % now) < (wid % cell_size)) cell_size = now;
      if ((wid % cell_size) == 0)
        break;
    }

   bsp->NUM_BRUSH_COLUMNS =
     (gint) (wid / cell_size);
   bsp->NUM_BRUSH_ROWS =
     (gint) ((gimp_brush_list_length (brush_list) + bsp->NUM_BRUSH_COLUMNS - 1) /
	     bsp->NUM_BRUSH_COLUMNS);

   bsp->cell_width  = cell_size;
   bsp->cell_height = cell_size;

   /*  recalculate scrollbar extents  */
   preview_calc_scrollbar (bsp);
 
   return FALSE;
}
 
static gint
brush_select_events (GtkWidget   *widget,
		     GdkEvent    *event,
		     BrushSelect *bsp)
{
  GdkEventButton *bevent;
  GimpBrush *brush;
  gint row, col, index;

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

    case GDK_2BUTTON_PRESS:
      bevent = (GdkEventButton *) event;
      col = bevent->x / bsp->cell_width;
      row = (bevent->y + bsp->scroll_offset) / bsp->cell_height;
      index = row * bsp->NUM_BRUSH_COLUMNS + col;
      
      /*  Get the brush and check if it is editable  */
      brush = gimp_brush_list_get_brush_by_index (brush_list, index);
      if (GIMP_IS_BRUSH_GENERATED (brush))
	brush_select_edit_brush_callback (NULL, bsp);
      break;

    case GDK_BUTTON_PRESS:
      bevent = (GdkEventButton *) event;

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

      brush = gimp_brush_list_get_brush_by_index (brush_list, index);

      if (brush)
	bsp->dnd_brush = brush;
      else
	bsp->dnd_brush = gimp_context_get_brush (bsp->context);

      if (bevent->button == 1)
	{
	  /*  Get the brush and display the popup brush preview  */
	  if (brush)
	    {
	      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  */
	      gimp_context_set_brush (bsp->context, brush);

	      /*  only if dialog is main one        */
	      if (bsp == brush_select_dialog &&
		  brush_edit_generated_dialog)
		{
		  brush_edit_generated_set_brush (brush_edit_generated_dialog,
						  brush);
		}
	      
	      /*  Show the brush popup window if the brush is too large  */
	      if (brush->mask->width  > bsp->cell_width - 2 * MARGIN_WIDTH ||
		  brush->mask->height > bsp->cell_height - 2 * MARGIN_HEIGHT ||
		  GIMP_IS_REALLY_A_BRUSH_PIPE (brush))
		{
		  brush_popup_open (bsp, bevent->x, bevent->y, brush);
		}
	    }
	}

      /*  wheelmouse support  */
      else if (bevent->button == 4)
	{
	  GtkAdjustment *adj = bsp->sbar_data;
	  gfloat new_value = adj->value - adj->page_increment / 2;
	  new_value =
	    CLAMP (new_value, adj->lower, adj->upper - adj->page_size);
	  gtk_adjustment_set_value (adj, new_value);
	}
      else if (bevent->button == 5)
	{
	  GtkAdjustment *adj = bsp->sbar_data;
	  gfloat new_value = adj->value + adj->page_increment / 2;
	  new_value =
	    CLAMP (new_value, adj->lower, adj->upper - adj->page_size);
	  gtk_adjustment_set_value (adj, new_value);
	}

      break;

    case GDK_BUTTON_RELEASE:
      bevent = (GdkEventButton *) event;

      if (bevent->button == 1)
	{
	  /*  Ungrab the pointer  */
	  gdk_pointer_ungrab (bevent->time);

	  /*  Close the brush popup window  */
	  brush_popup_close (bsp);
	}
      break;

    case GDK_DELETE:
      /* g_warning ("test"); */
      break;

    default:
      break;
    }

  return FALSE;
}

/*  Disabled until I've figured out how gtk window resizing *really* works.
 *  I don't think that the function below is the correct way to do it
 *    --  Michael
 *
static void
brush_select_map_callback (GtkWidget   *widget,
			   BrushSelect *bsp)
{
  GtkAllocation allocation;
  gint xdiff, ydiff;

  xdiff =
    bsp->shell->allocation.width - bsp->preview->allocation.width;
  ydiff =
    bsp->shell->allocation.height - bsp->preview->allocation.height;

  allocation = bsp->shell->allocation;
  allocation.width = brush_select_session_info.width + xdiff;
  allocation.height = brush_select_session_info.height + ydiff;

  gtk_widget_size_allocate (bsp->shell, &allocation);
}
*/

static void
brush_select_scroll_update (GtkAdjustment *adjustment,
			    gpointer       data)
{
  BrushSelect *bsp;
  GimpBrush *active;
  gint row, col, index;

  bsp = (BrushSelect *) data;

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

      display_brushes (bsp);

      active = gimp_context_get_brush (bsp->context);

      if (active)
	{
	  index = gimp_brush_list_get_brush_index (brush_list, active);
	  if (index < 0)
	    return;
	  row = index / bsp->NUM_BRUSH_COLUMNS;
	  col = index - row * bsp->NUM_BRUSH_COLUMNS;

	  brush_select_show_selected (bsp, row, col);
	}

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

static void
opacity_scale_update (GtkAdjustment *adjustment,
		      gpointer       data)
{
  BrushSelect *bsp;

  bsp = (BrushSelect *) data;

  gimp_context_set_opacity (bsp->context, adjustment->value / 100.0);
}

static void
paint_mode_menu_callback (GtkWidget *widget,
			  gpointer   data)
{
  BrushSelect *bsp;
  
  bsp = (BrushSelect *) gtk_object_get_user_data (GTK_OBJECT (widget));

  gimp_context_set_paint_mode (bsp->context, (LayerModeEffects) data);
}

static void
spacing_scale_update (GtkAdjustment *adjustment,
		      gpointer       data)
{
  BrushSelect *bsp;

  bsp = (BrushSelect *) data;

  if (bsp == brush_select_dialog)
    {
      gimp_brush_set_spacing (gimp_context_get_brush (bsp->context),
			      (gint) adjustment->value);
    }
  else
    {
      if (bsp->spacing_value != adjustment->value)
	{
	  bsp->spacing_value = adjustment->value;
	  brush_change_callbacks (bsp, FALSE);
	}
    }
}

static void
brush_select_close_callback (GtkWidget *widget,
			     gpointer   data)
{
  BrushSelect *bsp;

  bsp = (BrushSelect *) data;

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

  /* Free memory if poping down dialog which is not the main one */
  if (bsp != brush_select_dialog)
    {
      /* Send data back */
      brush_change_callbacks (bsp, TRUE);
      gtk_widget_destroy (bsp->shell); 
      brush_select_free (bsp); 
    }
}

static void
brush_select_refresh_callback (GtkWidget *widget,
			       gpointer   data)
{
  /*  re-init the brush list  */
  brushes_init (FALSE);
}

static void
brush_select_new_brush_callback (GtkWidget *widget,
				 gpointer   data)
{
  GimpBrushGenerated *brush;
  BrushSelect *bsp;

  bsp = (BrushSelect *) data;

  brush = gimp_brush_generated_new (10, .5, 0.0, 1.0);
  gimp_brush_list_add (brush_list, GIMP_BRUSH (brush));

  gimp_context_set_brush (bsp->context, GIMP_BRUSH (brush));

  if (brush_edit_generated_dialog)
    brush_edit_generated_set_brush (brush_edit_generated_dialog,
				    GIMP_BRUSH (brush));

  brush_select_edit_brush_callback (widget, data);
}

static void
brush_select_edit_brush_callback (GtkWidget *widget,
				  gpointer   data)
{
  BrushSelect *bsp;
  GimpBrush *brush;

  bsp = (BrushSelect *) data;
  brush = gimp_context_get_brush (bsp->context);

  if (GIMP_IS_BRUSH_GENERATED (brush))
    {
      if (!brush_edit_generated_dialog)
	{
	  /*  Create the dialog...  */
	  brush_edit_generated_dialog = brush_edit_generated_new ();
	  brush_edit_generated_set_brush (brush_edit_generated_dialog, brush);
	}
      else
	{
	  /*  Popup the dialog  */
	  if (!GTK_WIDGET_VISIBLE (brush_edit_generated_dialog->shell))
	    gtk_widget_show (brush_edit_generated_dialog->shell);
	  else
	    gdk_window_raise (brush_edit_generated_dialog->shell->window);
	}
    }
  else
    /* this should never happen */
    g_message (_("Sorry, this brush can't be edited."));
}

static void
brush_select_delete_brush_callback (GtkWidget *widget,
				    gpointer   data)
{
  BrushSelect *bsp;
  GimpBrush *brush;

  bsp = (BrushSelect *) data;
  brush = gimp_context_get_brush (bsp->context);

  if (GIMP_IS_BRUSH_GENERATED (brush))
    {
      gint index;

      gimp_brush_generated_delete (GIMP_BRUSH_GENERATED (brush));

      brush_select_freeze_all ();

      index = gimp_brush_list_get_brush_index (brush_list, brush);
      gimp_brush_list_remove (brush_list, GIMP_BRUSH (brush));
      gimp_context_refresh_brushes ();

      brush_select_thaw_all ();
    }
  else
    /* this should never happen */
    g_message (_("Sorry, this brush can't be deleted."));
}
