file-xbm.c 40.1 KB
Newer Older
1
/* GIMP - The GNU Image Manipulation Program
2 3
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
4
 * X10 and X11 bitmap (XBM) loading and exporting file filter for GIMP.
5 6 7 8 9 10 11
 * XBM code Copyright (C) 1998 Gordon Matzigkeit
 *
 * The XBM reading and writing code was written from scratch by Gordon
 * Matzigkeit <gord@gnu.org> based on the XReadBitmapFile(3X11) manual
 * page distributed with X11R6 and by staring at valid XBM files.  It
 * does not contain any code written for other XBM file loaders.
 *
12
 * This program is free software: you can redistribute it and/or modify
13
 * it under the terms of the GNU General Public License as published by
14
 * the Free Software Foundation; either version 3 of the License, or
15 16 17 18 19 20 21 22
 * (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
23
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
24
 */
25 26 27 28 29 30 31 32 33 34 35 36 37 38

/* Release 1.0, 1998-02-04, Gordon Matzigkeit <gord@gnu.org>:
 *   - Load and save X10 and X11 bitmaps.
 *   - Allow the user to specify the C identifier prefix.
 *
 * TODO:
 *   - Parsing is very tolerant, and the algorithms are quite hairy, so
 *     load_image should be carefully tested to make sure there are no XBM's
 *     that fail.
 */

/* Set this for debugging. */
/* #define VERBOSE 2 */

39
#include "config.h"
40

41
#include <errno.h>
42
#include <string.h>
43

44
#include <glib/gstdio.h>
45 46 47 48

#include <libgimp/gimp.h>
#include <libgimp/gimpui.h>

49
#include "libgimp/stdplugins-intl.h"
50

51

52 53
#define LOAD_PROC      "file-xbm-load"
#define SAVE_PROC      "file-xbm-save"
54
#define PLUG_IN_BINARY "file-xbm"
55
#define PLUG_IN_ROLE   "gimp-file-xbm"
56 57


58 59
/* Wear your GIMP with pride! */
#define DEFAULT_USE_COMMENT TRUE
60
#define MAX_COMMENT         72
61
#define MAX_MASK_EXT        32
62 63 64

/* C identifier prefix. */
#define DEFAULT_PREFIX "bitmap"
65
#define MAX_PREFIX     64
66

67
/* Whether or not to export as X10 bitmap. */
68 69 70 71
#define DEFAULT_X10_FORMAT FALSE

typedef struct _XBMSaveVals
{
72 73 74 75 76 77 78 79
  gchar    comment[MAX_COMMENT + 1];
  gint     x10_format;
  gint     use_hot;
  gint     x_hot;
  gint     y_hot;
  gchar    prefix[MAX_PREFIX + 1];
  gboolean write_mask;
  gchar    mask_ext[MAX_MASK_EXT + 1];
80 81 82 83
} XBMSaveVals;

static XBMSaveVals xsvals =
{
David Odin's avatar
David Odin committed
84 85
  "###",                /* comment */
  DEFAULT_X10_FORMAT,   /* x10_format */
Michael Natterer's avatar
Michael Natterer committed
86
  FALSE,
David Odin's avatar
David Odin committed
87 88 89
  0,                    /* x_hot */
  0,                    /* y_hot */
  DEFAULT_PREFIX,       /* prefix */
90
  FALSE,                /* write_mask */
91
  "-mask"
92 93 94 95 96
};


/* Declare some local functions.
 */
97 98 99 100 101 102 103 104 105
static void      query                   (void);
static void      run                     (const gchar      *name,
                                          gint              nparams,
                                          const GimpParam  *param,
                                          gint             *nreturn_vals,
                                          GimpParam       **return_vals);

static gint32    load_image              (const gchar      *filename,
                                          GError          **error);
106
static gint      save_image              (GFile            *file,
107 108 109 110 111 112 113
                                          const gchar      *prefix,
                                          const gchar      *comment,
                                          gboolean          save_mask,
                                          gint32            image_ID,
                                          gint32            drawable_ID,
                                          GError          **error);
static gboolean  save_dialog             (gint32            drawable_ID);
114 115 116 117 118 119

static gboolean  print                   (GOutputStream    *output,
                                          GError          **error,
                                          const gchar      *format,
                                          ...) G_GNUC_PRINTF (3, 4);

120 121
#if 0
/* DISABLED - see http://bugzilla.gnome.org/show_bug.cgi?id=82763 */
122 123
static void      comment_entry_callback  (GtkWidget        *widget,
                                          gpointer          data);
124
#endif
125 126 127 128
static void      prefix_entry_callback   (GtkWidget        *widget,
                                          gpointer          data);
static void      mask_ext_entry_callback (GtkWidget        *widget,
                                          gpointer          data);
129

130

131
const GimpPlugInInfo PLUG_IN_INFO =
132
{
133 134 135 136
  NULL,  /* init_proc  */
  NULL,  /* quit_proc  */
  query, /* query_proc */
  run,   /* run_proc   */
137 138
};

139
MAIN ()
140 141 142 143 144

#ifdef VERBOSE
static int verbose = VERBOSE;
#endif

145

146
static void
147
query (void)
148
{
149
  static const GimpParamDef load_args[] =
150
  {
151
    { GIMP_PDB_INT32,  "run-mode",     "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" },
Sven Neumann's avatar
Sven Neumann committed
152
    { GIMP_PDB_STRING, "filename",     "The name of the file to load" },
153
    { GIMP_PDB_STRING, "raw-filename", "The name entered"             }
154
  };
155

156
  static const GimpParamDef load_return_vals[] =
157
  {
Sven Neumann's avatar
Sven Neumann committed
158
    { GIMP_PDB_IMAGE,  "image",        "Output image" }
159 160
  };

161
  static const GimpParamDef save_args[] =
162
  {
163
    { GIMP_PDB_INT32,    "run-mode",       "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" },
Sven Neumann's avatar
Sven Neumann committed
164
    { GIMP_PDB_IMAGE,    "image",          "Input image" },
165 166
    { GIMP_PDB_DRAWABLE, "drawable",       "Drawable to export" },
    { GIMP_PDB_STRING,   "filename",       "The name of the file to export" },
167
    { GIMP_PDB_STRING,   "raw-filename",   "The name entered" },
Sven Neumann's avatar
Sven Neumann committed
168
    { GIMP_PDB_STRING,   "comment",        "Image description (maximum 72 bytes)" },
169
    { GIMP_PDB_INT32,    "x10",            "Export in X10 format" },
170 171
    { GIMP_PDB_INT32,    "x-hot",          "X coordinate of hotspot" },
    { GIMP_PDB_INT32,    "y-hot",          "Y coordinate of hotspot" },
Sven Neumann's avatar
Sven Neumann committed
172
    { GIMP_PDB_STRING,   "prefix",         "Identifier prefix [determined from filename]"},
173 174
    { GIMP_PDB_INT32,    "write-mask",     "(0 = ignore, 1 = save as extra file)" },
    { GIMP_PDB_STRING,   "mask-extension", "Extension of the mask file" }
175 176
  } ;

177
  gimp_install_procedure (LOAD_PROC,
Marc Lehmann's avatar
Marc Lehmann committed
178 179
                          "Load a file in X10 or X11 bitmap (XBM) file format",
                          "Load a file in X10 or X11 bitmap (XBM) file format.  XBM is a lossless format for flat black-and-white (two color indexed) images.",
180 181 182
                          "Gordon Matzigkeit",
                          "Gordon Matzigkeit",
                          "1998",
183
                          N_("X BitMap image"),
David Odin's avatar
David Odin committed
184
                          NULL,
Sven Neumann's avatar
Sven Neumann committed
185
                          GIMP_PLUGIN,
186 187
                          G_N_ELEMENTS (load_args),
                          G_N_ELEMENTS (load_return_vals),
188 189
                          load_args, load_return_vals);

190 191
  gimp_register_file_handler_mime (LOAD_PROC, "image/x-xbitmap");
  gimp_register_load_handler (LOAD_PROC,
David Odin's avatar
David Odin committed
192 193
                              "xbm,icon,bitmap",
                              "");
194

195
  gimp_install_procedure (SAVE_PROC,
196 197
                          "Export a file in X10 or X11 bitmap (XBM) file format",
                          "Export a file in X10 or X11 bitmap (XBM) file format.  XBM is a lossless format for flat black-and-white (two color indexed) images.",
David Odin's avatar
David Odin committed
198
                          "Gordon Matzigkeit",
199 200
                          "Gordon Matzigkeit",
                          "1998",
201
                          N_("X BitMap image"),
David Odin's avatar
David Odin committed
202
                          "INDEXED",
Sven Neumann's avatar
Sven Neumann committed
203
                          GIMP_PLUGIN,
204
                          G_N_ELEMENTS (save_args), 0,
205 206
                          save_args, NULL);

207
  gimp_register_file_handler_mime (SAVE_PROC, "image/x-xbitmap");
208
  gimp_register_file_handler_uri (SAVE_PROC);
209
  gimp_register_save_handler (SAVE_PROC, "xbm,icon,bitmap", "");
210 211
}

212
static gchar *
213
init_prefix (const gchar *filename)
214
{
215 216
  gchar *p, *prefix;
  gint len;
217

218
  prefix = g_path_get_basename (filename);
219 220

  memset (xsvals.prefix, 0, sizeof (xsvals.prefix));
221 222 223

  if (prefix)
    {
224
      /* Strip any extension. */
225 226 227 228 229 230 231 232 233
      p = strrchr (prefix, '.');
      if (p && p != prefix)
        len = MIN (MAX_PREFIX, p - prefix);
      else
        len = MAX_PREFIX;

      strncpy (xsvals.prefix, prefix, len);
      g_free (prefix);
    }
234 235

  return xsvals.prefix;
236 237 238
}

static void
239 240 241 242 243
run (const gchar      *name,
     gint              nparams,
     const GimpParam  *param,
     gint             *nreturn_vals,
     GimpParam       **return_vals)
244
{
245
  static GimpParam   values[2];
246
  GimpRunMode        run_mode;
247
  GimpPDBStatusType  status        = GIMP_PDB_SUCCESS;
248 249
  gint32             image_ID;
  gint32             drawable_ID;
250
  GimpParasite      *parasite      = NULL;
251
  gchar             *mask_basename = NULL;
252 253
  GError            *error         = NULL;
  GimpExportReturn   export        = GIMP_EXPORT_CANCEL;
254

255
  INIT_I18N ();
256
  gegl_init (NULL, NULL);
257

258
  strncpy (xsvals.comment, "Created with GIMP", MAX_COMMENT);
259

260 261 262
  run_mode = param[0].data.d_int32;

  *nreturn_vals = 1;
263
  *return_vals  = values;
264

Sven Neumann's avatar
Sven Neumann committed
265 266
  values[0].type          = GIMP_PDB_STATUS;
  values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR;
267 268 269 270 271 272

#ifdef VERBOSE
  if (verbose)
    printf ("XBM: RUN %s\n", name);
#endif

273
  if (strcmp (name, LOAD_PROC) == 0)
274
    {
275
      image_ID = load_image (param[1].data.d_string, &error);
276 277 278 279

      if (image_ID != -1)
        {
          *nreturn_vals = 2;
Sven Neumann's avatar
Sven Neumann committed
280
          values[1].type         = GIMP_PDB_IMAGE;
281 282 283 284
          values[1].data.d_image = image_ID;
        }
      else
        {
Sven Neumann's avatar
Sven Neumann committed
285
          status = GIMP_PDB_EXECUTION_ERROR;
286 287
        }
    }
288
  else if (strcmp (name, SAVE_PROC) == 0)
289
    {
290
      image_ID    = param[1].data.d_int32;
Sven Neumann's avatar
Sven Neumann committed
291
      drawable_ID = param[2].data.d_int32;
292

Sven Neumann's avatar
Sven Neumann committed
293
      switch (run_mode)
David Odin's avatar
David Odin committed
294 295 296 297
        {
        case GIMP_RUN_INTERACTIVE:
        case GIMP_RUN_WITH_LAST_VALS:
          gimp_ui_init (PLUG_IN_BINARY, FALSE);
298 299

          export = gimp_export_image (&image_ID, &drawable_ID, "XBM",
David Odin's avatar
David Odin committed
300 301 302 303 304 305 306
                                      GIMP_EXPORT_CAN_HANDLE_BITMAP |
                                      GIMP_EXPORT_CAN_HANDLE_ALPHA);

          if (export == GIMP_EXPORT_CANCEL)
            {
              values[0].data.d_status = GIMP_PDB_CANCEL;
              return;
307
            }
David Odin's avatar
David Odin committed
308
          break;
309

David Odin's avatar
David Odin committed
310 311 312
        default:
          break;
        }
313 314

      switch (run_mode)
David Odin's avatar
David Odin committed
315 316 317 318 319 320 321
        {
        case GIMP_RUN_INTERACTIVE:
        case GIMP_RUN_WITH_LAST_VALS:
          /*  Possibly retrieve data  */
          gimp_get_data (SAVE_PROC, &xsvals);

          /* Always override the prefix with the filename. */
322
          mask_basename = g_strdup (init_prefix (param[3].data.d_string));
David Odin's avatar
David Odin committed
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354
          break;

        case GIMP_RUN_NONINTERACTIVE:
          /*  Make sure all the required arguments are there!  */
          if (nparams < 5)
            {
              status = GIMP_PDB_CALLING_ERROR;
            }
          else
            {
              gint i = 5;

              if (nparams > i)
                {
                  memset (xsvals.comment, 0, sizeof (xsvals.comment));
                  strncpy (xsvals.comment, param[i].data.d_string,
                           MAX_COMMENT);
                }

              i ++;
              if (nparams > i)
                xsvals.x10_format = (param[i].data.d_int32) ? TRUE : FALSE;

              i += 2;
              if (nparams > i)
                {
                  /* They've asked for a hotspot. */
                  xsvals.use_hot = TRUE;
                  xsvals.x_hot = param[i - 1].data.d_int32;
                  xsvals.y_hot = param[i].data.d_int32;
                }

355
              mask_basename = g_strdup (init_prefix (param[3].data.d_string));
David Odin's avatar
David Odin committed
356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383

              i ++;
              if (nparams > i)
                {
                  memset (xsvals.prefix, 0, sizeof (xsvals.prefix));
                  strncpy (xsvals.prefix, param[i].data.d_string,
                           MAX_PREFIX);
                }

              i += 2;
              if (nparams > i)
                {
                  xsvals.write_mask = param[i - 1].data.d_int32;
                  memset (xsvals.mask_ext, 0, sizeof (xsvals.mask_ext));
                  strncpy (xsvals.mask_ext, param[i].data.d_string,
                           MAX_MASK_EXT);
                }

              i ++;
              /* Too many arguments. */
              if (nparams > i)
                status = GIMP_PDB_CALLING_ERROR;
            }
          break;

        default:
          break;
        }
Michael Natterer's avatar
Michael Natterer committed
384

Seth Burgess's avatar
Seth Burgess committed
385
      if (run_mode == GIMP_RUN_INTERACTIVE)
David Odin's avatar
David Odin committed
386 387
        {
          /* Get the parasites */
388
          parasite = gimp_image_get_parasite (image_ID, "gimp-comment");
Michael Natterer's avatar
Michael Natterer committed
389

390
          if (parasite)
David Odin's avatar
David Odin committed
391 392
            {
              gint size = gimp_parasite_data_size (parasite);
393

David Odin's avatar
David Odin committed
394 395 396
              strncpy (xsvals.comment,
                       gimp_parasite_data (parasite), MIN (size, MAX_COMMENT));
              xsvals.comment[MIN (size, MAX_COMMENT) + 1] = 0;
Michael Natterer's avatar
Michael Natterer committed
397

David Odin's avatar
David Odin committed
398 399
              gimp_parasite_free (parasite);
            }
400

401
          parasite = gimp_image_get_parasite (image_ID, "hot-spot");
Michael Natterer's avatar
Michael Natterer committed
402

403
          if (parasite)
David Odin's avatar
David Odin committed
404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419
            {
              gint x, y;

              if (sscanf (gimp_parasite_data (parasite), "%i %i", &x, &y) == 2)
               {
                 xsvals.use_hot = TRUE;
                 xsvals.x_hot = x;
                 xsvals.y_hot = y;
               }
             gimp_parasite_free (parasite);
           }

          /*  Acquire information with a dialog  */
          if (! save_dialog (drawable_ID))
            status = GIMP_PDB_CANCEL;
        }
420

Sven Neumann's avatar
Sven Neumann committed
421
      if (status == GIMP_PDB_SUCCESS)
David Odin's avatar
David Odin committed
422
        {
423 424 425
          GFile *file = g_file_new_for_uri (param[3].data.d_string);
          GFile *mask_file;
          GFile *dir;
David Odin's avatar
David Odin committed
426
          gchar *mask_prefix;
427
          gchar *temp;
David Odin's avatar
David Odin committed
428

429 430
          dir = g_file_get_parent (file);
          temp = g_strdup_printf ("%s%s.xbm", mask_basename, xsvals.mask_ext);
David Odin's avatar
David Odin committed
431

432
          mask_file = g_file_get_child (dir, temp);
David Odin's avatar
David Odin committed
433 434

          g_free (temp);
435
          g_object_unref (dir);
David Odin's avatar
David Odin committed
436 437 438

          /* Change any non-alphanumeric prefix characters to underscores. */
          for (temp = xsvals.prefix; *temp; temp++)
439 440
            if (! g_ascii_isalnum (*temp))
              *temp = '_';
441

David Odin's avatar
David Odin committed
442
          mask_prefix = g_strdup_printf ("%s%s",
443
                                         xsvals.prefix, xsvals.mask_ext);
444

David Odin's avatar
David Odin committed
445
          for (temp = mask_prefix; *temp; temp++)
446 447 448
            if (! g_ascii_isalnum (*temp))
              *temp = '_';

449
          if (save_image (file,
David Odin's avatar
David Odin committed
450 451 452 453
                          xsvals.prefix,
                          xsvals.comment,
                          FALSE,
                          image_ID, drawable_ID,
454
                          &error)
455 456 457 458 459 460 461 462

              && (! xsvals.write_mask ||
                  save_image (mask_file,
                              mask_prefix,
                              xsvals.comment,
                              TRUE,
                              image_ID, drawable_ID,
                              &error)))
David Odin's avatar
David Odin committed
463 464 465 466 467 468 469 470 471 472
            {
              /*  Store xsvals data  */
              gimp_set_data (SAVE_PROC, &xsvals, sizeof (xsvals));
            }
          else
            {
              status = GIMP_PDB_EXECUTION_ERROR;
            }

          g_free (mask_prefix);
473 474 475 476
          g_free (mask_basename);

          g_object_unref (file);
          g_object_unref (mask_file);
David Odin's avatar
David Odin committed
477
        }
Sven Neumann's avatar
Sven Neumann committed
478

479
      if (export == GIMP_EXPORT_EXPORT)
David Odin's avatar
David Odin committed
480
        gimp_image_delete (image_ID);
481
    }
482 483
  else
    {
Sven Neumann's avatar
Sven Neumann committed
484
      status = GIMP_PDB_CALLING_ERROR;
485
    }
486

487 488 489 490 491 492 493
  if (status != GIMP_PDB_SUCCESS && error)
    {
      *nreturn_vals = 2;
      values[1].type          = GIMP_PDB_STRING;
      values[1].data.d_string = error->message;
    }

494 495
  values[0].data.d_status = status;
}
496 497 498 499


/* Return the value of a digit. */
static gint
500
getval (gint c,
David Odin's avatar
David Odin committed
501
        gint base)
502
{
503 504
  const gchar *digits = "0123456789abcdefABCDEF";
  gint         val;
505 506 507 508 509 510 511 512 513 514 515 516 517

  /* Include uppercase hex digits. */
  if (base == 16)
    base = 22;

  /* Find a match. */
  for (val = 0; val < base; val ++)
    if (c == digits[val])
      return (val < 16) ? val : (val - 6);
  return -1;
}


Michael Natterer's avatar
Michael Natterer committed
518 519 520 521 522 523 524 525 526 527 528 529
/* Get a comment */
static gchar *
fgetcomment (FILE *fp)
{
  GString *str = NULL;
  gint comment, c;

  comment = 0;
  do
    {
      c = fgetc (fp);
      if (comment)
David Odin's avatar
David Odin committed
530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553
        {
          if (c == '*')
            {
              /* In a comment, with potential to leave. */
              comment = 1;
            }
          else if (comment == 1 && c == '/')
            {
              gchar *retval;

              /* Leaving a comment. */
              comment = 0;

              retval = g_strstrip (g_strdup (str->str));
              g_string_free (str, TRUE);
              return retval;
            }
          else
            {
              /* In a comment, with no potential to leave. */
              comment = 2;
              g_string_append_c (str, c);
            }
        }
Michael Natterer's avatar
Michael Natterer committed
554
      else
David Odin's avatar
David Odin committed
555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581
        {
          /* Not in a comment. */
          if (c == '/')
            {
              /* Potential to enter a comment. */
              c = fgetc (fp);
              if (c == '*')
                {
                  /* Entered a comment, with no potential to leave. */
                  comment = 2;
                  str = g_string_new (NULL);
                }
              else
                {
                  /* put everything back and return */
                  ungetc (c, fp);
                  c = '/';
                  ungetc (c, fp);
                  return NULL;
                }
            }
          else if (c != EOF && g_ascii_isspace (c))
            {
              /* Skip leading whitespace */
              continue;
            }
        }
Michael Natterer's avatar
Michael Natterer committed
582 583 584 585 586 587 588 589 590 591
    }
  while (comment && c != EOF);

  if (str)
    g_string_free (str, TRUE);

  return NULL;
}


592 593 594 595
/* Same as fgetc, but skip C-style comments and insert whitespace. */
static gint
cpp_fgetc (FILE *fp)
{
596
  gint comment, c;
597 598 599 600 601 602 603

  /* FIXME: insert whitespace as advertised. */
  comment = 0;
  do
    {
      c = fgetc (fp);
      if (comment)
David Odin's avatar
David Odin committed
604 605 606 607 608 609 610 611 612 613 614
        {
          if (c == '*')
            /* In a comment, with potential to leave. */
            comment = 1;
          else if (comment == 1 && c == '/')
            /* Leaving a comment. */
            comment = 0;
          else
            /* In a comment, with no potential to leave. */
            comment = 2;
        }
615
      else
David Odin's avatar
David Odin committed
616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632
        {
          /* Not in a comment. */
          if (c == '/')
            {
              /* Potential to enter a comment. */
              c = fgetc (fp);
              if (c == '*')
                /* Entered a comment, with no potential to leave. */
                comment = 2;
              else
                {
                  /* Just a slash in the open. */
                  ungetc (c, fp);
                  c = '/';
                }
            }
        }
633 634 635 636 637 638 639 640
    }
  while (comment && c != EOF);
  return c;
}


/* Match a string with a file. */
static gint
641 642
match (FILE        *fp,
       const gchar *s)
643
{
644
  gint c;
645 646 647 648 649

  do
    {
      c = fgetc (fp);
      if (c == *s)
David Odin's avatar
David Odin committed
650
        s ++;
651
      else
David Odin's avatar
David Odin committed
652
        break;
653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672
    }
  while (c != EOF && *s);

  if (!*s)
    return TRUE;

  if (c != EOF)
    ungetc (c, fp);
  return FALSE;
}


/* Read the next integer from the file, skipping all non-integers. */
static gint
get_int (FILE *fp)
{
  int digval, base, val, c;

  do
    c = cpp_fgetc (fp);
673
  while (c != EOF && ! g_ascii_isdigit (c));
674 675 676 677 678 679 680 681 682

  if (c == EOF)
    return 0;

  /* Check for the base. */
  if (c == '0')
    {
      c = fgetc (fp);
      if (c == 'x' || c == 'X')
David Odin's avatar
David Odin committed
683 684 685 686
        {
          c = fgetc (fp);
          base = 16;
        }
687
      else if (g_ascii_isdigit (c))
David Odin's avatar
David Odin committed
688
        base = 8;
689
      else
David Odin's avatar
David Odin committed
690 691 692 693
        {
          ungetc (c, fp);
          return 0;
        }
694 695 696 697 698 699 700 701 702
    }
  else
    base = 10;

  val = 0;
  for (;;)
    {
      digval = getval (c, base);
      if (digval == -1)
David Odin's avatar
David Odin committed
703 704 705 706
        {
          ungetc (c, fp);
          break;
        }
707 708 709 710 711 712 713 714 715
      val *= base;
      val += digval;
      c = fgetc (fp);
    }

  return val;
}


716
static gint32
717 718
load_image (const gchar  *filename,
            GError      **error)
719
{
720
  FILE         *fp;
721
  GeglBuffer   *buffer;
722 723 724 725 726 727 728 729 730 731 732
  gint32        image_ID;
  gint32        layer_ID;
  guchar       *data;
  gint          intbits;
  gint          width  = 0;
  gint          height = 0;
  gint          x_hot  = 0;
  gint          y_hot  = 0;
  gint          c, i, j, k;
  gint          tileheight, rowoffset;
  gchar        *comment;
733

Sven Neumann's avatar
Sven Neumann committed
734
  const guchar cmap[] =
735
  {
David Odin's avatar
David Odin committed
736 737
    0x00, 0x00, 0x00,           /* black */
    0xff, 0xff, 0xff            /* white */
738 739
  };

740 741 742
  gimp_progress_init_printf (_("Opening '%s'"),
                             gimp_filename_to_utf8 (filename));

743
  fp = g_fopen (filename, "rb");
744
  if (! fp)
745
    {
746 747 748
      g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
                   _("Could not open '%s' for reading: %s"),
                   gimp_filename_to_utf8 (filename), g_strerror (errno));
749 750 751
      return -1;
    }

Michael Natterer's avatar
Michael Natterer committed
752 753
  comment = fgetcomment (fp);

754 755 756 757 758
  /* Loosely parse the header */
  intbits = height = width = 0;
  c = ' ';
  do
    {
759
      if (g_ascii_isspace (c))
David Odin's avatar
David Odin committed
760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779
        {
          if (match (fp, "char"))
            {
              c = fgetc (fp);
              if (g_ascii_isspace (c))
                {
                  intbits = 8;
                  continue;
                }
            }
          else if (match (fp, "short"))
            {
              c = fgetc (fp);
              if (g_ascii_isspace (c))
                {
                  intbits = 16;
                  continue;
                }
            }
        }
780 781

      if (c == '_')
David Odin's avatar
David Odin committed
782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819
        {
          if (match (fp, "width"))
            {
              c = fgetc (fp);
              if (g_ascii_isspace (c))
                {
                  width = get_int (fp);
                  continue;
                }
            }
          else if (match (fp, "height"))
            {
              c = fgetc (fp);
              if (g_ascii_isspace (c))
                {
                  height = get_int (fp);
                  continue;
                }
            }
          else if (match (fp, "x_hot"))
            {
              c = fgetc (fp);
              if (g_ascii_isspace (c))
                {
                  x_hot = get_int (fp);
                  continue;
                }
            }
          else if (match (fp, "y_hot"))
            {
              c = fgetc (fp);
              if (g_ascii_isspace (c))
                {
                  y_hot = get_int (fp);
                  continue;
                }
            }
        }
820 821 822 823 824 825 826

      c = cpp_fgetc (fp);
    }
  while (c != '{' && c != EOF);

  if (c == EOF)
    {
827
      g_message (_("'%s':\nCould not read header (ftell == %ld)"),
828
                 gimp_filename_to_utf8 (filename), ftell (fp));
829
      fclose (fp);
830 831 832
      return -1;
    }

833
  if (width <= 0)
834
    {
835 836
      g_message (_("'%s':\nNo image width specified"),
                 gimp_filename_to_utf8 (filename));
837
      fclose (fp);
838 839 840
      return -1;
    }

841 842 843 844
  if (width > GIMP_MAX_IMAGE_SIZE)
    {
      g_message (_("'%s':\nImage width is larger than GIMP can handle"),
                 gimp_filename_to_utf8 (filename));
845
      fclose (fp);
846 847 848 849
      return -1;
    }

  if (height <= 0)
850
    {
851 852
      g_message (_("'%s':\nNo image height specified"),
                 gimp_filename_to_utf8 (filename));
853
      fclose (fp);
854 855 856
      return -1;
    }

857 858 859 860
  if (height > GIMP_MAX_IMAGE_SIZE)
    {
      g_message (_("'%s':\nImage height is larger than GIMP can handle"),
                 gimp_filename_to_utf8 (filename));
861
      fclose (fp);
862 863 864
      return -1;
    }

865 866
  if (intbits == 0)
    {
867 868
      g_message (_("'%s':\nNo image data type specified"),
                 gimp_filename_to_utf8 (filename));
869
      fclose (fp);
870 871 872
      return -1;
    }

Sven Neumann's avatar
Sven Neumann committed
873
  image_ID = gimp_image_new (width, height, GIMP_INDEXED);
874 875
  gimp_image_set_filename (image_ID, filename);

Michael Natterer's avatar
Michael Natterer committed
876 877
  if (comment)
    {
878
      GimpParasite *parasite;
Michael Natterer's avatar
Michael Natterer committed
879

880
      parasite = gimp_parasite_new ("gimp-comment",
David Odin's avatar
David Odin committed
881 882
                                    GIMP_PARASITE_PERSISTENT,
                                    strlen (comment) + 1, (gpointer) comment);
883
      gimp_image_attach_parasite (image_ID, parasite);
884
      gimp_parasite_free (parasite);
Michael Natterer's avatar
Michael Natterer committed
885 886 887 888 889 890 891 892 893

      g_free (comment);
    }

  x_hot = CLAMP (x_hot, 0, width);
  y_hot = CLAMP (y_hot, 0, height);

  if (x_hot > 0 || y_hot > 0)
    {
894 895
      GimpParasite *parasite;
      gchar        *str;
Michael Natterer's avatar
Michael Natterer committed
896 897

      str = g_strdup_printf ("%d %d", x_hot, y_hot);
898
      parasite = gimp_parasite_new ("hot-spot",
David Odin's avatar
David Odin committed
899 900
                                    GIMP_PARASITE_PERSISTENT,
                                    strlen (str) + 1, (gpointer) str);
Michael Natterer's avatar
Michael Natterer committed
901
      g_free (str);
902
      gimp_image_attach_parasite (image_ID, parasite);
903
      gimp_parasite_free (parasite);
Michael Natterer's avatar
Michael Natterer committed
904 905
    }

906
  /* Set a black-and-white colormap. */
907
  gimp_image_set_colormap (image_ID, cmap, 2);
908 909

  layer_ID = gimp_layer_new (image_ID,
David Odin's avatar
David Odin committed
910 911 912 913
                             _("Background"),
                             width, height,
                             GIMP_INDEXED_IMAGE,
                             100,
914
                             gimp_image_get_default_new_layer_mode (image_ID));
915
  gimp_image_insert_layer (image_ID, layer_ID, -1, 0);
916

917
  buffer = gimp_drawable_get_buffer (layer_ID);
918 919 920 921 922 923 924 925 926 927 928

  /* Allocate the data. */
  tileheight = gimp_tile_height ();
  data = (guchar *) g_malloc (width * tileheight);

  for (i = 0; i < height; i += tileheight)
    {
      tileheight = MIN (tileheight, height - i);

#ifdef VERBOSE
      if (verbose > 1)
David Odin's avatar
David Odin committed
929 930
        printf ("XBM: reading %dx(%d+%d) pixel region\n", width, i,
                tileheight);
931 932 933 934
#endif

      /* Parse the data from the file */
      for (j = 0; j < tileheight; j ++)
David Odin's avatar
David Odin committed
935 936 937 938 939 940 941 942 943 944 945
        {
          /* Read each row. */
          rowoffset = j * width;
          for (k = 0; k < width; k ++)
            {
              /* Expand each integer into INTBITS pixels. */
              if (k % intbits == 0)
                {
                  c = get_int (fp);

                  /* Flip all the bits so that 1's become black and
946
                     0's become white. */
David Odin's avatar
David Odin committed
947 948
                  c ^= 0xffff;
                }
949

David Odin's avatar
David Odin committed
950 951 952 953
              data[rowoffset + k] = c & 1;
              c >>= 1;
            }
        }
954 955

      /* Put the data into the image. */
956 957 958
      gegl_buffer_set (buffer, GEGL_RECTANGLE (0, i, width, tileheight), 0,
                       NULL, data, GEGL_AUTO_ROWSTRIDE);

959 960 961 962
      gimp_progress_update ((double) (i + tileheight) / (double) height);
    }

  g_free (data);
963
  g_object_unref (buffer);
964
  fclose (fp);
965

966 967
  gimp_progress_update (1.0);

968 969 970
  return image_ID;
}

971
static gboolean
972
save_image (GFile        *file,
David Odin's avatar
David Odin committed
973 974 975 976 977
            const gchar  *prefix,
            const gchar  *comment,
            gboolean      save_mask,
            gint32        image_ID,
            gint32        drawable_ID,
978
            GError      **error)
979
{
980 981
  GOutputStream *output;
  GeglBuffer    *buffer;
982
  GCancellable  *cancellable;
983 984 985 986 987 988 989 990
  gint           width, height, colors, dark;
  gint           intbits, lineints, need_comma, nints, rowoffset, tileheight;
  gint           c, i, j, k, thisbit;
  gboolean       has_alpha;
  gint           bpp;
  guchar        *data = NULL;
  guchar        *cmap;
  const gchar   *intfmt;
991

992 993 994 995 996 997 998
#if 0
  if (save_mask)
    g_printerr ("%s: save_mask '%s'\n", G_STRFUNC, prefix);
  else
    g_printerr ("%s: save_image '%s'\n", G_STRFUNC, prefix);
#endif

999
  cmap = gimp_image_get_colormap (image_ID, &colors);
1000

1001
  if (! gimp_drawable_is_indexed (drawable_ID) || colors > 2)
1002 1003
    {
      /* The image is not black-and-white. */
1004
      g_message (_("The image which you are trying to export as "
David Odin's avatar
David Odin committed
1005 1006 1007
                   "an XBM contains more than two colors.\n\n"
                   "Please convert it to a black and white "
                   "(1-bit) indexed image and try again."));
1008
      g_free (cmap);
1009 1010 1011 1012 1013
      return FALSE;
    }

  has_alpha = gimp_drawable_has_alpha (drawable_ID);

1014
  if (! has_alpha && save_mask)
1015 1016
    {
      g_message (_("You cannot save a cursor mask for an image\n"
David Odin's avatar
David Odin committed
1017
                   "which has no alpha channel."));
1018 1019 1020
      return FALSE;
    }

1021 1022 1023 1024
  buffer = gimp_drawable_get_buffer (drawable_ID);
  width  = gegl_buffer_get_width  (buffer);
  height = gegl_buffer_get_height (buffer);
  bpp    = gimp_drawable_bpp (drawable_ID);
1025

1026 1027 1028 1029
  /* Figure out which color is black, and which is white. */
  dark = 0;
  if (colors > 1)
    {
1030
      gint first, second;
1031 1032

      /* Maybe the second color is darker than the first. */
1033
      first  = (cmap[0] * cmap[0]) + (cmap[1] * cmap[1]) + (cmap[2] * cmap[2]);
1034 1035 1036
      second = (cmap[3] * cmap[3]) + (cmap[4] * cmap[4]) + (cmap[5] * cmap[5]);

      if (second < first)
David Odin's avatar
David Odin committed
1037
        dark = 1;
1038 1039
    }

1040
  gimp_progress_init_printf (_("Exporting '%s'"),
1041
                             gimp_file_get_utf8_name (file));
1042

1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055
  output = G_OUTPUT_STREAM (g_file_replace (file,
                                            NULL, FALSE, G_FILE_CREATE_NONE,
                                            NULL, error));
  if (output)
    {
      GOutputStream *buffered;

      buffered = g_buffered_output_stream_new (output);
      g_object_unref (output);

      output = buffered;
    }
  else
1056 1057 1058 1059 1060
    {
      return FALSE;
    }

  /* Maybe write the image comment. */
1061 1062 1063
#if 0
  /* DISABLED - see http://bugzilla.gnome.org/show_bug.cgi?id=82763 */
  /* a future version should write the comment at the end of the file */
1064
  if (*comment)
1065 1066 1067 1068
    {
      if (! print (output, error, "/* %s */\n", comment))
        goto fail;
    }
1069
#endif
1070 1071

  /* Write out the image height and width. */
1072 1073 1074
  if (! print (output, error, "#define %s_width %d\n",  prefix, width) ||
      ! print (output, error, "#define %s_height %d\n", prefix, height))
    goto fail;
1075 1076

  /* Write out the hotspot, if any. */
Michael Natterer's avatar
Michael Natterer committed
1077
  if (xsvals.use_hot)
1078
    {
1079 1080 1081 1082 1083
      if (! print (output, error,
                   "#define %s_x_hot %d\n", prefix, xsvals.x_hot) ||
          ! print (output, error,
                   "#define %s_y_hot %d\n", prefix, xsvals.y_hot))
        goto fail;
1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101
    }

  /* Now write the actual data. */
  if (xsvals.x10_format)
    {
      /* We can fit 9 hex shorts on a single line. */
      lineints = 9;
      intbits = 16;
      intfmt = " 0x%04x";
    }
  else
    {
      /* We can fit 12 hex chars on a single line. */
      lineints = 12;
      intbits = 8;
      intfmt = " 0x%02x";
    }

1102 1103 1104 1105
  if (! print (output, error,
               "static %s %s_bits[] = {\n  ",
               xsvals.x10_format ? "unsigned short" : "unsigned char", prefix))
    goto fail;
1106 1107 1108

  /* Allocate a new set of pixels. */
  tileheight = gimp_tile_height ();
1109
  data = (guchar *) g_malloc (width * tileheight * bpp);
1110 1111 1112 1113 1114 1115 1116 1117

  /* Write out the integers. */
  need_comma = 0;
  nints = 0;
  for (i = 0; i < height; i += tileheight)
    {
      /* Get a horizontal slice of the image. */
      tileheight = MIN (tileheight, height - i);
1118 1119 1120 1121

      gegl_buffer_get (buffer, GEGL_RECTANGLE (0, i, width, tileheight), 1.0,
                       NULL, data,
                       GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
1122 1123 1124

#ifdef VERBOSE
      if (verbose > 1)
David Odin's avatar
David Odin committed
1125 1126
        printf ("XBM: writing %dx(%d+%d) pixel region\n",
                width, i, tileheight);
1127 1128 1129
#endif

      for (j = 0; j < tileheight; j ++)
David Odin's avatar
David Odin committed
1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141
        {
          /* Write out a row at a time. */
          rowoffset = j * width * bpp;
          c = 0;
          thisbit = 0;

          for (k = 0; k < width * bpp; k += bpp)
            {
              if (k != 0 && thisbit == intbits)
                {
                  /* Output a completed integer. */
                  if (need_comma)
1142 1143 1144 1145 1146
                    {
                      if (! print (output, error, ","))
                        goto fail;
                    }

David Odin's avatar
David Odin committed
1147 1148 1149 1150 1151 1152
                  need_comma = 1;

                  /* Maybe start a new line. */
                  if (nints ++ >= lineints)
                    {
                      nints = 1;
1153 1154 1155

                      if (! print (output, error, "\n  "))
                        goto fail;
David Odin's avatar
David Odin committed
1156
                    }
1157 1158 1159

                  if (! print (output, error, intfmt, c))
                    goto fail;
David Odin's avatar
David Odin committed
1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183

                  /* Start a new integer. */
                  c = 0;
                  thisbit = 0;
                }

              /* Pack INTBITS pixels into an integer. */
              if (save_mask)
                {
                  c |= ((data[rowoffset + k + 1] < 128) ? 0 : 1) << (thisbit ++);
                }
              else
                {
                  if (has_alpha && (data[rowoffset + k + 1] < 128))
                    c |= 0 << (thisbit ++);
                  else
                    c |= ((data[rowoffset + k] == dark) ? 1 : 0) << (thisbit ++);
                }
            }

          if (thisbit != 0)
            {
              /* Write out the last oddball int. */
              if (need_comma)
1184 1185 1186 1187 1188
                {
                  if (! print (output, error, ","))
                    goto fail;
                }

David Odin's avatar
David Odin committed
1189 1190 1191 1192 1193 1194
              need_comma = 1;

              /* Maybe start a new line. */
              if (nints ++ == lineints)
                {
                  nints = 1;
1195 1196 1197

                  if (! print (output, error, "\n  "))
                    goto fail;
David Odin's avatar
David Odin committed
1198
                }
1199 1200 1201

              if (! print (output, error, intfmt, c))
                goto fail;
David Odin's avatar
David Odin committed
1202 1203
            }
        }
1204 1205 1206 1207 1208

      gimp_progress_update ((double) (i + tileheight) / (double) height);
    }

  /* Write the trailer. */
1209 1210
  if (! print (output, error, " };\n"))
    goto fail;
1211

1212 1213
  if (! g_output_stream_close (output, NULL, error))
    goto fail;
1214

1215
  g_free (data);
1216 1217
  g_object_unref (buffer);
  g_object_unref (output);
1218

1219 1220
  gimp_progress_update (1.0);

1221
  return TRUE;
1222 1223 1224

 fail:

1225 1226 1227 1228 1229
  cancellable = g_cancellable_new ();
  g_cancellable_cancel (cancellable);
  g_output_stream_close (output, cancellable, NULL);
  g_object_unref (cancellable);

1230 1231 1232 1233 1234
  g_free (data);
  g_object_unref (buffer);
  g_object_unref (output);

  return FALSE;
1235 1236
}

1237
static gboolean
1238
save_dialog (gint32 drawable_ID)
Sven Neumann's avatar
Sven Neumann committed
1239
{
1240 1241 1242 1243
  GtkWidget     *dialog;
  GtkWidget     *frame;
  GtkWidget     *vbox;
  GtkWidget     *toggle;
Simon Budig's avatar
Simon Budig committed
1244
  GtkWidget     *grid;
1245 1246 1247 1248
  GtkWidget     *entry;
  GtkWidget     *spinbutton;
  GtkAdjustment *adj;
  gboolean       run;
1249

1250
  dialog = gimp_export_dialog_new (_("XBM"), PLUG_IN_BINARY, SAVE_PROC);
1251

1252
  /* parameter settings */
1253 1254
  frame = gimp_frame_new (_("XBM Options"));
  gtk_container_set_border_width (GTK_CONTAINER (frame), 12);
1255
  gtk_box_pack_start (GTK_BOX (gimp_export_dialog_get_content_area (dialog)),
1256
                      frame, TRUE, TRUE, 0);
1257 1258
  gtk_widget_show (frame);

1259
  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
1260 1261 1262
  gtk_container_add (GTK_CONTAINER (frame), vbox);

  /*  X10 format  */
1263
  toggle = gtk_check_button_new_with_mnemonic (_("_X10 format bitmap"));
1264
  gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0);
1265
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), xsvals.x10_format);
1266 1267
  gtk_widget_show (toggle);

1268
  g_signal_connect (toggle, "toggled",
1269 1270 1271
                    G_CALLBACK (gimp_toggle_button_update),
                    &xsvals.x10_format);

Simon Budig's avatar
Simon Budig committed
1272 1273 1274 1275 1276
  grid = gtk_grid_new ();
  gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
  gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
  gtk_box_pack_start (GTK_BOX (vbox), grid, FALSE, FALSE, 0);
  gtk_widget_show (grid);
1277

1278
  /* prefix */
1279 1280
  entry = gtk_entry_new ();
  gtk_entry_set_max_length (GTK_ENTRY (entry), MAX_PREFIX);
1281
  gtk_entry_set_text (GTK_ENTRY (entry), xsvals.prefix);
Simon Budig's avatar
Simon Budig committed
1282 1283 1284
  gimp_grid_attach_aligned (GTK_GRID (grid), 0, 0,
                            _("_Identifier prefix:"), 0.0, 0.5,
                            entry, 1);
1285
  g_signal_connect (entry, "changed",
1286 1287
                    G_CALLBACK (prefix_entry_callback),
                    NULL);
1288

1289
  /* comment string. */
1290
#if 0
1291
  /* DISABLED - see http://bugzilla.gnome.org/show_bug.cgi?id=82763 */
1292 1293
  entry = gtk_entry_new ();
  gtk_entry_set_max_length (GTK_ENTRY (entry), MAX_COMMENT);
1294
  gtk_widget_set_size_request (entry, 240, -1);
1295
  gtk_entry_set_text (GTK_ENTRY (entry), xsvals.comment);
Simon Budig's avatar
Simon Budig committed
1296 1297 1298
  gimp_grid_attach_aligned (GTK_GRID (grid), 0, 1,
                            _("Comment:"), 0.0, 0.5,
                            entry, 1);
1299
  g_signal_connect (entry, "changed",
1300 1301
                    G_CALLBACK (comment_entry_callback),
                    NULL);
1302
#endif
1303

Michael Natterer's avatar
Michael Natterer committed
1304
  /* hotspot toggle */
1305
  toggle = gtk_check_button_new_with_mnemonic (_("_Write hot spot values"));
Michael Natterer's avatar
Michael Natterer committed
1306 1307 1308 1309
  gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0);
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), xsvals.use_hot);
  gtk_widget_show (toggle);

1310
  g_signal_connect (toggle, "toggled",
1311 1312 1313
                    G_CALLBACK (gimp_toggle_button_update),
                    &xsvals.use_hot);

Simon Budig's avatar
Simon Budig committed
1314 1315 1316 1317 1318
  grid = gtk_grid_new ();
  gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
  gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
  gtk_box_pack_start (GTK_BOX (vbox), grid, FALSE, FALSE, 0);
  gtk_widget_show (grid);
Michael Natterer's avatar
Michael Natterer committed
1319

1320
  g_object_bind_property (toggle, "active",
Simon Budig's avatar
Simon Budig committed
1321
                          grid,   "sensitive",
1322
                          G_BINDING_SYNC_CREATE);
Michael Natterer's avatar
Michael Natterer committed
1323

1324 1325 1326
  adj = gtk_adjustment_new (xsvals.x_hot, 0,
                            gimp_drawable_width (drawable_ID) - 1,
                            1, 10, 0);
1327 1328
  spinbutton = gtk_spin_button_new (adj, 1.0, 0);
  gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE);
Simon Budig's avatar
Simon Budig committed
1329 1330 1331
  gimp_grid_attach_aligned (GTK_GRID (grid), 0, 0,
                            _("Hot spot _X:"), 0.0, 0.5,
                            spinbutton, 1);
1332

1333
  g_signal_connect (adj, "value-changed",
1334 1335
                    G_CALLBACK (gimp_int_adjustment_update),
                    &xsvals.x_hot);
Michael Natterer's avatar
Michael Natterer committed
1336

1337 1338 1339
  adj = gtk_adjustment_new (xsvals.y_hot, 0,
                            gimp_drawable_height (drawable_ID) - 1,
                            1, 10, 0);
1340 1341
  spinbutton = gtk_spin_button_new (adj, 1.0, 0);
  gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE);
Simon Budig's avatar
Simon Budig committed
1342 1343 1344
  gimp_grid_attach_aligned (GTK_GRID (grid), 0, 1,
                            _("Hot spot _Y:"), 0.0, 0.5,
                            spinbutton, 1);
1345

1346
  g_signal_connect (adj, "value-changed",
1347 1348
                    G_CALLBACK (gimp_int_adjustment_update),
                    &xsvals.y_hot);
Michael Natterer's avatar
Michael Natterer committed
1349

1350
  /* mask file */
1351
  frame = gimp_frame_new (_("Mask File"));
1352 1353 1354
  gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
  gtk_widget_show (frame);

Simon Budig's avatar
Simon Budig committed
1355 1356 1357 1358 1359
  grid = gtk_grid_new ();
  gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
  gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
  gtk_container_add (GTK_CONTAINER (frame), grid);
  gtk_widget_show (grid);
1360

1361
  toggle = gtk_check_button_new_with_mnemonic (_("W_rite extra mask file"));
Simon Budig's avatar
Simon Budig committed
1362
  gtk_grid_attach (GTK_GRID (grid), toggle, 0, 0, 2, 1);
1363 1364 1365
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), xsvals.write_mask);
  gtk_widget_show (toggle);

1366
  g_signal_connect (toggle, "toggled",
1367 1368 1369 1370 1371
                    G_CALLBACK (gimp_toggle_button_update),
                    &xsvals.write_mask);

  entry = gtk_entry_new ();
  gtk_entry_set_max_length (GTK_ENTRY (entry), MAX_MASK_EXT);
1372
  gtk_entry_set_text (GTK_ENTRY (entry), xsvals.mask_ext);
Simon Budig's avatar
Simon Budig committed
1373 1374 1375
  gimp_grid_attach_aligned (GTK_GRID (grid), 0, 1,
                            _("_Mask file extension:"), 0.0, 0.5,
                            entry, 1);
1376
  g_signal_connect (entry, "changed",
1377 1378
                    G_CALLBACK (mask_ext_entry_callback),
                    NULL);
1379

1380 1381 1382
  g_object_bind_property (toggle, "active",
                          entry,  "sensitive",
                          G_BINDING_SYNC_CREATE);
1383 1384 1385

  gtk_widget_set_sensitive (frame, gimp_drawable_has_alpha (drawable_ID));

1386 1387
  /* Done. */
  gtk_widget_show (vbox);
1388
  gtk_widget_show (dialog);
1389

1390
  run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK);
1391

1392
  gtk_widget_destroy (dialog);
1393

1394 1395
  return run;
}
1396

1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413
static gboolean
print (GOutputStream  *output,
       GError        **error,
       const gchar    *format,
       ...)
{
  va_list  args;
  gboolean success;

  va_start (args, format);
  success =