gimp/app/pattern_select.c

710 lines
20 KiB
C

/* 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 pattern_popup_open (PatternSelectP, int, int, GPatternP);
static void pattern_popup_close (PatternSelectP);
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 },
};
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;
psp->pattern_popup = 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)
{
if (psp->pattern_popup != NULL)
gtk_widget_destroy (psp->pattern_popup);
g_free (psp);
}
}
/*
* Local functions
*/
static void
pattern_popup_open (PatternSelectP psp,
int x,
int y,
GPatternP pattern)
{
gint x_org, y_org;
gint scr_w, scr_h;
gchar *src, *buf;
if (psp->pattern_popup == NULL)
{
GtkWidget *frame;
psp->pattern_popup = gtk_window_new (GTK_WINDOW_POPUP);
gtk_window_set_policy (GTK_WINDOW (psp->pattern_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 (psp->pattern_popup), frame);
gtk_widget_show (frame);
psp->pattern_preview = gtk_preview_new (GTK_PREVIEW_COLOR);
gtk_container_add (GTK_CONTAINER (frame), psp->pattern_preview);
gtk_widget_show (psp->pattern_preview);
}
else
gtk_widget_hide (psp->pattern_popup);
gdk_window_get_origin (psp->preview->window, &x_org, &y_org);
scr_w = gdk_screen_width ();
scr_h = gdk_screen_height ();
x = x_org + x - pattern->mask->width * 0.5;
y = y_org + y - pattern->mask->height * 0.5;
x = (x < 0) ? 0 : x;
y = (y < 0) ? 0 : y;
x = (x + pattern->mask->width > scr_w) ? scr_w - pattern->mask->width : x;
y = (y + pattern->mask->height > scr_h) ? scr_h - pattern->mask->height : y;
gtk_preview_size (GTK_PREVIEW (psp->pattern_preview), pattern->mask->width, pattern->mask->height);
gtk_widget_popup (psp->pattern_popup, x, y);
/* Draw the pattern */
buf = g_new (gchar, pattern->mask->width * 3);
src = temp_buf_data (pattern->mask);
for (y = 0; y < pattern->mask->height; y++)
{
if (pattern->mask->bytes == 1)
for (x = 0; x < pattern->mask->width; x++)
{
buf[x*3+0] = src[x];
buf[x*3+1] = src[x];
buf[x*3+2] = src[x];
}
else
for (x = 0; x < pattern->mask->width; x++)
{
buf[x*3+0] = src[x*3+0];
buf[x*3+1] = src[x*3+1];
buf[x*3+2] = src[x*3+2];
}
gtk_preview_draw_row (GTK_PREVIEW (psp->pattern_preview), buf, 0, y, pattern->mask->width);
src += pattern->mask->width * pattern->mask->bytes;
}
/* Draw the pattern preview */
gtk_widget_draw (psp->pattern_preview, NULL);
}
static void
pattern_popup_close (PatternSelectP psp)
{
if (psp->pattern_popup != NULL)
gtk_widget_hide (psp->pattern_popup);
}
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) + (ystart - offset_y) * rowstride;
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;
GPatternP pattern;
int row, col, index;
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;
/* Get the pattern and display the popup pattern preview */
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 pattern the active pattern */
select_pattern (pattern);
/* Show the pattern popup window if the pattern is too large */
if (pattern->mask->width > psp->cell_width ||
pattern->mask->height > psp->cell_height)
pattern_popup_open (psp, bevent->x, bevent->y, pattern);
}
}
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 */
pattern_popup_close (psp);
}
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);
}
}