1 /* GIMP - The GNU Image Manipulation Program
2  * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
16  */
17 
18 /* Original plug-in coded by Tim Newsome.
19  *
20  * Changed to make use of real-life units by Sven Neumann <sven@gimp.org>.
21  *
22  * The interface code is heavily commented in the hope that it will
23  * help other plug-in developers to adapt their plug-ins to make use
24  * of the gimp_size_entry functionality.
25  *
26  * Note: There is a convenience constructor called gimp_coordinetes_new ()
27  *       which simplifies the task of setting up a standard X,Y sizeentry.
28  *
29  * For more info and bugs see libgimp/gimpsizeentry.h and libgimp/gimpwidgets.h
30  *
31  * May 2000 tim copperfield [timecop@japan.co.jp]
32  * http://www.ne.jp/asahi/linux/timecop
33  * Added dynamic preview.  Due to weird implementation of signals from all
34  * controls, preview will not auto-update.  But this plugin isn't really
35  * crying for real-time updating either.
36  *
37  */
38 
39 #include "config.h"
40 
41 #include <string.h>
42 
43 #include <libgimp/gimp.h>
44 #include <libgimp/gimpui.h>
45 
46 #include "libgimp/stdplugins-intl.h"
47 
48 
49 #define PLUG_IN_PROC        "plug-in-grid"
50 #define PLUG_IN_BINARY      "grid"
51 #define PLUG_IN_ROLE        "gimp-grid"
52 #define SPIN_BUTTON_WIDTH    8
53 #define COLOR_BUTTON_WIDTH  55
54 
55 
56 /* Declare local functions. */
57 static void   query  (void);
58 static void   run    (const gchar      *name,
59                       gint              nparams,
60                       const GimpParam  *param,
61                       gint             *nreturn_vals,
62                       GimpParam       **return_vals);
63 
64 static guchar      best_cmap_match (const guchar  *cmap,
65                                     gint           ncolors,
66                                     const GimpRGB *color);
67 static void        grid            (gint32         image_ID,
68                                     gint32         drawable_ID,
69                                     GimpPreview   *preview);
70 static gint        dialog          (gint32         image_ID,
71                                     gint32         drawable_ID);
72 
73 const GimpPlugInInfo PLUG_IN_INFO =
74 {
75   NULL,  /* init_proc  */
76   NULL,  /* quit_proc  */
77   query, /* query_proc */
78   run,   /* run_proc   */
79 };
80 
81 static gint sx1, sy1, sx2, sy2;
82 
83 static GtkWidget *main_dialog    = NULL;
84 static GtkWidget *hcolor_button  = NULL;
85 static GtkWidget *vcolor_button  = NULL;
86 
87 typedef struct
88 {
89   gint    hwidth;
90   gint    hspace;
91   gint    hoffset;
92   GimpRGB hcolor;
93   gint    vwidth;
94   gint    vspace;
95   gint    voffset;
96   GimpRGB vcolor;
97   gint    iwidth;
98   gint    ispace;
99   gint    ioffset;
100   GimpRGB icolor;
101 } Config;
102 
103 static Config grid_cfg =
104 {
105   1, 16, 8, { 0.0, 0.0, 0.0, 1.0 },    /* horizontal   */
106   1, 16, 8, { 0.0, 0.0, 0.0, 1.0 },    /* vertical     */
107   0,  2, 6, { 0.0, 0.0, 0.0, 1.0 },    /* intersection */
108 };
109 
110 
MAIN()111 MAIN ()
112 
113 static
114 void query (void)
115 {
116   static const GimpParamDef args[] =
117   {
118     { GIMP_PDB_INT32,    "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }"   },
119     { GIMP_PDB_IMAGE,    "image",    "Input image"                    },
120     { GIMP_PDB_DRAWABLE, "drawable", "Input drawable"                 },
121 
122     { GIMP_PDB_INT32,    "hwidth",   "Horizontal Width   (>= 0)"      },
123     { GIMP_PDB_INT32,    "hspace",   "Horizontal Spacing (>= 1)"      },
124     { GIMP_PDB_INT32,    "hoffset",  "Horizontal Offset  (>= 0)"      },
125     { GIMP_PDB_COLOR,    "hcolor",   "Horizontal Colour"              },
126     { GIMP_PDB_INT8,     "hopacity", "Horizontal Opacity (0...255)"   },
127 
128     { GIMP_PDB_INT32,    "vwidth",   "Vertical Width   (>= 0)"        },
129     { GIMP_PDB_INT32,    "vspace",   "Vertical Spacing (>= 1)"        },
130     { GIMP_PDB_INT32,    "voffset",  "Vertical Offset  (>= 0)"        },
131     { GIMP_PDB_COLOR,    "vcolor",   "Vertical Colour"                },
132     { GIMP_PDB_INT8,     "vopacity", "Vertical Opacity (0...255)"     },
133 
134     { GIMP_PDB_INT32,    "iwidth",   "Intersection Width   (>= 0)"    },
135     { GIMP_PDB_INT32,    "ispace",   "Intersection Spacing (>= 0)"    },
136     { GIMP_PDB_INT32,    "ioffset",  "Intersection Offset  (>= 0)"    },
137     { GIMP_PDB_COLOR,    "icolor",   "Intersection Colour"            },
138     { GIMP_PDB_INT8,     "iopacity", "Intersection Opacity (0...255)" }
139   };
140 
141   gimp_install_procedure (PLUG_IN_PROC,
142                           N_("Draw a grid on the image"),
143                           "Draws a grid using the specified colors. "
144                           "The grid origin is the upper left corner.",
145                           "Tim Newsome",
146                           "Tim Newsome, Sven Neumann, Tom Rathborne, TC",
147                           "1997 - 2000",
148                           N_("_Grid (legacy)..."),
149                           "RGB*, GRAY*, INDEXED*",
150                           GIMP_PLUGIN,
151                           G_N_ELEMENTS (args), 0,
152                           args, NULL);
153 
154   gimp_plugin_menu_register (PLUG_IN_PROC, "<Image>/Filters/Render/Pattern");
155 }
156 
157 static void
run(const gchar * name,gint n_params,const GimpParam * param,gint * nreturn_vals,GimpParam ** return_vals)158 run (const gchar      *name,
159      gint              n_params,
160      const GimpParam  *param,
161      gint             *nreturn_vals,
162      GimpParam       **return_vals)
163 {
164   static GimpParam   values[1];
165   gint32             image_ID;
166   gint32             drawable_ID;
167   GimpRunMode        run_mode;
168   GimpPDBStatusType  status = GIMP_PDB_SUCCESS;
169 
170   INIT_I18N ();
171   gegl_init (NULL, NULL);
172 
173   *nreturn_vals = 1;
174   *return_vals  = values;
175 
176   run_mode    = param[0].data.d_int32;
177   image_ID    = param[1].data.d_int32;
178   drawable_ID = param[2].data.d_drawable;
179 
180   if (run_mode == GIMP_RUN_NONINTERACTIVE)
181     {
182       if (n_params != 18)
183         status = GIMP_PDB_CALLING_ERROR;
184 
185       if (status == GIMP_PDB_SUCCESS)
186         {
187           grid_cfg.hwidth  = MAX (0, param[3].data.d_int32);
188           grid_cfg.hspace  = MAX (1, param[4].data.d_int32);
189           grid_cfg.hoffset = MAX (0, param[5].data.d_int32);
190           grid_cfg.hcolor  = param[6].data.d_color;
191 
192           gimp_rgb_set_alpha (&(grid_cfg.hcolor),
193                               ((double) param[7].data.d_int8) / 255.0);
194 
195 
196           grid_cfg.vwidth  = MAX (0, param[8].data.d_int32);
197           grid_cfg.vspace  = MAX (1, param[9].data.d_int32);
198           grid_cfg.voffset = MAX (0, param[10].data.d_int32);
199           grid_cfg.vcolor  = param[11].data.d_color;
200 
201           gimp_rgb_set_alpha (&(grid_cfg.vcolor),
202                               ((double) param[12].data.d_int8) / 255.0);
203 
204 
205 
206           grid_cfg.iwidth  = MAX (0, param[13].data.d_int32);
207           grid_cfg.ispace  = MAX (0, param[14].data.d_int32);
208           grid_cfg.ioffset = MAX (0, param[15].data.d_int32);
209           grid_cfg.icolor  = param[16].data.d_color;
210 
211           gimp_rgb_set_alpha (&(grid_cfg.icolor),
212                               ((double) (guint) param[17].data.d_int8) / 255.0);
213 
214 
215         }
216     }
217   else
218     {
219       gimp_context_get_foreground (&grid_cfg.hcolor);
220       grid_cfg.vcolor = grid_cfg.icolor = grid_cfg.hcolor;
221 
222       /*  Possibly retrieve data  */
223       gimp_get_data (PLUG_IN_PROC, &grid_cfg);
224     }
225 
226   if (run_mode == GIMP_RUN_INTERACTIVE)
227     {
228       if (! dialog (image_ID, drawable_ID))
229         {
230           /* The dialog was closed, or something similarly evil happened. */
231           status = GIMP_PDB_EXECUTION_ERROR;
232         }
233     }
234 
235   if (grid_cfg.hspace <= 0 || grid_cfg.vspace <= 0)
236     {
237       status = GIMP_PDB_EXECUTION_ERROR;
238     }
239 
240   if (status == GIMP_PDB_SUCCESS)
241     {
242       gimp_progress_init (_("Drawing grid"));
243 
244       grid (image_ID, drawable_ID, NULL);
245 
246       if (run_mode != GIMP_RUN_NONINTERACTIVE)
247         gimp_displays_flush ();
248 
249       if (run_mode == GIMP_RUN_INTERACTIVE)
250         gimp_set_data (PLUG_IN_PROC, &grid_cfg, sizeof (grid_cfg));
251     }
252 
253   values[0].type          = GIMP_PDB_STATUS;
254   values[0].data.d_status = status;
255 }
256 
257 
258 #define MAXDIFF 195076
259 
260 static guchar
best_cmap_match(const guchar * cmap,gint ncolors,const GimpRGB * color)261 best_cmap_match (const guchar  *cmap,
262                  gint           ncolors,
263                  const GimpRGB *color)
264 {
265   guchar cmap_index = 0;
266   gint   max = MAXDIFF;
267   gint   i, diff, sum;
268   guchar r, g, b;
269 
270   gimp_rgb_get_uchar (color, &r, &g, &b);
271 
272   for (i = 0; i < ncolors; i++)
273     {
274       diff = r - *cmap++;
275       sum = SQR (diff);
276       diff = g - *cmap++;
277       sum += SQR (diff);
278       diff = b - *cmap++;
279       sum += SQR (diff);
280 
281       if (sum < max)
282         {
283           cmap_index = i;
284           max = sum;
285         }
286     }
287 
288   return cmap_index;
289 }
290 
291 static inline void
pix_composite(guchar * p1,guchar p2[4],gint bytes,gboolean blend,gboolean alpha)292 pix_composite (guchar   *p1,
293                guchar    p2[4],
294                gint      bytes,
295                gboolean  blend,
296                gboolean  alpha)
297 {
298   gint b;
299 
300   if (blend)
301     {
302       if (alpha)
303         bytes--;
304 
305       for (b = 0; b < bytes; b++)
306         {
307           *p1 = *p1 * (1.0 - p2[3]/255.0) + p2[b] * p2[3]/255.0;
308           p1++;
309         }
310     }
311   else
312     {
313       /* blend should only be TRUE for indexed (bytes == 1) */
314       *p1++ = *p2;
315     }
316 
317   if (alpha && *p1 < 255)
318     {
319       b = *p1 + 255.0 * ((gdouble) p2[3] / (255.0 - *p1));
320 
321       *p1 = b > 255 ? 255 : b;
322     }
323 }
324 
325 static void
grid(gint32 image_ID,gint32 drawable_ID,GimpPreview * preview)326 grid (gint32       image_ID,
327       gint32       drawable_ID,
328       GimpPreview *preview)
329 {
330   GeglBuffer *src_buffer;
331   GeglBuffer *dest_buffer;
332   const Babl *format;
333   gint        bytes;
334   gint        x_offset;
335   gint        y_offset;
336   guchar     *dest;
337   guchar     *buffer = NULL;
338   gint        x, y;
339   gboolean    alpha;
340   gboolean    blend;
341   guchar      hcolor[4];
342   guchar      vcolor[4];
343   guchar      icolor[4];
344   guchar     *cmap;
345   gint        ncolors;
346 
347   gimp_rgba_get_uchar (&grid_cfg.hcolor,
348                        hcolor, hcolor + 1, hcolor + 2, hcolor + 3);
349   gimp_rgba_get_uchar (&grid_cfg.vcolor,
350                        vcolor, vcolor + 1, vcolor + 2, vcolor + 3);
351   gimp_rgba_get_uchar (&grid_cfg.icolor,
352                        icolor, icolor + 1, icolor + 2, icolor + 3);
353 
354   alpha = gimp_drawable_has_alpha (drawable_ID);
355 
356   switch (gimp_image_base_type (image_ID))
357     {
358     case GIMP_RGB:
359       blend = TRUE;
360 
361       if (alpha)
362         format = babl_format ("R'G'B'A u8");
363       else
364         format = babl_format ("R'G'B' u8");
365       break;
366 
367     case GIMP_GRAY:
368       hcolor[0] = gimp_rgb_luminance_uchar (&grid_cfg.hcolor);
369       vcolor[0] = gimp_rgb_luminance_uchar (&grid_cfg.vcolor);
370       icolor[0] = gimp_rgb_luminance_uchar (&grid_cfg.icolor);
371       blend = TRUE;
372 
373       if (alpha)
374         format = babl_format ("Y'A u8");
375       else
376         format = babl_format ("Y' u8");
377       break;
378 
379     case GIMP_INDEXED:
380       cmap = gimp_image_get_colormap (image_ID, &ncolors);
381 
382       hcolor[0] = best_cmap_match (cmap, ncolors, &grid_cfg.hcolor);
383       vcolor[0] = best_cmap_match (cmap, ncolors, &grid_cfg.vcolor);
384       icolor[0] = best_cmap_match (cmap, ncolors, &grid_cfg.icolor);
385 
386       g_free (cmap);
387       blend = FALSE;
388 
389       format = gimp_drawable_get_format (drawable_ID);
390       break;
391 
392     default:
393       g_assert_not_reached ();
394       blend = FALSE;
395     }
396 
397   bytes = babl_format_get_bytes_per_pixel (format);
398 
399   if (preview)
400     {
401       gimp_preview_get_position (preview, &sx1, &sy1);
402       gimp_preview_get_size (preview, &sx2, &sy2);
403 
404       buffer = g_new (guchar, bytes * sx2 * sy2);
405 
406       sx2 += sx1;
407       sy2 += sy1;
408     }
409   else
410     {
411       gint w, h;
412 
413       if (! gimp_drawable_mask_intersect (drawable_ID,
414                                           &sx1, &sy1, &w, &h))
415         return;
416 
417       sx2 = sx1 + w;
418       sy2 = sy1 + h;
419 
420       dest_buffer = gimp_drawable_get_shadow_buffer (drawable_ID);
421     }
422 
423   src_buffer = gimp_drawable_get_buffer (drawable_ID);
424 
425   dest = g_new (guchar, (sx2 - sx1) * bytes);
426 
427   for (y = sy1; y < sy2; y++)
428     {
429       gegl_buffer_get (src_buffer, GEGL_RECTANGLE (sx1, y, sx2 - sx1, 1), 1.0,
430                        format, dest,
431                        GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
432 
433       y_offset = y - grid_cfg.hoffset;
434       while (y_offset < 0)
435         y_offset += grid_cfg.hspace;
436 
437       if ((y_offset +
438            (grid_cfg.hwidth / 2)) % grid_cfg.hspace < grid_cfg.hwidth)
439         {
440           for (x = sx1; x < sx2; x++)
441             {
442               pix_composite (&dest[(x-sx1) * bytes],
443                              hcolor, bytes, blend, alpha);
444             }
445         }
446 
447       for (x = sx1; x < sx2; x++)
448         {
449           x_offset = grid_cfg.vspace + x - grid_cfg.voffset;
450           while (x_offset < 0)
451             x_offset += grid_cfg.vspace;
452 
453           if ((x_offset +
454                (grid_cfg.vwidth / 2)) % grid_cfg.vspace < grid_cfg.vwidth)
455             {
456               pix_composite (&dest[(x-sx1) * bytes],
457                              vcolor, bytes, blend, alpha);
458             }
459 
460           if ((x_offset +
461                (grid_cfg.iwidth / 2)) % grid_cfg.vspace < grid_cfg.iwidth
462               &&
463               ((y_offset % grid_cfg.hspace >= grid_cfg.ispace
464                 &&
465                 y_offset % grid_cfg.hspace < grid_cfg.ioffset)
466                ||
467                (grid_cfg.hspace -
468                 (y_offset % grid_cfg.hspace) >= grid_cfg.ispace
469                 &&
470                 grid_cfg.hspace -
471                 (y_offset % grid_cfg.hspace) < grid_cfg.ioffset)))
472             {
473               pix_composite (&dest[(x-sx1) * bytes],
474                              icolor, bytes, blend, alpha);
475             }
476         }
477 
478       if ((y_offset +
479            (grid_cfg.iwidth / 2)) % grid_cfg.hspace < grid_cfg.iwidth)
480         {
481           for (x = sx1; x < sx2; x++)
482             {
483               x_offset = grid_cfg.vspace + x - grid_cfg.voffset;
484               while (x_offset < 0)
485                 x_offset += grid_cfg.vspace;
486 
487               if ((x_offset % grid_cfg.vspace >= grid_cfg.ispace
488                    &&
489                    x_offset % grid_cfg.vspace < grid_cfg.ioffset)
490                   ||
491                   (grid_cfg.vspace -
492                    (x_offset % grid_cfg.vspace) >= grid_cfg.ispace
493                    &&
494                    grid_cfg.vspace -
495                    (x_offset % grid_cfg.vspace) < grid_cfg.ioffset))
496                 {
497                   pix_composite (&dest[(x-sx1) * bytes],
498                                  icolor, bytes, blend, alpha);
499                 }
500             }
501         }
502 
503       if (preview)
504         {
505           memcpy (buffer + (y - sy1) * (sx2 - sx1) * bytes,
506                   dest,
507                   (sx2 - sx1) * bytes);
508         }
509       else
510         {
511           gegl_buffer_set (dest_buffer,
512                            GEGL_RECTANGLE (sx1, y, sx2 - sx1, 1), 0,
513                            format, dest,
514                            GEGL_AUTO_ROWSTRIDE);
515 
516           if (y % 16 == 0)
517             gimp_progress_update ((gdouble) y / (gdouble) (sy2 - sy1));
518         }
519     }
520 
521   g_free (dest);
522 
523   g_object_unref (src_buffer);
524 
525   if (preview)
526     {
527       gimp_preview_draw_buffer (preview, buffer, bytes * (sx2 - sx1));
528       g_free (buffer);
529     }
530   else
531     {
532       gimp_progress_update (1.0);
533 
534       g_object_unref (dest_buffer);
535 
536       gimp_drawable_merge_shadow (drawable_ID, TRUE);
537       gimp_drawable_update (drawable_ID,
538                             sx1, sy1, sx2 - sx1, sy2 - sy1);
539     }
540 }
541 
542 
543 /***************************************************
544  * GUI stuff
545  */
546 
547 
548 static void
update_values(void)549 update_values (void)
550 {
551   GtkWidget *entry;
552 
553   entry = g_object_get_data (G_OBJECT (main_dialog), "width");
554 
555   grid_cfg.hwidth =
556     RINT (gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (entry), 0));
557   grid_cfg.vwidth =
558     RINT (gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (entry), 1));
559   grid_cfg.iwidth =
560     RINT (gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (entry), 2));
561 
562   entry = g_object_get_data (G_OBJECT (main_dialog), "space");
563 
564   grid_cfg.hspace =
565     RINT (gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (entry), 0));
566   grid_cfg.vspace =
567     RINT (gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (entry), 1));
568   grid_cfg.ispace =
569     RINT (gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (entry), 2));
570 
571   entry = g_object_get_data (G_OBJECT (main_dialog), "offset");
572 
573   grid_cfg.hoffset =
574     RINT (gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (entry), 0));
575   grid_cfg.voffset =
576     RINT (gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (entry), 1));
577   grid_cfg.ioffset =
578     RINT (gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (entry), 2));
579 }
580 
581 static void
update_preview(GimpPreview * preview,gpointer drawable_ID)582 update_preview (GimpPreview  *preview,
583                 gpointer      drawable_ID)
584 {
585   update_values ();
586 
587   grid (gimp_item_get_image (GPOINTER_TO_INT (drawable_ID)),
588         GPOINTER_TO_INT (drawable_ID),
589         preview);
590 }
591 
592 static void
entry_callback(GtkWidget * widget,gpointer data)593 entry_callback (GtkWidget *widget,
594                 gpointer   data)
595 {
596   static gdouble x = -1.0;
597   static gdouble y = -1.0;
598   gdouble new_x;
599   gdouble new_y;
600 
601   new_x = gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (widget), 0);
602   new_y = gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (widget), 1);
603 
604   if (gimp_chain_button_get_active (GIMP_CHAIN_BUTTON (data)))
605     {
606       if (new_x != x)
607         {
608           y = new_y = x = new_x;
609           gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (widget), 1, y);
610         }
611       if (new_y != y)
612         {
613           x = new_x = y = new_y;
614           gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (widget), 0, x);
615         }
616     }
617   else
618     {
619       x = new_x;
620       y = new_y;
621     }
622 }
623 
624 static void
color_callback(GtkWidget * widget,gpointer data)625 color_callback (GtkWidget *widget,
626                 gpointer   data)
627 {
628   if (gimp_chain_button_get_active (GIMP_CHAIN_BUTTON (data)))
629     {
630       GimpRGB  color;
631 
632       gimp_color_button_get_color (GIMP_COLOR_BUTTON (widget), &color);
633 
634       if (widget == vcolor_button)
635         gimp_color_button_set_color (GIMP_COLOR_BUTTON (hcolor_button), &color);
636       else if (widget == hcolor_button)
637         gimp_color_button_set_color (GIMP_COLOR_BUTTON (vcolor_button), &color);
638     }
639 }
640 
641 
642 static gint
dialog(gint32 image_ID,gint32 drawable_ID)643 dialog (gint32 image_ID,
644         gint32 drawable_ID)
645 {
646   GimpColorConfig *config;
647   GtkWidget       *dlg;
648   GtkWidget       *main_vbox;
649   GtkWidget       *vbox;
650   GtkSizeGroup    *group;
651   GtkWidget       *label;
652   GtkWidget       *preview;
653   GtkWidget       *button;
654   GtkWidget       *width;
655   GtkWidget       *space;
656   GtkWidget       *offset;
657   GtkWidget       *chain_button;
658   GtkWidget       *table;
659   GimpUnit         unit;
660   gint             d_width;
661   gint             d_height;
662   gdouble          xres;
663   gdouble          yres;
664   gboolean         run;
665 
666   g_return_val_if_fail (main_dialog == NULL, FALSE);
667 
668   gimp_ui_init (PLUG_IN_BINARY, TRUE);
669 
670   d_width  = gimp_drawable_width  (drawable_ID);
671   d_height = gimp_drawable_height (drawable_ID);
672 
673   main_dialog = dlg = gimp_dialog_new (_("Grid"), PLUG_IN_ROLE,
674                                        NULL, 0,
675                                        gimp_standard_help_func, PLUG_IN_PROC,
676 
677                                        _("_Cancel"), GTK_RESPONSE_CANCEL,
678                                        _("_OK"),     GTK_RESPONSE_OK,
679 
680                                        NULL);
681 
682   gtk_dialog_set_alternative_button_order (GTK_DIALOG (dlg),
683                                            GTK_RESPONSE_OK,
684                                            GTK_RESPONSE_CANCEL,
685                                            -1);
686 
687   gimp_window_set_transient (GTK_WINDOW (dlg));
688 
689   /*  Get the image resolution and unit  */
690   gimp_image_get_resolution (image_ID, &xres, &yres);
691   unit = gimp_image_get_unit (image_ID);
692 
693   main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
694   gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12);
695   gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dlg))),
696                       main_vbox, TRUE, TRUE, 0);
697   gtk_widget_show (main_vbox);
698 
699   preview = gimp_drawable_preview_new_from_drawable_id (drawable_ID);
700   gtk_box_pack_start (GTK_BOX (main_vbox), preview, TRUE, TRUE, 0);
701   gtk_widget_show (preview);
702 
703   g_signal_connect (preview, "invalidated",
704                     G_CALLBACK (update_preview),
705                     GINT_TO_POINTER (drawable_ID));
706 
707   vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
708   gtk_box_pack_start (GTK_BOX (main_vbox), vbox, FALSE, FALSE, 0);
709   gtk_widget_show (vbox);
710 
711   /*  The width entries  */
712   width = gimp_size_entry_new (3,                            /*  number_of_fields  */
713                                unit,                         /*  unit              */
714                                "%a",                         /*  unit_format       */
715                                TRUE,                         /*  menu_show_pixels  */
716                                TRUE,                         /*  menu_show_percent */
717                                FALSE,                        /*  show_refval       */
718                                SPIN_BUTTON_WIDTH,            /*  spinbutton_usize  */
719                                GIMP_SIZE_ENTRY_UPDATE_SIZE); /*  update_policy     */
720 
721 
722   gtk_box_pack_start (GTK_BOX (vbox), width, FALSE, FALSE, 0);
723   gtk_widget_show (width);
724 
725   /*  set the unit back to pixels, since most times we will want pixels */
726   gimp_size_entry_set_unit (GIMP_SIZE_ENTRY (width), GIMP_UNIT_PIXEL);
727 
728   /*  set the resolution to the image resolution  */
729   gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (width), 0, xres, TRUE);
730   gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (width), 1, yres, TRUE);
731   gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (width), 2, xres, TRUE);
732 
733   /*  set the size (in pixels) that will be treated as 0% and 100%  */
734   gimp_size_entry_set_size (GIMP_SIZE_ENTRY (width), 0, 0.0, d_height);
735   gimp_size_entry_set_size (GIMP_SIZE_ENTRY (width), 1, 0.0, d_width);
736   gimp_size_entry_set_size (GIMP_SIZE_ENTRY (width), 2, 0.0, d_width);
737 
738   /*  set upper and lower limits (in pixels)  */
739   gimp_size_entry_set_refval_boundaries (GIMP_SIZE_ENTRY (width), 0, 0.0,
740                                          d_height);
741   gimp_size_entry_set_refval_boundaries (GIMP_SIZE_ENTRY (width), 1, 0.0,
742                                          d_width);
743   gimp_size_entry_set_refval_boundaries (GIMP_SIZE_ENTRY (width), 2, 0.0,
744                                          MAX (d_width, d_height));
745   gtk_table_set_row_spacing (GTK_TABLE (width), 0, 6);
746   gtk_table_set_col_spacings (GTK_TABLE (width), 6);
747   gtk_table_set_col_spacing (GTK_TABLE (width), 2, 12);
748 
749   /*  initialize the values  */
750   gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (width), 0, grid_cfg.hwidth);
751   gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (width), 1, grid_cfg.vwidth);
752   gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (width), 2, grid_cfg.iwidth);
753 
754   /*  attach labels  */
755   gimp_size_entry_attach_label (GIMP_SIZE_ENTRY (width), _("Horizontal\nLines"),
756                                 0, 1, 0.0);
757   gimp_size_entry_attach_label (GIMP_SIZE_ENTRY (width), _("Vertical\nLines"),
758                                 0, 2, 0.0);
759   gimp_size_entry_attach_label (GIMP_SIZE_ENTRY (width), _("Intersection"),
760                                 0, 3, 0.0);
761 
762   label = gimp_size_entry_attach_label (GIMP_SIZE_ENTRY (width), _("Width:"),
763                                         1, 0, 0.0);
764 
765   group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
766   gtk_size_group_add_widget (group, label);
767   g_object_unref (group);
768 
769   /*  put a chain_button under the size_entries  */
770   chain_button = gimp_chain_button_new (GIMP_CHAIN_BOTTOM);
771   if (grid_cfg.hwidth == grid_cfg.vwidth)
772     gimp_chain_button_set_active (GIMP_CHAIN_BUTTON (chain_button), TRUE);
773   gtk_table_attach_defaults (GTK_TABLE (width), chain_button, 1, 3, 2, 3);
774   gtk_widget_show (chain_button);
775 
776   /* connect to the 'value-changed' signal because we have to take care
777    * of keeping the entries in sync when the chainbutton is active
778    */
779   g_signal_connect (width, "value-changed",
780                     G_CALLBACK (entry_callback),
781                     chain_button);
782   g_signal_connect_swapped (width, "value-changed",
783                             G_CALLBACK (gimp_preview_invalidate),
784                             preview);
785 
786   /*  The spacing entries  */
787   space = gimp_size_entry_new (3,                            /*  number_of_fields  */
788                                unit,                         /*  unit              */
789                                "%a",                         /*  unit_format       */
790                                TRUE,                         /*  menu_show_pixels  */
791                                TRUE,                         /*  menu_show_percent */
792                                FALSE,                        /*  show_refval       */
793                                SPIN_BUTTON_WIDTH,            /*  spinbutton_usize  */
794                                GIMP_SIZE_ENTRY_UPDATE_SIZE); /*  update_policy     */
795 
796   gtk_box_pack_start (GTK_BOX (vbox), space, FALSE, FALSE, 0);
797   gtk_widget_show (space);
798 
799   gimp_size_entry_set_unit (GIMP_SIZE_ENTRY (space), GIMP_UNIT_PIXEL);
800 
801   /*  set the resolution to the image resolution  */
802   gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (space), 0, xres, TRUE);
803   gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (space), 1, yres, TRUE);
804   gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (space), 2, xres, TRUE);
805 
806   /*  set the size (in pixels) that will be treated as 0% and 100%  */
807   gimp_size_entry_set_size (GIMP_SIZE_ENTRY (space), 0, 0.0, d_height);
808   gimp_size_entry_set_size (GIMP_SIZE_ENTRY (space), 1, 0.0, d_width);
809   gimp_size_entry_set_size (GIMP_SIZE_ENTRY (space), 2, 0.0, d_width);
810 
811   /*  set upper and lower limits (in pixels)  */
812   gimp_size_entry_set_refval_boundaries (GIMP_SIZE_ENTRY (space), 0, 1.0,
813                                          d_height);
814   gimp_size_entry_set_refval_boundaries (GIMP_SIZE_ENTRY (space), 1, 1.0,
815                                          d_width);
816   gimp_size_entry_set_refval_boundaries (GIMP_SIZE_ENTRY (space), 2, 0.0,
817                                          MAX (d_width, d_height));
818   gtk_table_set_col_spacings (GTK_TABLE (space), 6);
819   gtk_table_set_col_spacing (GTK_TABLE (space), 2, 12);
820 
821   /*  initialize the values  */
822   gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (space), 0, grid_cfg.hspace);
823   gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (space), 1, grid_cfg.vspace);
824   gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (space), 2, grid_cfg.ispace);
825 
826   /*  attach labels  */
827   label = gimp_size_entry_attach_label (GIMP_SIZE_ENTRY (space), _("Spacing:"),
828                                         1, 0, 0.0);
829   gtk_size_group_add_widget (group, label);
830 
831   /*  put a chain_button under the spacing_entries  */
832   chain_button = gimp_chain_button_new (GIMP_CHAIN_BOTTOM);
833   if (grid_cfg.hspace == grid_cfg.vspace)
834     gimp_chain_button_set_active (GIMP_CHAIN_BUTTON (chain_button), TRUE);
835   gtk_table_attach_defaults (GTK_TABLE (space), chain_button, 1, 3, 2, 3);
836   gtk_widget_show (chain_button);
837 
838   /* connect to the 'value-changed' and "unit-changed" signals because
839    * we have to take care of keeping the entries in sync when the
840    * chainbutton is active
841    */
842   g_signal_connect (space, "value-changed",
843                     G_CALLBACK (entry_callback),
844                     chain_button);
845   g_signal_connect (space, "unit-changed",
846                     G_CALLBACK (entry_callback),
847                     chain_button);
848   g_signal_connect_swapped (space, "value-changed",
849                             G_CALLBACK (gimp_preview_invalidate),
850                             preview);
851 
852   /*  The offset entries  */
853   offset = gimp_size_entry_new (3,                            /*  number_of_fields  */
854                                 unit,                         /*  unit              */
855                                 "%a",                         /*  unit_format       */
856                                 TRUE,                         /*  menu_show_pixels  */
857                                 TRUE,                         /*  menu_show_percent */
858                                 FALSE,                        /*  show_refval       */
859                                 SPIN_BUTTON_WIDTH,            /*  spinbutton_usize  */
860                                 GIMP_SIZE_ENTRY_UPDATE_SIZE); /*  update_policy     */
861 
862   gtk_box_pack_start (GTK_BOX (vbox), offset, FALSE, FALSE, 0);
863   gtk_widget_show (offset);
864 
865   gimp_size_entry_set_unit (GIMP_SIZE_ENTRY (offset), GIMP_UNIT_PIXEL);
866 
867   /*  set the resolution to the image resolution  */
868   gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (offset), 0, xres, TRUE);
869   gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (offset), 1, yres, TRUE);
870   gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (offset), 2, xres, TRUE);
871 
872   /*  set the size (in pixels) that will be treated as 0% and 100%  */
873   gimp_size_entry_set_size (GIMP_SIZE_ENTRY (offset), 0, 0.0, d_height);
874   gimp_size_entry_set_size (GIMP_SIZE_ENTRY (offset), 1, 0.0, d_width);
875   gimp_size_entry_set_size (GIMP_SIZE_ENTRY (offset), 2, 0.0, d_width);
876 
877   /*  set upper and lower limits (in pixels)  */
878   gimp_size_entry_set_refval_boundaries (GIMP_SIZE_ENTRY (offset), 0, 0.0,
879                                          d_height);
880   gimp_size_entry_set_refval_boundaries (GIMP_SIZE_ENTRY (offset), 1, 0.0,
881                                          d_width);
882   gimp_size_entry_set_refval_boundaries (GIMP_SIZE_ENTRY (offset), 2, 0.0,
883                                          MAX (d_width, d_height));
884   gtk_table_set_col_spacings (GTK_TABLE (offset), 6);
885   gtk_table_set_col_spacing (GTK_TABLE (offset), 2, 12);
886 
887   /*  initialize the values  */
888   gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (offset), 0, grid_cfg.hoffset);
889   gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (offset), 1, grid_cfg.voffset);
890   gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (offset), 2, grid_cfg.ioffset);
891 
892   /*  attach labels  */
893   label = gimp_size_entry_attach_label (GIMP_SIZE_ENTRY (offset), _("Offset:"),
894                                         1, 0, 0.0);
895   gtk_size_group_add_widget (group, label);
896 
897   /*  this is a weird hack: we put a table into the offset table  */
898   table = gtk_table_new (3, 3, FALSE);
899   gtk_table_attach_defaults (GTK_TABLE (offset), table, 1, 4, 2, 3);
900   gtk_table_set_row_spacing (GTK_TABLE (table), 0, 10);
901   gtk_table_set_col_spacing (GTK_TABLE (table), 1, 12);
902 
903   /*  put a chain_button under the offset_entries  */
904   chain_button = gimp_chain_button_new (GIMP_CHAIN_BOTTOM);
905   if (grid_cfg.hoffset == grid_cfg.voffset)
906     gimp_chain_button_set_active (GIMP_CHAIN_BUTTON (chain_button), TRUE);
907   gtk_table_attach_defaults (GTK_TABLE (table), chain_button, 0, 2, 0, 1);
908   gtk_widget_show (chain_button);
909 
910   /* connect to the 'value-changed' and "unit-changed" signals because
911    * we have to take care of keeping the entries in sync when the
912    * chainbutton is active
913    */
914   g_signal_connect (offset, "value-changed",
915                     G_CALLBACK (entry_callback),
916                     chain_button);
917   g_signal_connect (offset, "unit-changed",
918                     G_CALLBACK (entry_callback),
919                     chain_button);
920   g_signal_connect_swapped (offset, "value-changed",
921                             G_CALLBACK (gimp_preview_invalidate),
922                             preview);
923 
924   /*  put a chain_button under the color_buttons  */
925   chain_button = gimp_chain_button_new (GIMP_CHAIN_BOTTOM);
926   if (gimp_rgba_distance (&grid_cfg.hcolor, &grid_cfg.vcolor) < 0.0001)
927     gimp_chain_button_set_active (GIMP_CHAIN_BUTTON (chain_button), TRUE);
928   gtk_table_attach_defaults (GTK_TABLE (table), chain_button, 0, 2, 2, 3);
929   gtk_widget_show (chain_button);
930 
931   /*  attach color selectors  */
932   hcolor_button = gimp_color_button_new (_("Horizontal Color"),
933                                          COLOR_BUTTON_WIDTH, 16,
934                                          &grid_cfg.hcolor,
935                                          GIMP_COLOR_AREA_SMALL_CHECKS);
936   gimp_color_button_set_update (GIMP_COLOR_BUTTON (hcolor_button), TRUE);
937   gtk_table_attach_defaults (GTK_TABLE (table), hcolor_button, 0, 1, 1, 2);
938   gtk_widget_show (hcolor_button);
939 
940   config = gimp_get_color_configuration ();
941   gimp_color_button_set_color_config (GIMP_COLOR_BUTTON (hcolor_button),
942                                       config);
943 
944   g_signal_connect (hcolor_button, "color-changed",
945                     G_CALLBACK (gimp_color_button_get_color),
946                     &grid_cfg.hcolor);
947   g_signal_connect (hcolor_button, "color-changed",
948                     G_CALLBACK (color_callback),
949                     chain_button);
950   g_signal_connect_swapped (hcolor_button, "color-changed",
951                             G_CALLBACK (gimp_preview_invalidate),
952                             preview);
953 
954   vcolor_button = gimp_color_button_new (_("Vertical Color"),
955                                          COLOR_BUTTON_WIDTH, 16,
956                                          &grid_cfg.vcolor,
957                                          GIMP_COLOR_AREA_SMALL_CHECKS);
958   gimp_color_button_set_update (GIMP_COLOR_BUTTON (vcolor_button), TRUE);
959   gtk_table_attach_defaults (GTK_TABLE (table), vcolor_button, 1, 2, 1, 2);
960   gtk_widget_show (vcolor_button);
961 
962   gimp_color_button_set_color_config (GIMP_COLOR_BUTTON (vcolor_button),
963                                       config);
964 
965   g_signal_connect (vcolor_button, "color-changed",
966                     G_CALLBACK (gimp_color_button_get_color),
967                     &grid_cfg.vcolor);
968   g_signal_connect (vcolor_button, "color-changed",
969                     G_CALLBACK (color_callback),
970                     chain_button);
971   g_signal_connect_swapped (vcolor_button, "color-changed",
972                             G_CALLBACK (gimp_preview_invalidate),
973                             preview);
974 
975   button = gimp_color_button_new (_("Intersection Color"),
976                                   COLOR_BUTTON_WIDTH, 16,
977                                   &grid_cfg.icolor,
978                                   GIMP_COLOR_AREA_SMALL_CHECKS);
979   gimp_color_button_set_update (GIMP_COLOR_BUTTON (button), TRUE);
980   gtk_table_attach_defaults (GTK_TABLE (table), button, 2, 3, 1, 2);
981   gtk_widget_show (button);
982 
983   gimp_color_button_set_color_config (GIMP_COLOR_BUTTON (button),
984                                       config);
985   g_object_unref (config);
986 
987   g_signal_connect (button, "color-changed",
988                     G_CALLBACK (gimp_color_button_get_color),
989                     &grid_cfg.icolor);
990   g_signal_connect_swapped (button, "color-changed",
991                             G_CALLBACK (gimp_preview_invalidate),
992                             preview);
993 
994   gtk_widget_show (table);
995 
996   gtk_widget_show (dlg);
997 
998   g_object_set_data (G_OBJECT (dlg), "width",  width);
999   g_object_set_data (G_OBJECT (dlg), "space",  space);
1000   g_object_set_data (G_OBJECT (dlg), "offset", offset);
1001 
1002   run = (gimp_dialog_run (GIMP_DIALOG (dlg)) == GTK_RESPONSE_OK);
1003 
1004   if (run)
1005     update_values ();
1006 
1007   gtk_widget_destroy (dlg);
1008 
1009   return run;
1010 }
1011 
1012