gimpbrushmenu.c 13.5 KB
Newer Older
1 2
/* LIBGIMP - The GIMP Library
 * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
3 4
 *
 * gimpbrushmenu.c
5 6 7
 * Copyright (C) 1998 Andy Thomas                
 *
 * This library is free software; you can redistribute it and/or
Marc Lehmann's avatar
Marc Lehmann committed
8
 * modify it under the terms of the GNU Lesser General Public
9
 * License as published by the Free Software Foundation; either
10 11 12 13 14
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
15 16
 * Library General Public License for more details.
 *
Marc Lehmann's avatar
Marc Lehmann committed
17
 * You should have received a copy of the GNU Lesser General Public
18 19 20
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
21 22
 */

Sven Neumann's avatar
Sven Neumann committed
23 24
#include "config.h"

25 26
#include <string.h>

27 28 29 30 31
#ifdef __GNUC__
#warning GTK_DISABLE_DEPRECATED
#endif
#undef GTK_DISABLE_DEPRECATED

32 33 34
#include "gimp.h"
#include "gimpui.h"

35 36
#include "libgimp-intl.h"

37 38 39 40 41

/* Idea is to have a function to call that returns a widget that 
 * completely controls the selection of a brush.
 * you get a widget returned that you can use in a table say.
 * In:- Initial brush name. Null means use current selection.
42
 *      pointer to func to call when brush changes (GimpRunBrushCallback).
43 44
 * Returned:- Pointer to a widget that you can use in UI.
 * 
45 46
 * Widget simply made up of a preview widget (20x20) containing the brush
 * mask and a button that can be clicked on to change the brush.
47 48 49
 */


50 51 52 53
#define BSEL_DATA_KEY     "__bsel_data"
#define CELL_SIZE         20
#define BRUSH_EVENT_MASK  GDK_EXPOSURE_MASK       | \
                          GDK_BUTTON_PRESS_MASK   | \
54 55 56
			  GDK_BUTTON_RELEASE_MASK | \
                          GDK_BUTTON1_MOTION_MASK 

57 58
struct __brushes_sel 
{
59 60
  gchar                *title;
  GimpRunBrushCallback  callback;
61 62 63 64 65 66 67 68
  GtkWidget            *brush_preview;
  GtkWidget            *device_brushpopup; 
  GtkWidget            *device_brushpreview;
  GtkWidget            *button;
  GtkWidget            *top_hbox;
  gchar                *brush_name;       /* Local copy */
  gdouble               opacity;
  gint                  spacing;
69
  GimpLayerModeEffects  paint_mode;
70 71
  gint                  width;
  gint                  height;
72 73
  guchar               *mask_data;        /* local copy */
  gchar                *brush_popup_pnt;  /* used to control the popup */
74
  gpointer              data;
75 76
};

77
typedef struct __brushes_sel BSelect;
78

79

80 81 82
static void  brush_popup_close (BSelect *bsel);


83
static void
84 85 86
brush_popup_open (BSelect  *bsel,
		  gint      x,
		  gint      y)
87
{
88
  GtkWidget    *frame;
89 90 91 92 93 94 95 96
  const guchar *src;
  const guchar *s;
  guchar       *buf;
  guchar       *b;
  gint          x_org;
  gint          y_org;
  gint          scr_w;
  gint          scr_h;
97

98 99
  if (bsel->device_brushpopup)
    brush_popup_close (bsel);
100

101 102
  if (bsel->width <= CELL_SIZE && bsel->height <= CELL_SIZE)
    return;
103

104
  bsel->device_brushpopup = gtk_window_new (GTK_WINDOW_POPUP);
105

106 107 108 109 110 111 112 113
  frame = gtk_frame_new (NULL);
  gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT);
  gtk_container_add (GTK_CONTAINER (bsel->device_brushpopup), frame);
  gtk_widget_show (frame);
  
  bsel->device_brushpreview = gtk_preview_new (GTK_PREVIEW_GRAYSCALE);
  gtk_container_add (GTK_CONTAINER (frame), bsel->device_brushpreview);
  gtk_widget_show (bsel->device_brushpreview);
114 115 116 117 118

  /* decide where to put the popup */
  gdk_window_get_origin (bsel->brush_preview->window, &x_org, &y_org);
  scr_w = gdk_screen_width ();
  scr_h = gdk_screen_height ();
119 120 121

  x = x_org + x - (bsel->width  / 2);
  y = y_org + y - (bsel->height / 2);
122 123
  x = (x < 0) ? 0 : x;
  y = (y < 0) ? 0 : y;
124
  x = (x + bsel->width  > scr_w) ? scr_w - bsel->width  : x;
125
  y = (y + bsel->height > scr_h) ? scr_h - bsel->height : y;
126

127 128
  gtk_preview_size (GTK_PREVIEW (bsel->device_brushpreview),
                    bsel->width, bsel->height);
129

130
  gtk_window_move (GTK_WINDOW (bsel->device_brushpopup), x, y);
131 132
  
  /*  Draw the brush  */
133 134
  buf = g_new (guchar, bsel->width);
  memset (buf, 255, bsel->width);
135 136
  
  src = bsel->mask_data;
137

138 139 140
  for (y = 0; y < bsel->height; y++)
    {
      int j;
141

142 143
      s = src;
      b = buf;
144

145 146 147
      for (j = 0; j < bsel->width ; j++)
	*b++ = 255 - *s++;

148
      gtk_preview_draw_row (GTK_PREVIEW (bsel->device_brushpreview), 
149
			    buf, 0, y, bsel->width);
150 151
      src += bsel->width;
    }
152

153
  g_free (buf);
154

155
  gtk_widget_show (bsel->device_brushpopup);
156 157 158
}

static void
159
brush_popup_close (BSelect *bsel)
160
{
161
  if (bsel->device_brushpopup)
162 163 164 165
    {
      gtk_widget_destroy (bsel->device_brushpopup);
      bsel->device_brushpopup = NULL;
    }
166 167 168 169
}

static gint
brush_preview_events (GtkWidget    *widget,
170 171
		      GdkEvent     *event,
		      gpointer      data)
172 173
{
  GdkEventButton *bevent;
174
  BSelect        *bsel = (BSelect*)data;
175

176
  if (bsel->mask_data)
177 178 179 180 181 182 183 184 185 186 187
    {
      switch (event->type)
	{
	case GDK_EXPOSE:
	  break;
	  
	case GDK_BUTTON_PRESS:
	  bevent = (GdkEventButton *) event;
	  
	  if (bevent->button == 1)
	    {
188
	      gtk_grab_add (widget);
189
	      brush_popup_open (bsel, bevent->x, bevent->y);
190 191 192 193 194 195 196 197
	    }
	  break;
	  
	case GDK_BUTTON_RELEASE:
	  bevent = (GdkEventButton *) event;
	  
	  if (bevent->button == 1)
	    {
198
	      gtk_grab_remove (widget);
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
	      brush_popup_close (bsel);
	    }
	  break;
	case GDK_DELETE:
	  break;
	  
	default:
	  break;
	}
    }

  return FALSE;
}

static void
214 215 216 217
brush_preview_update (GtkWidget   *brush_preview,
		      gint         brush_width,
		      gint         brush_height,
		      const gchar *mask_data)
218
{
219 220 221 222 223 224 225 226
  guchar       *buf;
  guchar       *b;
  const guchar *src;
  const guchar *s;
  gint          i, y;
  gint          offset_x, offset_y;
  gint          ystart, yend;
  gint          width, height;
227

228
  /*  Draw the brush  */
229
  buf = g_new (guchar, CELL_SIZE); 
230 231

  /* Set buffer to white */  
232
  memset (buf, 255, CELL_SIZE);
233
  for (i = 0; i < CELL_SIZE; i++)
234
    gtk_preview_draw_row (GTK_PREVIEW (brush_preview),
235 236 237 238 239 240 241 242
			  buf, 0, i, CELL_SIZE);

 /* Limit to cell size */
  width  = (brush_width  > CELL_SIZE) ? CELL_SIZE: brush_width;
  height = (brush_height > CELL_SIZE) ? CELL_SIZE: brush_height;

  offset_x = ((CELL_SIZE - width)  / 2);
  offset_y = ((CELL_SIZE - height) / 2);
243 244

  ystart = CLAMP (offset_y, 0, CELL_SIZE);
245
  yend   = CLAMP (offset_y + height, 0, CELL_SIZE);
246 247 248 249 250

  src = mask_data;

  for (y = ystart; y < yend; y++)
    {
251 252
      gint j;

253 254 255 256 257
      s = src;
      b = buf;
      for (j = 0; j < width ; j++)
	*b++ = 255 - *s++;

258 259
      gtk_preview_draw_row (GTK_PREVIEW (brush_preview),
			    buf, offset_x, y, width);
260 261
      src += brush_width;
    }
262

263
  g_free (buf);
264

265
  gtk_widget_queue_draw (brush_preview);
266 267 268
}

static void
269 270 271 272 273 274 275 276 277
brush_select_invoker (const gchar          *name,
		      gdouble               opacity,
		      gint                  spacing,
		      GimpLayerModeEffects  paint_mode,
		      gint                  width,
		      gint                  height,
		      const guchar         *mask_data,
		      gboolean              closing,
		      gpointer              data)
278
{
279
  BSelect *bsel = (BSelect *) data;
280

281 282
  g_free (bsel->brush_name);
  g_free (bsel->mask_data);
283

284 285 286 287 288 289
  bsel->brush_name = g_strdup (name);
  bsel->width      = width;
  bsel->height     = height;
  bsel->mask_data  = g_memdup (mask_data, width * height);
  bsel->opacity    = opacity;
  bsel->spacing    = spacing;
290
  bsel->paint_mode = paint_mode;
291

292 293 294 295 296
  brush_preview_update (bsel->brush_preview, width, height, mask_data);

  if (bsel->callback)
    bsel->callback (name, opacity, spacing, paint_mode, 
		    width, height, mask_data, closing, bsel->data);
297

298
  if (closing)
299
    bsel->brush_popup_pnt = NULL;
300 301 302 303 304 305
}

static void
brush_select_callback (GtkWidget *widget,
		       gpointer   data)
{
306 307
  BSelect *bsel = (BSelect*)data;

308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328
  if (bsel->brush_popup_pnt)
    {
      /*  calling gimp_brushes_set_popup() raises the dialog  */
      gimp_brushes_set_popup (bsel->brush_popup_pnt, 
			      bsel->brush_name,
			      bsel->opacity,
			      bsel->spacing,
			      bsel->paint_mode);
    }
  else
    {
      bsel->brush_popup_pnt = 
	gimp_interactive_selection_brush (bsel->title ?
					  bsel->title : _("Brush Selection"),
					  bsel->brush_name,
					  bsel->opacity,
					  bsel->spacing,
					  bsel->paint_mode,
					  brush_select_invoker,
					  bsel);
    }
329 330
}

331 332
/**
 * gimp_brush_select_widget:
333 334
 * @title: Title of the dialog to use or %NULL to use the default title.
 * @brush_name: Initial brush name or %NULL to use current selection. 
335 336 337
 * @opacity: Initial opacity. -1 means to use current opacity.
 * @spacing: Initial spacing. -1 means to use current spacing.
 * @paint_mode: Initial paint mode.  -1 means to use current paint mode.
338 339
 * @callback: a function to call when the selected brush changes.
 * @data: a pointer to arbitary data to be used in the call to @callback.
340 341 342 343 344
 *
 * Creates a new #GtkWidget that completely controls the selection of a 
 * #GimpBrush.  This widget is suitable for placement in a table in a
 * plug-in dialog.
 *
345
 * Returns: A #GtkWidget that you can use in your UI.
346
 */
347
GtkWidget * 
348 349
gimp_brush_select_widget (const gchar          *title,
			  const gchar          *brush_name, 
350 351
			  gdouble               opacity,
			  gint                  spacing,
352 353
			  GimpLayerModeEffects  paint_mode,
			  GimpRunBrushCallback  callback,
354
			  gpointer              data)
355
{
356 357 358 359 360 361 362
  GtkWidget *frame;
  GtkWidget *hbox;
  GtkWidget *brush;
  GtkWidget *button;
  gint       width;
  gint       height;
  gint       init_spacing;
363
  GimpLayerModeEffects  init_paint_mode;
364
  gdouble    init_opacity;
365
  gint       mask_data_size;
366
  guint8    *mask_data;
367
  gchar     *name;
368 369
  BSelect   *bsel;

370
  bsel = g_new0 (BSelect, 1);
371 372

  hbox = gtk_hbox_new (FALSE, 3);
373
  gtk_widget_show (hbox);
374 375 376

  frame = gtk_frame_new (NULL);
  gtk_frame_set_shadow_type (GTK_FRAME(frame), GTK_SHADOW_OUT);
377
  gtk_widget_show (frame);
378 379 380

  brush = gtk_preview_new (GTK_PREVIEW_GRAYSCALE);
  gtk_preview_size (GTK_PREVIEW (brush), CELL_SIZE, CELL_SIZE); 
381 382
  gtk_widget_show (brush);
  gtk_container_add (GTK_CONTAINER (frame), brush); 
383 384 385

  gtk_widget_set_events (brush, BRUSH_EVENT_MASK);

386
  g_signal_connect (brush, "event",
387 388
                    G_CALLBACK (brush_preview_events),
                    bsel);
389

390
  bsel->callback          = callback;
391
  bsel->data              = data;
392
  bsel->device_brushpopup = bsel->device_brushpreview = NULL;
393
  bsel->brush_preview     = brush;
394
  bsel->title             = g_strdup (title);
395 396

  /* Do initial brush setup */
397 398 399 400 401 402 403 404 405 406
  name = gimp_brushes_get_brush_data ((gchar *) brush_name,
				      &init_opacity,
				      &init_spacing,
				      &init_paint_mode,
				      &width,
				      &height,
				      &mask_data_size,
				      &mask_data);

  if (name)
407
    {
408 409 410 411
      bsel->brush_name = name;
      bsel->opacity    = (opacity == -1.0)  ? init_opacity    : opacity;
      bsel->spacing    = (spacing == -1)    ? init_spacing    : spacing;
      bsel->paint_mode = (paint_mode == -1) ? init_paint_mode : paint_mode;
412 413
      bsel->width      = width;
      bsel->height     = height;
414 415 416
      bsel->mask_data  = mask_data;

      brush_preview_update (bsel->brush_preview, width, height, mask_data);
417 418 419 420 421 422
    }

  gtk_box_pack_start (GTK_BOX (hbox), frame, TRUE, TRUE, 0);

  button = gtk_button_new_with_label ("... ");
  gtk_container_add (GTK_CONTAINER (hbox), button); 
423
  gtk_widget_show (button);
424 425

  bsel->button = button;
426
  g_signal_connect (button, "clicked",
427 428
                    G_CALLBACK (brush_select_callback),
                    bsel);
429

430
  g_object_set_data (G_OBJECT (hbox), BSEL_DATA_KEY, bsel);
431 432 433 434 435

  return hbox;
}


436 437 438 439 440 441
/**
 * gimp_brush_select_widget_close_popup:
 * @widget: A brush select widget.
 *
 * Closes the popup window associated with @widget.
 */
442
void
443
gimp_brush_select_widget_close_popup (GtkWidget *widget)
444
{
445
  BSelect  *bsel;
446

447
  bsel = (BSelect *) g_object_get_data (G_OBJECT (widget), BSEL_DATA_KEY);
448

449
  if (bsel && bsel->brush_popup_pnt)
450
    {
451
      gimp_brushes_close_popup (bsel->brush_popup_pnt);
452 453 454 455
      bsel->brush_popup_pnt = NULL;
    }
}

456 457 458
/**
 * gimp_brush_select_widget_set_popup:
 * @widget: A brush select widget.
459
 * @brush_name: Brush name to set; %NULL means no change. 
460 461 462 463 464 465 466 467
 * @opacity: Opacity to set. -1 means no change.
 * @spacing: Spacing to set. -1 means no change.
 * @paint_mode: Paint mode to set.  -1 means no change.
 *
 * Sets the current brush and other values for the brush
 * select widget.  Calls the callback function if one was
 * supplied in the call to gimp_brush_select_widget().
 */
468
void
469 470 471 472 473
gimp_brush_select_widget_set_popup (GtkWidget            *widget,
				    const gchar          *brush_name,
				    gdouble               opacity,
				    gint                  spacing,
				    GimpLayerModeEffects  paint_mode)
474
{
475 476 477
  gint     width;
  gint     height;
  gint     init_spacing;
478
  GimpLayerModeEffects init_paint_mode;
479
  gdouble  init_opacity;
480
  gint     mask_data_size;
481
  guint8  *mask_data;
482
  gchar   *name;
483 484
  BSelect *bsel;
  
485
  bsel = (BSelect *) g_object_get_data (G_OBJECT (widget), BSEL_DATA_KEY);
486

487
  if (bsel)
488
    {
489 490 491 492 493 494 495 496 497 498 499 500
      name = gimp_brushes_get_brush_data ((gchar *) brush_name,
					  &init_opacity,
					  &init_spacing,
					  &init_paint_mode,
					  &width,
					  &height,
					  &mask_data_size,
					  &mask_data);

      if (opacity == -1.0)  opacity    = init_opacity;
      if (spacing == -1)    spacing    = init_spacing;
      if (paint_mode == -1) paint_mode = init_paint_mode;
501
  
502
      brush_select_invoker (brush_name, opacity, spacing, paint_mode,
503
			    width, height, mask_data, 0, bsel);
504

505 506
      if (bsel->brush_popup_pnt)
	gimp_brushes_set_popup (bsel->brush_popup_pnt, 
507 508 509 510
				name, opacity, spacing, paint_mode);

      g_free (name);
      g_free (mask_data);
511 512
    }
}