/* 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 <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "appenv.h"
#include "actionarea.h"
#include "patterns.h"
#include "pattern_select.h"
#include "buildmenu.h"
#include "colormaps.h"
#include "disp_callbacks.h"
#include "errors.h"
#include "paint_funcs.h"


#define MIN_CELL_SIZE    32
#define MAX_CELL_SIZE    45


/*
#define STD_PATTERN_COLUMNS 6
#define STD_PATTERN_ROWS    5 
*/

#define MAX_WIN_WIDTH     (MIN_CELL_SIZE * NUM_PATTERN_COLUMNS)
#define MAX_WIN_HEIGHT    (MIN_CELL_SIZE * NUM_PATTERN_ROWS)
#define MARGIN_WIDTH      1
#define MARGIN_HEIGHT     1
#define PATTERN_EVENT_MASK GDK_BUTTON1_MOTION_MASK | \
                           GDK_EXPOSURE_MASK | \
                           GDK_BUTTON_PRESS_MASK | \
			   GDK_ENTER_NOTIFY_MASK
                           

/*  local function prototypes  */
static void display_pattern                  (PatternSelectP, GPatternP, int, int);
static void display_patterns                 (PatternSelectP);
static void display_setup                    (PatternSelectP);
static void draw_preview                     (PatternSelectP);
static void preview_calc_scrollbar           (PatternSelectP);
static void pattern_select_show_selected     (PatternSelectP, int, int);
static void update_active_pattern_field      (PatternSelectP);
static void pattern_select_close_callback    (GtkWidget *, gpointer);
static gint pattern_select_delete_callback    (GtkWidget *, GdkEvent *, gpointer);
static void pattern_select_refresh_callback  (GtkWidget *, gpointer);
static gint pattern_select_events            (GtkWidget *, GdkEvent *, PatternSelectP);
static gint pattern_select_resize            (GtkWidget *, GdkEvent *, PatternSelectP);
static void pattern_select_scroll_update     (GtkAdjustment *, gpointer);

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

static int last_x, last_y;
static int scroll_row, scroll_col, scroll_index;

gint NUM_PATTERN_COLUMNS = 6;
gint NUM_PATTERN_ROWS    = 5;
gint STD_CELL_SIZE = MIN_CELL_SIZE;

PatternSelectP
pattern_select_new ()
{
  PatternSelectP psp;
  GPatternP active;
  GtkWidget *vbox;
  GtkWidget *hbox;
  GtkWidget *sbar;
  GtkWidget *label_box;

  psp = g_malloc (sizeof (_PatternSelect));
  psp->preview = NULL;

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

  /* handle the wm close event */
  gtk_signal_connect (GTK_OBJECT (psp->shell), "delete_event",
		      GTK_SIGNAL_FUNC (pattern_select_delete_callback),
		      psp);

  psp->options_box = gtk_vbox_new (FALSE, 1);
  gtk_box_pack_start (GTK_BOX (vbox), psp->options_box, FALSE, FALSE, 0);

  /*  Create the active pattern label  */
  label_box = gtk_hbox_new (FALSE, 1);
  gtk_container_border_width (GTK_CONTAINER (label_box), 2);
  gtk_box_pack_start (GTK_BOX (psp->options_box), label_box, FALSE, FALSE, 0);
  psp->pattern_name = gtk_label_new ("Active");
  gtk_box_pack_start (GTK_BOX (label_box), psp->pattern_name, FALSE, FALSE, 2);
  psp->pattern_size = gtk_label_new ("(0x0)");
  gtk_box_pack_start (GTK_BOX (label_box), psp->pattern_size, FALSE, FALSE, 5);

  gtk_widget_show (psp->pattern_name);
  gtk_widget_show (psp->pattern_size);
  gtk_widget_show (label_box);

  /*  The horizontal box containing preview & scrollbar  */
  hbox = gtk_hbox_new (FALSE, 1);
  gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0);
  psp->frame = gtk_frame_new (NULL);
  gtk_frame_set_shadow_type (GTK_FRAME (psp->frame), GTK_SHADOW_IN);

  gtk_box_pack_start (GTK_BOX (hbox), psp->frame, TRUE, TRUE, 0);

  psp->sbar_data = GTK_ADJUSTMENT (gtk_adjustment_new (0, 0, MAX_WIN_HEIGHT, 1, 1, MAX_WIN_HEIGHT));
  gtk_signal_connect (GTK_OBJECT (psp->sbar_data), "value_changed",
		      (GtkSignalFunc) pattern_select_scroll_update,
		      psp);
  sbar = gtk_vscrollbar_new (psp->sbar_data);
  gtk_box_pack_start (GTK_BOX (hbox), sbar, FALSE, FALSE, 0);

  /*  Create the pattern preview window and the underlying image  */
  psp->cell_width = STD_CELL_SIZE;
  psp->cell_height = STD_CELL_SIZE;

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

  psp->preview = gtk_preview_new (GTK_PREVIEW_COLOR);
  gtk_preview_size (GTK_PREVIEW (psp->preview), psp->width, psp->height);
  gtk_widget_set_events (psp->preview, PATTERN_EVENT_MASK);

  gtk_signal_connect (GTK_OBJECT (psp->preview), "event",
		      (GtkSignalFunc) pattern_select_events,
		      psp);

  gtk_signal_connect_after (GTK_OBJECT(psp->frame), "size_allocate",
                           (GtkSignalFunc)pattern_select_resize,
                           psp);

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

  gtk_widget_show (sbar);
  gtk_widget_show (hbox);
  gtk_widget_show (psp->frame);

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

  gtk_widget_show (psp->options_box);
  gtk_widget_show (vbox);
  gtk_widget_show (psp->shell);

  preview_calc_scrollbar (psp);
  display_patterns (psp);

  /*  update the active selection  */
  active = get_active_pattern ();
  if (active)
    pattern_select_select (psp, active->index);

  return psp;
}

void
pattern_select_select (PatternSelectP psp,
		       int            index)
{
  int row, col;

  update_active_pattern_field (psp);
  row = index / NUM_PATTERN_COLUMNS;
  col = index - row * NUM_PATTERN_COLUMNS;

  pattern_select_show_selected (psp, row, col);
}

void
pattern_select_free (PatternSelectP psp)
{
  if (psp)
    {
      g_free (psp);
    }
}

static void
display_pattern (PatternSelectP psp,
		 GPatternP      pattern,
		 int            col,
		 int            row)
{
  TempBuf * pattern_buf;
  unsigned char * src, *s;
  unsigned char * buf, *b;
  int cell_width, cell_height;
  int width, height;
  int rowstride;
  int offset_x, offset_y;
  int yend;
  int ystart;
  int i, j;

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

  pattern_buf = pattern->mask;

  /*  calculate the offset into the image  */
  cell_width = psp->cell_width - 2*MARGIN_WIDTH;
  cell_height = psp->cell_height - 2*MARGIN_HEIGHT;
  width = (pattern_buf->width > cell_width) ? cell_width :
    pattern_buf->width;
  height = (pattern_buf->height > cell_height) ? cell_height :
    pattern_buf->height;

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

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

  /*  Get the pointer into the pattern mask data  */
  rowstride = pattern_buf->width * pattern_buf->bytes;
  src = temp_buf_data (pattern_buf) +
    (pattern->off_y + ystart - offset_y) * rowstride +
    pattern->off_x * pattern_buf->bytes;

  for (i = ystart; i < yend; i++)
    {
      s = src;
      b = buf;

      if (pattern_buf->bytes == 1)
	for (j = 0; j < width; j++)
	  {
	    *b++ = *s;
	    *b++ = *s;
	    *b++ = *s++;
	  }
      else
	for (j = 0; j < width; j++)
	  {
	    *b++ = *s++;
	    *b++ = *s++;
	    *b++ = *s++;
	  }

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

      src += rowstride;
    }

  g_free (buf);
}

static void
display_setup (PatternSelectP psp)
{
  unsigned char * buf;
  int i;

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

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

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

  g_free (buf);
}

static void
display_patterns (PatternSelectP psp)
{
  link_ptr list = pattern_list;    /*  the global pattern list  */
  int row, col;
  GPatternP pattern;

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

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

  row = col = 0;
  while (list)
    {
      pattern = (GPatternP) list->data;

      /*  Display the pattern  */
      display_pattern (psp, pattern, col, row);

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

      list = next_item (list);
    }
}

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

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

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

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

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

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

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

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

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

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

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

  area.x = offset_x;
  area.y = ystart;
  area.width = psp->cell_width;
  area.height = yend - ystart;
  gtk_widget_draw (psp->preview, &area);

  old_row = row;
  old_col = col;

  g_free (buf);
}

static void
draw_preview (PatternSelectP psp)
{
  /*  Draw the image buf to the preview window  */
  gtk_widget_draw (psp->preview, NULL);
}

static void
preview_calc_scrollbar (PatternSelectP psp)
{
  int num_rows;
  int page_size;
  int max;

  psp->scroll_offset = 0;
  num_rows = (num_patterns + NUM_PATTERN_COLUMNS - 1) / NUM_PATTERN_COLUMNS;
  max = num_rows * psp->cell_width;
  if (!num_rows) num_rows = 1;
  page_size = psp->preview->allocation.height;

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

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

static void
update_active_pattern_field (PatternSelectP psp)
{
  GPatternP pattern;
  char buf[32];

  pattern = get_active_pattern ();

  if (!pattern)
    return;

  /*  Set pattern name  */
  gtk_label_set (GTK_LABEL (psp->pattern_name), pattern->name);

  /*  Set pattern size  */
  sprintf (buf, "(%d X %d)", pattern->mask->width, pattern->mask->height);
  gtk_label_set (GTK_LABEL (psp->pattern_size), buf);
}

static gint
pattern_select_resize (GtkWidget      *widget,
		       GdkEvent       *event,
		       PatternSelectP  psp)
{  
/* calculate the best-fit approximation... */  
  gint wid;
  gint now;

  wid = widget->allocation.width-4;

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

  NUM_PATTERN_COLUMNS = wid / STD_CELL_SIZE;
  NUM_PATTERN_ROWS = (gint) (num_patterns + NUM_PATTERN_COLUMNS-1) / NUM_PATTERN_COLUMNS;

  psp->cell_width = STD_CELL_SIZE;
  psp->cell_height = STD_CELL_SIZE;
  psp->width = widget->allocation.width - 4;
  psp->height = widget->allocation.height - 4;

  /*
  NUM_PATTERN_COLUMNS=(gint)(widget->allocation.width/STD_CELL_WIDTH);
  NUM_PATTERN_ROWS = (num_patterns + NUM_PATTERN_COLUMNS - 1) / NUM_PATTERN_COLUMNS;
  */
  
/*  psp->width=widget->allocation.width;
  psp->height=widget->allocation.height; */

  gtk_preview_size (GTK_PREVIEW (psp->preview), psp->width, psp->height);
  
  /*  recalculate scrollbar extents  */
  preview_calc_scrollbar (psp);

  /*  render the patterns into the newly created image structure  */
  display_patterns (psp);

  /*  update the active selection  */
/*  active = get_active_pattern ();
  if (active)
    pattern_select_select (psp, active->index); */

  /*  update the display  */
  draw_preview (psp);
  
  return FALSE;
}

static gint
pattern_select_events (GtkWidget      *widget,
		       GdkEvent       *event,
		       PatternSelectP  psp)
{
  GdkEventButton *bevent;
  GdkEventMotion *mevent;
  GPatternP pattern;
  int row, col, index;
  int x_extent, y_extent;
  int tx, ty;

  switch (event->type)
    {
    case GDK_BUTTON_PRESS:
      bevent = (GdkEventButton *) event;

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

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

	  if ((pattern = get_pattern_by_index (index)))
	    {
	      gdk_pointer_grab (psp->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_pattern (pattern);
	    }
	}
      break;

    case GDK_BUTTON_RELEASE:
      bevent = (GdkEventButton *) event;
      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 ((pattern = get_pattern_by_index (scroll_index)))
	{
	  /*  What are the pattern subwindow scrolling extents?  */
	  x_extent = pattern->mask->width - psp->cell_width;
	  if (x_extent < 0) x_extent = 0;
	  y_extent = pattern->mask->height - psp->cell_height;
	  if (y_extent < 0) y_extent = 0;

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

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

	  display_pattern (psp, pattern, scroll_col, scroll_row);
	  pattern_select_show_selected (psp, scroll_row, scroll_col);
	}
      break;

    default:
      break;
    }

  return FALSE;
}

static gint
pattern_select_delete_callback (GtkWidget *w,
				GdkEvent *e,
				gpointer client_data)
{
  pattern_select_close_callback (w, client_data);

  return FALSE;
}

static void
pattern_select_close_callback (GtkWidget *w,
			       gpointer   client_data)
{
  PatternSelectP psp;
  psp = (PatternSelectP) client_data;
  if (GTK_WIDGET_VISIBLE (psp->shell))
    gtk_widget_hide (psp->shell);
}

static void
pattern_select_refresh_callback (GtkWidget *w,
				 gpointer   client_data)
{
  PatternSelectP psp;
  GPatternP active;

  psp = (PatternSelectP) client_data;

  /*  re-init the pattern list  */
  patterns_init ();

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

  /*  render the patterns into the newly created image structure  */
  display_patterns (psp);

  /*  update the active selection  */
  active = get_active_pattern ();
  if (active)
    pattern_select_select (psp, active->index);

  /*  update the display  */
  draw_preview (psp);
}

static void
pattern_select_scroll_update (GtkAdjustment *adjustment,
			      gpointer       data)
{
  PatternSelectP psp;
  GPatternP active;
  int row, col;

  psp = data;

  if (psp)
    {
      psp->scroll_offset = adjustment->value;
      display_patterns (psp);

      active = get_active_pattern ();
      if (active)
	{
	  row = active->index / NUM_PATTERN_COLUMNS;
	  col = active->index - row * NUM_PATTERN_COLUMNS;
	  pattern_select_show_selected (psp, row, col);
	}

      draw_preview (psp);
    }
}


