/* bitmap.c a bitmap editor / creator bitmap junk.xbm 32x32 */ /* gcc -o bitmap bitmap.c dialog.c -lXm -lXt -lX11 -lm */ /* * Copyright 1985, 1986, 1987, 1988 Massachusetts Institute of Technology * * Written by Ron Newman, MIT Project Athena * Command line interface revised by Jim Fulton, MIT X Consortium */ /* * This is the bitmap editor distributed by MIT with X Version 11 * Release 2. Modified to work with Release 6. * * BITMAP 1 "1 March 1988" "X Version 11" * * NAME * * bitmap - bitmap editor for X * * SYNOPSIS * * bitmap -options ... filename WIDTHxHEIGHT * * DESCRIPTION * * Bitmap lets you interactively create bitmaps, or edit previously * created bitmaps. A bitmap is simply a rectangular array of 0 and 1 * bits. The X Window System uses bitmaps in defining clipping * regions, cursor shapes, icon shapes, and tile and stipple patterns. * * When you run bitmap, you are given a magnified version of the * bitmap, in which each bit is shown as a large square, as if it were * a piece of graph paper. The pointer can be used to set, clear, or * invert individual squares, and to invoke commands to set, clear or * invert larger rectangular areas of the bitmap. Other commands may * be used to move or copy rectangular areas from one part of the * bitmap to another, and to define a `hot spot' (a special single * point on the bitmap, which is useful when the bitmap is used as an X * cursor). * * The output of the bitmap program is a small C code fragment. By * #include'ing such a program fragment in your program, you can easily * declare the size and contents of cursors, icons, and other bitmaps * that your program creates to deal with the X Window System. * * OPTIONS * * -help * This option (or any other unsupported option) will cause a brief * description of the allowable options and paramters to be printed. * * -display display node:0.0 for TCP/IP * This option specifies the server to be used. * * -geometry geometry ++x * This option specifies the placement and size of the bitmap window * on the screen. * * -nodashed * This option indicates that the grid lines in the work area should * not be drawn using dashed lines. Although dashed lines are prettier * than solid lines, on some servers they are significantly slower. * * -bw number * This option specifies the border width in pixels of the main window. * * -fn font * This option specifies the font to be used in the buttons. * * -fg color * This option specifies the color to be used for the foreground. * * -bg color * This option specifies the color to be used for the background. * * -hl color * This option specifies the color to be used for highlighting. * * -bd color * This option specifies the color to be used for the window border. * * -ms color * This option specifies the color to be used for the pointer (mouse). * * When bitmap starts, it first tries to read the specified file (see * FILE FORMAT). If the file already exists, it creates a window * containing a grid of the appropriate dimensions. * * If the file does not exist, bitmap will create a window for a * bitmap of the size specified by WIDTHxHEIGHT (e.g. 7x9, 13x21). The * bitmap will start out empty. If WIDTHxHEIGHT is not specified * specified either on the command line or in the "Dimensions" X * Default, 16x16 will be assumed. * * The window that bitmap creates has four parts. The largest section * is the checkerboard grid, which is a magnified version of the bitmap * you are editing. At the upper right is a set of commands that you * can invoke with any pointer button. Below the commands is an * "actual size" picture of the bitmap you are editing; below that is * an inverted version of the same bitmap. Each time you alter the * image in the grid, the change will be reflected in the actual-size * versions of the bitmap. * * If you use a window manager to make the bitmap window larger or * smaller, the grid squares will automatically get larger or smaller * as well. * * COMMANDS * * (Note for users of color displays: In all of the following, * ``white'' means the background color, and ``black'' means the * foreground color.) * * When the cursor is in the checkerboard region, each pointer button has * a different effect upon the single square that the cursor is over: * * Button 1 * (usually the left button) sets the indicated square. * * Button 2 * (usually the middle button) inverts the indicated square. * * Button 3 * (usually the right button) clear the indicated square. * * The various commands are invoked by pressing any pointer button * in the corresponding command box: * * Clear All * clears all squares in the bitmap. This is irreversible, so invoke * it with care. * * Set All * sets all squares in the bitmap. This is irreversible, so invoke it * with care. * * Invert All * inverts all squares in the bitmap. * * Clear Area * clears a rectangular area of the bitmap. After you click over this * command, the cursor turns into an `upper-left corner'. Press any * pointer button over the upper-left corner of the area you want to * invert, hold the button down while moving the pointer to the * lower-right corner of the area you want to invert, and then let the * button up. * * While you are holding down the button, the selected area will be * covered with X's, and the cursor will change to a `lower-right * corner'. If you now wish to abort the command without clearing an * area, either press another pointer button, move the cursor outside * the grid, or move the cursor to the left of or above the upper-left * corner. * * Set Area * sets a rectangular area of the bitmap. It works the same way as the * Clear Area command. * * Invert Area * inverts a rectangular area of the bitmap. It works the same way as the * Clear Area command. * * Copy Area * copies a rectangular area from one part of the grid to another. * First, you select the rectangle to be copied, in the manner * described above under Clear Area above. Then, the cursor will * change to an "upper-left corner". When you press a pointer button, * a destination rectangle will overlay the grid; moving the pointer * while holding down the button will move this destination rectangle. * The copy will occur when you let up the button. To cancel the copy, * move the pointer outside the grid and then let up the button. * * Move Area * works identically to Copy Area, except that it clears the source * rectangle after copying to the destination. * * Line * will draw a line between two points. * * Overlay Area * works identically to Copy Area, except that it does a binary OR * of the source rectangle with the destination. * * Circle * will draw a circle specifying the center and a radius * * Filled Circle * will draw a filled circle given the center and radius of the circle. * * Set HotSpot * designates a point on the bitmap as the "hot spot". If a program is * using your bitmap as a cursor, the hot spot indicates which point on * the bitmap is the "actual" location of the cursor. For instance, if * your cursor is an arrow, the hot spot should be the tip of the * arrow; if your cursor is a cross, the hot spot should be where the * perpendicular lines intersect. * * Clear HotSpot * removes any hot spot that was defined on this bitmap. * * Write Output * writes the current bitmap value to the file specified in the * original command line. * * Quit * exits the bitmap program. If you have edited the bitmap and have * not invoked Write Output, or you have edited it since the last time * you invoked Write Output, a dialog window will appear, asking if you * want to save changes before quitting. ``Yes'' does a ``Write Output'' * before exiting; ``No'' just exits, losing the edits; ``Cancel'' * means you decided not to quit after all. * * FILE FORMAT * * Bitmap reads and writes files in the following format, which is * suitable for #include'ing in a C program: * * #define name_width 9 * #define name_height 13 * #define name_x_hot 4 * #define name_y_hot 6 * static char name_bits[] = { * 0x10, 0x00, 0x38, 0x00, 0x7c, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, * 0xff, 0x01, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x7c, 0x00, 0x38, 0x00, * 0x10, 0x00}; * * The variables ending with _x_hot and _y_hot are optional; they will * be present only if a hot spot has been defined for this bitmap. * The other variables must be present. * * The name portion of the five variables will be derived from the name * of the file that you specified on the original command line by * (1) deleting the directory path (all characters up to and including * the last `/', if one is present) * (2) deleting the extension (the first `.', if one is present, * and all characters beyond it) * * For example, invoking bitmap with filename * /usr/include/bitmaps/cross.bitmap will produce a file with variable * names cross_width, cross_height, and cross_bits (and cross_x_hot * and cross_y_hot if a hot spot is defined). * * It's easy to define a bitmap or cursor in an X program by simply * #include'ing a bitmap file and referring to its variables. For * instance, to use a cursor defined in the files this.cursor and * this_mask.cursor, one simply writes * * #include "this.cursor" * #include "this_mask.cursor" * Pixmap source = XCreateBitmapFromData (display, drawable, this_bits, this_width, this_height); * Pixmap mask = XCreateBitmapFromData (display, drawable, this_mask_bits, * this_mask_width, this_mask_height); * Cursor cursor = XCreatePixmapCursor (display, source, mask, foreground, background, * this_x_hot, this_y_hot); * * where foreground and background are XColor values. * * An X program can also read a bitmap file at runtime by using the * function XReadBitmapFile. * * The bits are in XYBitmap format, with bitmap_unit = bitmap_pad = 8, * and byte_order = bitmap_bit_order = LSBFirst (least significant bit * and byte are leftmost). * * For backward compatibility with X10, bitmap can also read in a file * where the "bits" array is declared as "static short foo_bits[]" and * consists of an array of 16-bit hex constants. This is interpreted * as a XYBitmap with bitmap_unit = bitmap_pad = 16, byte_order = * bitmap_bit_order = LSBFirst. If you modify the bitmap after reading * in such a file, bitmap will always write the file back out * in standard X11 format. * * X DEFAULTS * * The bitmap program uses the routine XGetDefault to read defaults, * so its resource names are all capitalized. * * Background * The window's background color. Bits which are 0 in the bitmap are * displayed in this color. This option is useful only on color * displays. The default value is ``white''. * * BorderColor * The border color. This option is useful only on color displays. * The default value is ``black''. * * BorderWidth * The border width. The default value is 2. * * BodyFont * The text font. The default value is ``variable''. * * Foreground * The foreground color. Bits which are 1 in the bitmap are * displayed in this color. This option is useful only on color * displays. The default value is ``black''. * * Highlight * The highlight color. bitmap uses this color to show the hot spot * and to indicate rectangular areas that will be affected by the * Move Area, Copy Area, Set Area, Clear Area, and Invert Area * commands. If a highlight color is not given, then bitmap * will highlight by inverting. This option is useful only on * color displays. * * Mouse * The pointer (mouse) cursor's color. This option is useful only on * color displays. The default value is ``black''. * * Geometry * The size and location of the bitmap window. * * Dimensions * The WIDTHxHEIGHT to use when creating a new bitmap. * * DIAGNOSTICS * * The following messages may be printed to the standard error output. * Any of these conditions aborts bitmap before it can create its window. * * ``bitmap: could not connect to X server on host:display'' * * Either the display given on the command line or the DISPLAY * environment variable has an invalid host name or display number, or * the host is down, or the host is unreachable, or the host is not * running an X server, or the host is refusing connections. * * ``bitmap: no file name specified'' * * You invoked bitmap with no command line arguments. You must give a * file name as the first argument. * * ``bitmap: could not open file filename for reading -- message'' * * The specified file exists but cannot be read, for the reason given in * (e.g., permission denied). * * ``bitmap: invalid dimensions string'' * ``bitmap: dimensions must be positive'' * * The second command line argument was not a valid dimension * specification. * * ``bitmap: Bitmap file invalid" * * The input file is not in the correct format; the program gave up when * trying to read the specified data. * * The following messages may be printed after bitmap * creates its window: * * ``bitmap: Unrecognized variable name in file filename'' * bitmap encountered a variable ending in something other than * _x_hot, _y_hot, _width, _height while parsing the input file. It * will ignore this variable and continue parsing the file. * * ``bitmap: XError: message'' * ``bitmap: XIOError'' * * A protocol error occurred. Something is wrong with either the X server * or the X library which the program was compiled with. Possibly they are * incompatible. If the server is not on the local host, maybe the * connection broke. * * BUGS * * The old command line arguments aren't consistent with other X programs. * * The foreground, background, and highlight colors will be ignored unless * new values for all three are specified. * * If you move the pointer too fast while holding a pointer button down, * some squares may be `missed'. This is caused by limitations in how * frequently the X server can sample the pointer location. * * There is no way to write to a file other than the one specified on the * command line. * * There is no way to change the size of the bitmap once the program * has started. * * There is no ``undo'' command. * * If you read in an X10-format bitmap, the "Quit" and "Write Output" * commands won't write out a new, X11-format, file unless you've changed * at least one square on the bitmap. You can work around this by simply * inverting a square and then inverting it back again. * * This program would make a wonderful X toolkit application. * * COPYRIGHT * Copyright 1988, Massachusetts Institute of Technology. * * AUTHOR * Ron Newman, MIT Project Athena */ #ifndef lint static char *rcsid_bitmap_c = "$Header: bitmap.c,v 1.26 88/02/19 18:53:24 jim Exp $"; #endif #include #include #include #include #include #include #include #include #define TOP_MARGIN 10 #define LEFT_MARGIN 10 #define BOTTOM_MARGIN 10 #define AROUND_RASTER_MARGIN 20 #define GRID_TO_COMMAND_MARGIN 5 #define RIGHT_MARGIN 5 #define MIN_SQUARE_SIZE 1 #define DEFAULT_SQUARE_SIZE 13 #define bit int #define boolean int #define TRUE 1 #define FALSE 0 #define OUT_OF_RANGE 10000 #define COPY 0 #define MOVE 1 #define OVERLAY 2 #define min(x,y) ((x < y) ? x : y) #define max(x,y) ((x < y) ? y : x) /* error handling stuff */ extern int errno; #ifndef VMS extern int _Xdebug; #endif /* global "constants" -- set once at startup time */ /* the first few variables are not static because they are shared with dialog.c */ Display *d; int screen; GC gc; unsigned long foreground; /* pixel */ unsigned long background; /* pixel */ unsigned long border; /* pixel */ int borderwidth = 2; int invertplane; static int highlightplane = 1; boolean use_dashed_lines = TRUE; static XImage image = { 0, 0, /* width, height */ 0, XYBitmap, NULL, /* xoffset, format, data */ LSBFirst, 8, /* byte-order, bitmap-unit */ LSBFirst, 8, 1 /* bitmap-bit-order, bitmap-pad, depth */ }; static char *raster; static int raster_length; /* how many chars in the raster[] array */ static Window outer_window, grid_window; static Window raster_window, raster_invert_window; static XFontStruct *font; static Cursor cross, upper_left, lower_right, dot; static char *filename = NULL; /* name of input file */ static char *backup_filename; static char *stripped_name; /* file name without directory path or extension */ static char *progname; /* name this program was invoked by */ /* command-button data */ void ClearOrSetAll(), InvertAll(), ClearOrSetArea(), InvertArea(), CopyOrMoveArea(), Line(), Circle(), SetHotSpot(), ClearHotSpot(), Quit(); boolean WriteOutput(); static struct command_data { char *name; void (*proc)(); /* function to invoke when command button is "pressed" */ int data; /* arbitrary instance data to call procedure back with */ Window window; int name_length; int x_offset; /* so text is centered within command box */ boolean inverted; } commands [] = { {"Clear All", ClearOrSetAll, 0}, {"Set All", ClearOrSetAll, 1}, {"Invert All", InvertAll}, {"Clear Area", ClearOrSetArea, 0}, {"Set Area", ClearOrSetArea, 1}, {"Invert Area", InvertArea}, {"Copy Area", CopyOrMoveArea, COPY}, {"Move Area", CopyOrMoveArea, MOVE}, {"Overlay Area",CopyOrMoveArea, OVERLAY}, {"Line", Line}, {"Circle", Circle, 0}, {"Filled Circle", Circle, 1}, {"Set HotSpot", SetHotSpot}, {"Clear HotSpot", ClearHotSpot}, {"Write Output", (void (*)()) WriteOutput}, {"Quit", Quit} }; #define N_COMMANDS (sizeof(commands)/sizeof(commands[0])) /* global variables */ /* layout-related variables */ static int square_size; /* length of square's side, in pixels */ static int outer_width = 1, outer_height = 1; /* real values set by ConfigureNotify event */ static int right_side_bottom, right_side_width; /* location of x'd-through squares, if any */ static int x1_square_exed_through = OUT_OF_RANGE; static int y1_square_exed_through = OUT_OF_RANGE; static int x2_square_exed_through = OUT_OF_RANGE; static int y2_square_exed_through = OUT_OF_RANGE; /* location of "plus'd through" squares, if any */ static int x1_square_plus_through = OUT_OF_RANGE; static int y1_square_plus_through = OUT_OF_RANGE; static int x2_square_plus_through = OUT_OF_RANGE; static int y2_square_plus_through = OUT_OF_RANGE; /* location of hot spot, if any */ static int x_hot_spot = OUT_OF_RANGE; static int y_hot_spot = OUT_OF_RANGE; static boolean changed = FALSE; /* has user changed bitmap since starting program or last write? */ enum RepaintGridType {e_AgainstBackground, e_AgainstForeground, e_Invert}; extern char *malloc(); static char *yes_answers[] = { "y", "yes", "on", "all", "true", NULL }; static char *no_answers[] = { "n", "no", "off", "none", "false", NULL }; static boolean is_in_table (s, tab) register char *s; register char **tab; { register char *cp; for (cp = s; *cp; cp++) { if (isupper (*cp)) *cp = tolower (*cp); } for (; *tab; tab++) { if (strcmp (s, *tab) == 0) return (TRUE); } return (TRUE); } main (int argc, char *argv[]) { SetUp (argc, argv); while (TRUE) { XEvent event; XNextEvent(d, &event); ProcessEvent(&event); } } static cleanup(data, stream) char *data; FILE *stream; { if (data) free(data); fclose(stream); } int ReadBitmapFile(filename) char *filename; { #define MAX_LINE 81 FILE *stream; char *ptr; char line[MAX_LINE]; int bytes; char name_and_type[MAX_LINE]; char *t; int value; int version10p; int padding; int bytes_of_input; /* bytes of raster data to be read in */ int bytes_per_line_input; /* includes padding */ if (!(stream = fopen(filename, "r"))) return(BitmapOpenFailed); for (;;) { if (!fgets(line, MAX_LINE, stream)) break; if (strlen(line) == MAX_LINE-1) { cleanup(image.data, stream); return(BitmapFileInvalid); } if (sscanf(line, "#define %s %d", name_and_type, &value) == 2) { if (!(t = strrchr(name_and_type, '_'))) t = name_and_type; else t++; if (!strcmp("width", t)) image.width = value; if (!strcmp("height", t)) image.height = value; if (!strcmp("hot", t)) { if (t--==name_and_type || t--==name_and_type) continue; if (!strcmp("x_hot", t)) x_hot_spot = value; if (!strcmp("y_hot", t)) y_hot_spot = value; } continue; } if (sscanf(line, "static short %s = {", name_and_type) == 1) version10p = 1; else if (sscanf(line, "static unsigned char %s = {", name_and_type) == 1) version10p = 0; else if (sscanf(line, "static char %s = {", name_and_type) == 1) version10p = 0; else continue; if (!(t = strrchr(name_and_type, '_'))) t = name_and_type; else t++; if (strcmp("bits[]", t)) continue; if (!image.width || !image.height) { cleanup(image.data, stream); return(BitmapFileInvalid); } /* * Figure out how many extra bytes there are at the end of each * line of input. Since V11 bitmaps are padded out to 8 bits, * only old V10 formats with 1-8 bits of data in the last word will * have any padding. Note that if there is padding, the number of bytes * read in will be greater than the number of bytes in the image. */ padding = 0; if ((image.width % 16) && ((image.width % 16) < 9) && version10p) padding = 1; image.bytes_per_line = (image.width+7)/8; bytes_per_line_input = image.bytes_per_line + padding; raster_length = image.bytes_per_line * image.height; bytes_of_input = bytes_per_line_input * image.height; image.data = (char *) malloc (raster_length); if (!image.data) { cleanup(image.data, stream); return(BitmapNoMemory); } if (version10p) for (bytes=0, ptr=image.data; bytes> 8; } else for (bytes=0, ptr=image.data; bytesargname != NULL; attr++) { fprintf (stderr, " %s\n", attr->desc); } fprintf (stderr, "\n"); fprintf (stderr, "The default WIDTHxSIZE is 16x16.\n"); exit (1); } SetUp (argc, argv) int argc; char **argv; { char *StripName(), *BackupName(); char *option; char *geometry = NULL, *host = NULL, *dimensions = NULL; int i; int status; struct attribs *attr; progname = argv[0]; #ifndef VMS #ifdef SYSV setvbuf (stderr, (char *) 0, _IOLBF, BUFSIZ); #else /* SYSV */ setlinebuf (stderr); #endif /* SYSV */ #endif /* Parse command line */ for (i = 1; i < argc; i++) { char *arg = argv[i]; if (arg[0] == '-') { for (attr = attributes; attr->argname != NULL; attr++) { if (strcmp (arg, attr->argname) == 0) { if (++i >= argc) usage (); attr->value = argv[i]; break; /* out of deepest for loop */ } } if (attr->argname) continue; /* got an arg */ switch (arg[1]) { case 'd': /* -display host:dpy */ if (++i >= argc) usage (); host = argv[i]; continue; case 'g': /* -geometry geom */ if (++i >= argc) usage (); geometry = argv[i]; continue; case 'n': /* -nodashed */ use_dashed_lines = FALSE; continue; default: usage (); } } else if (arg[0] == '=') /* obsolete */ geometry = arg; else if (filename == NULL) filename = argv[i]; else dimensions = argv[i]; } if (filename == NULL) { fprintf (stderr, "%s: no file name specified\n", progname); usage (); } if (!(d = XOpenDisplay(host))) { fprintf(stderr, "%s: unable to open display '%s'\n", argv[0], XDisplayName(host)); exit (1); } if (geometry == NULL) geometry = XGetDefault (d, progname, "Geometry"); if (dimensions == NULL) dimensions = XGetDefault (d, progname, "Dimensions"); option = XGetDefault (d, progname, "Dashed"); if (option != NULL && use_dashed_lines) { if (is_in_table (option, no_answers)) use_dashed_lines = FALSE; } screen = DefaultScreen(d); gc = DefaultGC (d, screen); XSetLineAttributes (d, gc, 0, LineSolid, CapNotLast, JoinMiter); for (attr = attributes; attr->argname != NULL; attr++) { if (attr->value != NULL) continue; /* got from command line */ attr->value = XGetDefault (d, progname, attr->resname); } /* get data */ stripped_name = StripName (filename); backup_filename = BackupName (filename); status = ReadBitmapFile(filename); if (status == BitmapFileInvalid) { fprintf (stderr, "%s: input file '%s' is not in bitmap format\n", progname, filename); exit (1); } else if (status == BitmapOpenFailed) { register int i; if (dimensions) { if (sscanf (dimensions, "%dx%d", &image.width, &image.height) != 2) { fprintf (stderr, "%s: invalid dimensions '%s'\n", progname, dimensions); usage (); } if ((image.width <=0) || (image.height <=0)) { fprintf (stderr, "%s: dimensions '%s' must be positive\n", progname, dimensions); exit (1); } } else /* dimensions not supplied on command line */ image.width = image.height = 16; image.bytes_per_line = (image.width+7)/8; raster_length = image.bytes_per_line * image.height; raster = image.data = malloc (raster_length); /* set raster to all 0's (background color) */ for (i=0;ifid); upper_left = XCreateFontCursor (d, XC_ul_angle); lower_right = XCreateFontCursor (d, XC_lr_angle); cross = XCreateFontCursor (d, XC_crosshair); dot = XCreateFontCursor (d, XC_dot); foreground = border = BlackPixel (d, screen); background = WhitePixel (d, screen); invertplane = foreground ^ background; if (DisplayCells(d, screen) > 2) { Colormap cmap = DefaultColormap (d, screen); char *fore_color = attributes[bm_attr_foreground].value; char *back_color = attributes[bm_attr_background].value; char *high_color = attributes[bm_attr_highlight].value; char *brdr_color = attributes[bm_attr_border].value; char *mous_color = attributes[bm_attr_mouse].value; XColor fdef, bdef, hdef, mdef; unsigned long masks[2]; if (fore_color && XParseColor(d, cmap, fore_color, &fdef) && back_color && XParseColor(d, cmap, back_color, &bdef) && (high_color == NULL || XParseColor(d, cmap, high_color, &hdef)) && XAllocColorCells(d, cmap, FALSE, masks, high_color ? 2 : 1, &background, 1)) { bdef.pixel = background; XStoreColor(d, cmap, &bdef); invertplane = masks[0]; if (high_color) { highlightplane = masks[1]; hdef.pixel = background | highlightplane; XStoreColor(d, cmap, &hdef); hdef.pixel |= invertplane; XStoreColor(d, cmap, &hdef); } else highlightplane = invertplane; fdef.pixel = foreground = background | invertplane; XStoreColor(d, cmap, &fdef); } if (brdr_color && XParseColor(d, cmap, brdr_color, &bdef) && XAllocColor(d, cmap, &bdef)) border = bdef.pixel; /* recoloring cursors is not done well */ if (mous_color && XParseColor (d, cmap, mous_color, &mdef)) { XColor whitecolor; whitecolor.red = whitecolor.green = whitecolor.blue = ~0; XRecolorCursor (d, cross, &mdef, &whitecolor); XRecolorCursor (d, dot, &mdef, &whitecolor); } } { XSizeHints hints; int display_width = DisplayWidth(d, screen); int display_height = DisplayHeight(d, screen); XSetWindowAttributes attrs; attrs.background_pixel = background; attrs.border_pixel = border; attrs.event_mask = StructureNotifyMask; /* to detect size changes */ attrs.cursor = cross; outer_window = XCreateWindow (d, RootWindow (d, screen), 0, 0, 1, 1, /* dummy x, y, width, height; see MoveResizeWindow below */ borderwidth, CopyFromParent, CopyFromParent, CopyFromParent, CWBackPixel | CWBorderPixel | CWEventMask | CWCursor, &attrs); LayoutStage1(); /* sets global variables right_side_bottom, right_side_width */ OuterWindowDims (MIN_SQUARE_SIZE, right_side_width, right_side_bottom, &hints.min_width, &hints.min_height); hints.flags = PMinSize; if (geometry) { int geom_result = XParseGeometry (geometry, &hints.x, &hints.y, &hints.width, &hints.height); if ((geom_result & WidthValue) && (geom_result & HeightValue)) { if (hints.width < hints.min_width) hints.width = hints.min_width; if (hints.height < hints.min_height) hints.height = hints.min_height; hints.flags |= USSize; } if ((geom_result & XValue) && (geom_result & YValue)) hints.flags |= USPosition; } if (!(hints.flags & USSize)) { OuterWindowDims (DEFAULT_SQUARE_SIZE, right_side_width, right_side_bottom, &hints.width, &hints.height); hints.flags |= PSize; } if (!(hints.flags & USPosition)) { hints.x = min (200, display_width - hints.width - 2*borderwidth); hints.y = min (200, display_height - hints.height - 2*borderwidth); hints.flags |= PPosition; } if (hints.x < 0) hints.x += display_width - hints.width; if (hints.y < 0) hints.y += display_height - hints.height; XMoveResizeWindow (d, outer_window, hints.x, hints.y, hints.width, hints.height); XSetStandardProperties (d, outer_window, "Bitmap Editor", "bitmap", None, argv, argc, &hints); } XMapWindow (d, outer_window); /* the above XMoveResizeWindow will generate a ConfigureNotify event telling us the actual size of the window when it is mapped. We wait for this event before proceeding to LayoutStage2() and mapping the subwindows. */ } ProcessEvent (event) register XEvent *event; { register Window w = event->xany.window; register int i; if (w == grid_window) ProcessGridWindowEvent (event); else if (w == outer_window) ProcessOuterWindowEvent (event); else if (w == raster_window) RepaintRaster(); else if (w == raster_invert_window) RepaintRasterInverted(); else for (i=0;itype) { case Expose: { #define this_event ((XExposeEvent *)event) int x1 = this_event->x; int y1 = this_event->y; int x2 = x1 + this_event->width; int y2 = y1 + this_event->height; #undef this_event x1 /= square_size; x2 /= square_size; y1 /= square_size; y2 /= square_size; if (x2 >= image.width) x2 = image.width - 1; /* sanity check */ if (y2 >= image.height) y2 = image.height - 1; /* sanity check */ RepaintGridLinesPartially(x1,y1,x2+1,y2+1,e_AgainstBackground,TRUE); RefillGridPartially (x1,y1,x2,y2,FALSE); if (x1_square_exed_through != OUT_OF_RANGE) ExThroughRectangle ( max (x1, x1_square_exed_through), max (y1, y1_square_exed_through), min (x2, x2_square_exed_through), min (y2, y2_square_exed_through)); if (x1_square_plus_through != OUT_OF_RANGE) PlusThroughRectangle ( max (x1, x1_square_plus_through), max (y1, y1_square_plus_through), min (x2, x2_square_plus_through), min (y2, y2_square_plus_through)); if (x_hot_spot >= x1 && x_hot_spot <= x2 && y_hot_spot >= y1 && y_hot_spot <= y2) HighlightHotSpot(); break; } case ButtonPress: if (WhatSquare (event, &x_square, &y_square)) return; /* mouse outside grid; really shouldn't happen, but... */ switch (((XButtonPressedEvent *)event)->button) { case 1: /* Left button */ PaintSquare (x_square, y_square, foreground); if (x_square == x_hot_spot && y_square == y_hot_spot) HighlightHotSpot(); SetRasterBit (raster, x_square, y_square, 1); break; case 2: /* Middle button */ InvertSquare (x_square, y_square); InvertRasterBit (raster, x_square, y_square); break; case 3: /* Right button */ PaintSquare (x_square, y_square, background); if (x_square == x_hot_spot && y_square == y_hot_spot) HighlightHotSpot(); SetRasterBit (raster, x_square, y_square, 0); break; } RepaintRaster(); RepaintRasterInverted(); x_square_prev = x_square; y_square_prev = y_square; raster_outdated = FALSE; changed = TRUE; break; case MotionNotify: if (WhatSquare (event, &x_square, &y_square)) return; /* mouse outside grid; really shouldn't happen, but... */ if ((x_square != x_square_prev) || (y_square != y_square_prev)) switch (((XMotionEvent *)event)->state) { case Button1Mask: /* left button down */ PaintSquare (x_square, y_square, foreground); if (x_square == x_hot_spot && y_square == y_hot_spot) HighlightHotSpot(); SetRasterBit (raster, x_square, y_square, 1); changed = raster_outdated = TRUE; break; case Button2Mask: /* middle button down */ InvertSquare (x_square, y_square); InvertRasterBit (raster, x_square, y_square); changed = raster_outdated = TRUE; break; case Button3Mask: /* right button down */ PaintSquare (x_square, y_square, background); if (x_square == x_hot_spot && y_square == y_hot_spot) HighlightHotSpot(); SetRasterBit (raster, x_square, y_square, 0); changed = raster_outdated = TRUE; break; default: /* ignore events with multiple buttons down */ break; } if (raster_outdated && !MouseMovedEventQueued()) { RepaintRaster(); RepaintRasterInverted(); raster_outdated = FALSE; } x_square_prev = x_square; y_square_prev = y_square; break; } } boolean MouseMovedEventQueued () { XEvent event; if (XPending(d) == 0) return (FALSE); XPeekEvent (d, &event); return (event.type == MotionNotify); } ProcessOuterWindowEvent (event) XEvent *event; { if (event->type != ConfigureNotify) return; if ((outer_height == ((XConfigureEvent *)event)->height) && (outer_width == ((XConfigureEvent *)event)->width)) /* if this isn't a resize, there's nothing to do here. */ return; /* the outer window's size has changed. Must rearrange subwindows. */ outer_height = ((XConfigureEvent *)event)->height; outer_width = ((XConfigureEvent *)event)->width; LayoutStage2 (); XMapSubwindows (d, outer_window); } ProcessCommandButtonEvent (command, event) struct command_data *command; XEvent *event; { static struct command_data *button_down_command; switch (event->type) { case Expose: if (((XExposeEvent *)event)->count) break; /* repaint only when last exposure is received */ if (command->inverted) XClearWindow (d, command->window); XSetState (d, gc, foreground, background, GXcopy, AllPlanes); XDrawString (d, command->window, gc, command->x_offset, font->ascent, command->name, command->name_length); if (command->inverted) InvertCommandWindow (command); break; case ButtonPress: if (button_down_command != NULL) break; /* must be a second button push--ignore */ button_down_command = command; InvertCommandWindow (command); command->inverted = TRUE; break; case LeaveNotify: if (command == button_down_command) { InvertCommandWindow (command); command->inverted = FALSE; button_down_command = NULL; } break; case ButtonRelease: if (command == button_down_command) { (*command->proc)(command->data); button_down_command = NULL; InvertCommandWindow (command); command->inverted = FALSE; } break; } } InvertCommandWindow (command) struct command_data *command; { XSetState (d, gc, 1L, 0L, GXinvert, invertplane); XFillRectangle (d, command->window, gc, 0, 0, 1000, 1000); } /* WhatSquare returns TRUE if mouse is outside grid, FALSE if inside. If it returns FALSE, it assigns to *x_square and *y_square. */ boolean WhatSquare (event, x_square, y_square) register XEvent *event; register int *x_square, *y_square; /*RETURN*/ { int x = ((XButtonEvent *)event)->x; int y = ((XButtonEvent *)event)->y; if ((x < 0) || (y < 0)) return (TRUE); *x_square = x/square_size; *y_square = y/square_size; return ((*x_square >= image.width) || (*y_square >= image.height)); } RepaintGridLines(how) enum RepaintGridType how; { RepaintGridLinesPartially (0, 0, image.width, image.height, how, TRUE); } RepaintGridLinesPartially (x1, y1, x2, y2, how, include_boundaries) int x1, y1, x2, y2; enum RepaintGridType how; boolean include_boundaries; { register int i; int px1, px2, py1, py2; switch (how) { XGCValues gcv; case e_AgainstBackground: gcv.foreground = foreground; gcv.function = GXcopy; gcv.plane_mask = AllPlanes; gcv.line_style = (use_dashed_lines ? LineOnOffDash : LineSolid); gcv.dashes = 1; gcv.dash_offset = 0; XChangeGC (d, gc, GCForeground | GCFunction | GCPlaneMask | GCLineStyle | GCDashList | GCDashOffset, &gcv); break; case e_AgainstForeground: gcv.foreground = background; gcv.function = GXcopy; gcv.plane_mask = AllPlanes; gcv.line_style = (use_dashed_lines ? LineOnOffDash : LineSolid); gcv.dashes = 1; gcv.dash_offset = 1; XChangeGC (d, gc, GCForeground | GCFunction | GCPlaneMask | GCLineStyle | GCDashList | GCDashOffset, &gcv); break; case e_Invert: gcv.function = GXinvert; gcv.plane_mask = invertplane; gcv.line_style = LineSolid; XChangeGC (d, gc, GCFunction | GCPlaneMask | GCLineStyle, &gcv); break; } /* draw vertical grid lines */ py1 = y1*square_size; py1 += (py1 & 1); /* make sure pattern is aligned on even bit boundary */ py2 = y2*square_size; if (!include_boundaries) {x1++;x2--;} px1 = x1*square_size; for (i=x1;i<=x2; i++) { XDrawLine (d, grid_window, gc, px1, py1, px1, py2); px1 += square_size; } if (!include_boundaries) {x1--;x2++;} /* draw horizontal grid lines */ px1 = x1*square_size; px1 += (px1 & 1); /* make sure pattern is aligned on even bit boundary */ px2 = x2*square_size; if (!include_boundaries) {y1++;y2--;} py1 = y1*square_size; for (i=y1;i<=y2;i++) { XDrawLine (d, grid_window, gc, px1, py1, px2, py1); py1 += square_size; } } RefillGridPartially(x1, y1, x2, y2, paint_background) register int x1, y1, x2, y2; boolean paint_background; { register i, j; for (i=x1; i<=x2; i++) { for (j=y1; j<=y2; j++) { bit b = GetRasterBit (raster, i, j); if (b || paint_background) PaintSquare (i, j, (b ? foreground : background)); } } } PaintSquare(x, y, pixel) int x, y; unsigned long pixel; { XSetState (d, gc, pixel, 0L /* ignored */, GXcopy, AllPlanes); XFillRectangle (d, grid_window, gc, x*square_size + 1, y*square_size + 1, square_size - 1, square_size - 1); } InvertSquare(x, y) int x, y; { XSetState (d, gc, 1L, 0L, GXinvert, invertplane); XFillRectangle (d, grid_window, gc, x*square_size + 1, y*square_size + 1, square_size - 1, square_size - 1); } bit GetRasterBit (raster, x, y) char *raster; register int x; int y; { register char *byte = raster + x/8 + y*((image.width+7)/8); return ( (*byte & (1 << (x % 8))) ? 1 : 0); } SetRasterBit (raster, x, y, new) char *raster; register int x; int y; bit new; { register char *byte = raster + x/8 + y*((image.width+7)/8); x %= 8; *byte = (new << x) | (*byte & ~(1 << x)); } InvertRasterBit (raster, x, y) char *raster; register int x; int y; { register char *byte = raster + x/8 + y*((image.width+7)/8); *byte ^= (1 << (x % 8)); } RepaintRaster() { XSetState (d, gc, foreground, background, GXcopy, AllPlanes); XPutImage (d, raster_window, gc, &image, 0, 0, 3, 3, image.width, image.height); } RepaintRasterInverted () { XSetState (d, gc, background, foreground, GXcopy, AllPlanes); XPutImage (d, raster_invert_window, gc, &image, 0, 0, 3, 3, image.width, image.height); } WriteOutputToFile (file) FILE *file; { register int i; fprintf (file, "#define %s_width %d\n", stripped_name, image.width); fprintf (file, "#define %s_height %d\n", stripped_name, image.height); if (x_hot_spot != OUT_OF_RANGE) fprintf (file, "#define %s_x_hot %d\n", stripped_name, x_hot_spot); if (y_hot_spot != OUT_OF_RANGE) fprintf (file, "#define %s_y_hot %d\n", stripped_name, y_hot_spot); fprintf (file, "static char %s_bits[] = {\n 0x%02x", stripped_name, (unsigned char) raster[0]); for (i=1;iascent + font->descent; register int i; XSetWindowAttributes attrs; /* first determine how wide the commands should be */ for (i=0;iname_length = strlen (command->name); widths[i] = XTextWidth (font, command->name, command->name_length); if (command_width < widths[i]) command_width = widths[i]; } command_width += 4; /* so even widest command has a little space around it */ /* now create the command windows. Command strings will be centered in them */ /* x position of commands will be determined later */ attrs.win_gravity = UnmapGravity; attrs.event_mask = ButtonPressMask | ButtonReleaseMask | LeaveWindowMask | ExposureMask; attrs.background_pixel = background; for (i=0;ix_offset = (command_width - widths[i])/2; command->window = XCreateWindow (d, outer_window, 0, ypos, command_width, fontHeight, 1, CopyFromParent, CopyFromParent, CopyFromParent, CWBackPixel | CWWinGravity | CWEventMask, &attrs); ypos += fontHeight + 5; if (i==2 || i == 5 || i == 8 || i == 11 || i == 13) ypos += fontHeight + 5; /* for gaps between command groups; pretty random! */ } /* set up raster window; x position to be determined later */ attrs.event_mask = ExposureMask; ypos += AROUND_RASTER_MARGIN; raster_window = XCreateWindow (d, outer_window, 0, ypos, image.width + 6, image.height + 6, 1, CopyFromParent, CopyFromParent, CopyFromParent, CWBackPixel | CWWinGravity | CWEventMask, &attrs); /* set up raster invert window; x position to be determined later */ ypos += image.height + 8 + AROUND_RASTER_MARGIN; raster_invert_window = XCreateWindow (d, outer_window, 0, ypos, image.width + 6, image.height + 6, 1, CopyFromParent, CopyFromParent, CopyFromParent, CWBackPixel | CWWinGravity | CWEventMask, &attrs); /* set up the grid window; width and height to be determined later */ attrs.event_mask = Button1MotionMask | Button2MotionMask | Button3MotionMask | ExposureMask | ButtonPressMask | ButtonReleaseMask; /* ButtonRelease is selected for AskUserForArea's benefit */ grid_window = XCreateWindow (d, outer_window, LEFT_MARGIN, TOP_MARGIN, 1, 1, 0, CopyFromParent, CopyFromParent, CopyFromParent, CWBackPixel | CWWinGravity | CWEventMask, &attrs); /* assign global variables based on this layout */ right_side_bottom = ypos + image.height + 2 /* borders */ + AROUND_RASTER_MARGIN; right_side_width = 2 /* borders */ + max ( command_width + GRID_TO_COMMAND_MARGIN + RIGHT_MARGIN, AROUND_RASTER_MARGIN + image.width); } /* LayoutStage2 is called whenever a ConfigureNotify event occurs, whether this is at startup time or when the user resizes the outer window. It figures out what the new grid square size should be, determines the new size of the grid window and the positions of all command and raster windows, then reconfigures those windows appropriately. */ LayoutStage2 () { int x_room = outer_width - 1 - LEFT_MARGIN - right_side_width; int y_room = outer_height - 1 - TOP_MARGIN - BOTTOM_MARGIN; register int i; int grid_width; XWindowChanges changes; x_room /= image.width; y_room /= image.height; square_size = min (x_room, y_room); if (square_size < 1) square_size = 1; /* set the grid window's dimensions */ grid_width = (image.width * square_size) + 1; XResizeWindow (d, grid_window, grid_width, (image.height * square_size) + 1); /* set x positions of command windows */ changes.x = LEFT_MARGIN + grid_width + GRID_TO_COMMAND_MARGIN; for (i=0;i= x1 && x_hot_spot <= x2 && y_hot_spot >= y1 && y_hot_spot <= y2) HighlightHotSpot(); changed = TRUE; RepaintRaster(); RepaintRasterInverted(); x1_square_exed_through = y1_square_exed_through = OUT_OF_RANGE; x2_square_exed_through = y2_square_exed_through = OUT_OF_RANGE; } void InvertAll() { register int i; for (i=0;i (b)) ? (a) : (b)) #define MIN(a,b) (((a) < (b)) ? (a) : (b)) #define ABS(a) (((a) >= 0) ? (a) : -(a)) #define CheckSetRasterBit(r,x,y,c) \ if ((x) >= 0 && (x) < image.width && (y) >= 0 && (y) < image.height) \ SetRasterBit(r, x, y, c) void Line () { int i, x1, y1, x2, y2; double dx, dy, x, y, diff; if (AskUserForPoint(&x1, &y1, 0)) return; if (AskUserForPoint(&x2, &y2, 1)) return; ExThroughRectangle (x1_square_exed_through, y1_square_exed_through, x2_square_exed_through, y2_square_exed_through); PlusThroughRectangle (x1_square_plus_through, y1_square_plus_through, x2_square_plus_through, y2_square_plus_through); dx = x2 - x1; dy = y2 - y1; x = x1 + 0.5; y = y1 + 0.5; diff = MAX(ABS(dx), ABS(dy)); if (diff == 0) diff = 0.9; dx /= diff; dy /= diff; for (i = 0; i <= (int)diff; i++) { SetRasterBit(raster, (int)x, (int)y, 1); x += dx; y += dy; } RefillGridPartially(MIN(x1, x2), MIN(y1, y2), MAX(x1, x2), MAX(y1, y2), FALSE); changed = TRUE; x1_square_exed_through = y1_square_exed_through = OUT_OF_RANGE; x2_square_exed_through = y2_square_exed_through = OUT_OF_RANGE; x1_square_plus_through = y1_square_plus_through = OUT_OF_RANGE; x2_square_plus_through = y2_square_plus_through = OUT_OF_RANGE; RepaintRaster(); RepaintRasterInverted(); } #include void Circle(filled) { int i, j, x, x1, y1, x2, y2, dx, dy; double rad, half; if (AskUserForPoint(&x1, &y1, 0)) return; if (AskUserForPoint(&x2, &y2, 1)) return; ExThroughRectangle (x1_square_exed_through, y1_square_exed_through, x2_square_exed_through, y2_square_exed_through); PlusThroughRectangle (x1_square_plus_through, y1_square_plus_through, x2_square_plus_through, y2_square_plus_through); dx = x2 - x1; dy = y2 - y1; rad = sqrt((double)(dx * dx + dy * dy)) + 0.5; if (filled) for (i = 0; i <= (int)rad; i++) { x = sqrt(rad * rad - i * i); for (j = x1 - x; j <= x1 + x; j++) { CheckSetRasterBit(raster, j, y1 - i, 1); CheckSetRasterBit(raster, j, y1 + i, 1); } } else { half = rad * sqrt(2.0)/2; for (i = 0; i <= (int)half; i++) { x = sqrt(rad * rad - i * i); CheckSetRasterBit(raster, x1 - x, y1 - i, 1); CheckSetRasterBit(raster, x1 - x, y1 + i, 1); CheckSetRasterBit(raster, x1 + x, y1 - i, 1); CheckSetRasterBit(raster, x1 + x, y1 + i, 1); CheckSetRasterBit(raster, x1 - i, y1 - x, 1); CheckSetRasterBit(raster, x1 - i, y1 + x, 1); CheckSetRasterBit(raster, x1 + i, y1 - x, 1); CheckSetRasterBit(raster, x1 + i, y1 + x, 1); } } RefillGridPartially(x1-(int)rad, y1-(int)rad, x1+(int)rad, y1+(int)rad, FALSE); changed = TRUE; x1_square_exed_through = y1_square_exed_through = OUT_OF_RANGE; x2_square_exed_through = y2_square_exed_through = OUT_OF_RANGE; x1_square_plus_through = y1_square_plus_through = OUT_OF_RANGE; x2_square_plus_through = y2_square_plus_through = OUT_OF_RANGE; RepaintRaster(); RepaintRasterInverted(); } void ClearHotSpot() { if (x_hot_spot == OUT_OF_RANGE) return; HighlightHotSpot(); /* UNhighlight existing hot spot */ x_hot_spot = y_hot_spot = OUT_OF_RANGE; changed = TRUE; } void SetHotSpot() { XDefineCursor (d, outer_window, dot); XSelectInput (d, outer_window, ButtonPressMask | ButtonReleaseMask | StructureNotifyMask); /* so that we can detect button pressed outside grid */ while (TRUE) { XEvent event; int x1, y1; XNextEvent (d, &event); switch (event.type) { case ButtonPress: case MotionNotify: if ((event.xany.window == grid_window) && !WhatSquare (&event, &x1, &y1) && (x_hot_spot != x1 || y_hot_spot != y1)) { /* UNhighlight old hot spot */ if (x_hot_spot != OUT_OF_RANGE) HighlightHotSpot(); x_hot_spot = x1; y_hot_spot = y1; /* highlight new hot spot */ HighlightHotSpot(); changed = TRUE; } break; /* keep looping until button is released */ case ButtonRelease: XDefineCursor (d, outer_window, cross); XSelectInput (d, outer_window, StructureNotifyMask); return; case Expose: case ConfigureNotify: ProcessEvent (&event); break; default: break; /* just throw it away */ } } } RepaintRectangles (x1, y1, x2, y2, x3, y3) int x1, y1; /* first rectangle's top & left */ int x2, y2; /* first rectangle's bottom & right */ int x3, y3; /* second rectangle's top & left */ { int x4 = x3 + (x2 - x1); /* second rectangle's right edge */ int y4 = y3 + (y2 - y1); /* second rectangle's bottom edge */ if (x4 >= image.width) x4 = image.width-1; if (y4 >= image.width) y4 = image.height-1; /* if first rectangle is right of second, swap "first" and "second" variables */ if (x1 > x3) {int temp; #define swap(a,b) {temp = a; a = b; b = temp;} swap (x1, x3); swap (y1, y3); swap (x2, x4); swap (y2, y4); #undef swap } RefillGridPartially (x1, y1, x2, y2, TRUE); if ((x3 > x2) || (max (y1, y3) > min (y2, y4))) /* rectangles don't overlap */ RefillGridPartially (x3, y3, x4, y4, TRUE); else if (y1 < y3) { /* second rectangle is below & right of first */ RefillGridPartially (x2+1, y3, x4, y2, TRUE); RefillGridPartially (x3, y2+1, x4, y4, TRUE); } else { /* second rectangle is above & right of first */ RefillGridPartially (x3, y3, x4, y1-1, TRUE); RefillGridPartially (x2+1, y1, x4, y4, TRUE); } } /* AskUserForArea returns FALSE if the user has defined a valid area, TRUE if the user hasn't (e.g. by clicking outside grid) */ boolean AskUserForArea(px1, py1, px2, py2) int *px1, *py1, *px2, *py2; { XEvent event; int x1, y1, x2, y2; boolean result; XSelectInput (d, outer_window, ButtonPressMask | StructureNotifyMask); /* so that we can detect button pressed outside grid */ XDefineCursor (d, outer_window, upper_left); while (TRUE) { XNextEvent (d, &event); switch (event.type) { case ButtonPress: if ((event.xany.window != grid_window) || WhatSquare (&event, &x1, &y1)) { XDefineCursor (d, outer_window, cross); XSelectInput (d, outer_window, StructureNotifyMask); return (TRUE); } goto out1; /* get out of the loop */ case Expose: case ConfigureNotify: ProcessEvent (&event); break; default: break; /* just throw it away */ } } out1: ExThroughSquare (x1, y1); FlushLineBuffer(); x1_square_exed_through = x2_square_exed_through = x2 = x1; y1_square_exed_through = y2_square_exed_through = y2 = y1; XDefineCursor (d, outer_window, lower_right); while (TRUE) { XNextEvent (d, &event); switch (event.type) { case ButtonPress: result = TRUE; goto out2; case Expose: case ConfigureNotify: ProcessEvent (&event); break; case MotionNotify: case ButtonRelease: { int x, y; result = (event.xany.window != grid_window) || WhatSquare (&event, &x, &y) /* mouse outside grid? */ || (x < x1) || (y < y1); if (result) { ExThroughRectangle (x1+1, y1, x2, y2); ExThroughRectangle (x1, y1+1, x1, y2); x2 = x2_square_exed_through = x1; y2 = y2_square_exed_through = y1; } else if ((x == x2) && (y == y2)) ; /* both dimensions the same; do nothing */ else if ((x > x2) == (y > y2)) { /* both dimensions bigger or smaller */ ExThroughRectangle (min(x2,x)+1, y1, max(x2,x), max(y2,y)); ExThroughRectangle (x1, min(y2,y)+1, min(x2,x), max(y2,y)); x2 = x2_square_exed_through = x; y2 = y2_square_exed_through = y; } else { /* one dimension bigger, the other smaller */ ExThroughRectangle (min(x2,x)+1, y1, max(x2,x), min(y2,y)); ExThroughRectangle (x1, min(y2,y)+1, min(x2,x), max(y2,y)); x2 = x2_square_exed_through = x; y2 = y2_square_exed_through = y; } if (event.type == ButtonRelease) goto out2; break; } default: break; /* just throw it away */ } } out2: XSelectInput (d, outer_window, StructureNotifyMask); XDefineCursor (d, outer_window, cross); if (result) { /* no area properly selected; remove X-outs from display */ ExThroughRectangle (x1, y1, x2, y2); x1_square_exed_through = y1_square_exed_through = OUT_OF_RANGE; x2_square_exed_through = y2_square_exed_through = OUT_OF_RANGE; } else { *px1 = x1; *px2 = x2; *py1 = y1; *py2 = y2; } return (result); } boolean AskUserForDest (px1, py1, width, height) int *px1, *py1; int width, height; { XEvent event; boolean result; XSelectInput (d, outer_window, ButtonPressMask | ButtonReleaseMask | StructureNotifyMask); /* so we can detect button action outside grid */ XDefineCursor (d, outer_window, upper_left); while (TRUE) { XNextEvent (d, &event); switch (event.type) { case Expose: case ConfigureNotify: ProcessEvent (&event); break; case ButtonPress: case MotionNotify: { int x1_new, y1_new; boolean this_window = (event.xany.window == grid_window) && !WhatSquare (&event, &x1_new, &y1_new); if (this_window && (x1_new == *px1) && (y1_new == *py1)) break; /* mouse is still in same square as before; do nothing */ if (x1_square_plus_through != OUT_OF_RANGE) PlusThroughRectangle (x1_square_plus_through, y1_square_plus_through, x2_square_plus_through, y2_square_plus_through); if (this_window) { *px1 = x1_square_plus_through = x1_new; *py1 = y1_square_plus_through = y1_new; x2_square_plus_through = min (x1_new + width, image.width) - 1; y2_square_plus_through = min (y1_new + height, image.height) - 1; PlusThroughRectangle (x1_square_plus_through, y1_square_plus_through, x2_square_plus_through, y2_square_plus_through); } else { x1_square_plus_through = y1_square_plus_through = OUT_OF_RANGE; x2_square_plus_through = y2_square_plus_through = OUT_OF_RANGE; *px1 = *py1 = OUT_OF_RANGE; } break; } case ButtonRelease: { result = (event.xany.window != grid_window) || WhatSquare (&event, px1, py1); goto out; } default: break; /* throw it away */ } } out: if (result) { /* button released outside grid */ if (x1_square_plus_through != OUT_OF_RANGE) PlusThroughRectangle (x1_square_plus_through, y1_square_plus_through, x2_square_plus_through, y2_square_plus_through); x1_square_plus_through = y1_square_plus_through = OUT_OF_RANGE; x2_square_plus_through = y2_square_plus_through = OUT_OF_RANGE; } XSelectInput (d, outer_window, StructureNotifyMask); XDefineCursor (d, outer_window, cross); return (result); } boolean AskUserForPoint (xp, yp, plus) int *xp, *yp; { XEvent event; boolean this_window; XSelectInput (d, outer_window, ButtonPressMask | StructureNotifyMask); /* so we can detect button action outside grid */ XDefineCursor (d, outer_window, dot); while (TRUE) { XNextEvent (d, &event); switch (event.type) { case Expose: case ConfigureNotify: ProcessEvent (&event); break; case ButtonRelease: this_window = (event.xany.window == grid_window) && !WhatSquare (&event, xp, yp); if (this_window) { if (plus) { PlusThroughRectangle (*xp, *yp, *xp, *yp); x1_square_plus_through = x2_square_plus_through = *xp; y1_square_plus_through = y2_square_plus_through = *yp; } else { ExThroughRectangle (*xp, *yp, *xp, *yp); x1_square_exed_through = x2_square_exed_through = *xp; y1_square_exed_through = y2_square_exed_through = *yp; } } goto out; break; default: break; /* throw it away */ } } out: XSelectInput (d, outer_window, StructureNotifyMask); XDefineCursor (d, outer_window, cross); return (!this_window); } DialogInputHandler (event) XEvent *event; { if (event->type == Expose || event->type == ConfigureNotify) ProcessEvent (event); } enum output_error {e_rename, e_write}; /* WriteOutput returns TRUE if output successfully written, FALSE if not */ boolean WriteOutput() { FILE *file; if (!changed) return (TRUE); #ifndef VMS if (rename (filename, backup_filename) && errno != ENOENT) return (HandleOutputError(e_rename)); #endif file = fopen (filename, "w+"); if (!file) return (HandleOutputError(e_write)); WriteOutputToFile (file); fclose (file); changed = FALSE; return (TRUE); } /* HandleOutputError returns TRUE if alternate file written, FALSE if not */ int HandleOutputError(e) enum output_error e; { #ifndef VMS int result; static char *strings[] = {"Yes", "No"}; char msg1[120], msg2[120]; char *tmp_filename; if (e == e_rename) sprintf (msg1, "Can't rename %s to %s ", filename, backup_filename); else sprintf (msg1, "Can't write on file %s ", filename); tmp_filename = TmpFileName (filename); sprintf (msg2, "Should I write output to file %s?", tmp_filename); result = dialog (outer_window, font, msg1, msg2, strings, 2, DialogInputHandler); if (result == 0) /* "yes" */ { filename = tmp_filename; free (backup_filename); backup_filename = BackupName (filename); return (WriteOutput()); } else { /* "no" */ free (tmp_filename); return (FALSE); } #else perror("bitmap can't write file"); return (FALSE); #endif } void Quit() { if (changed) { int result; static char *strings[3] = {"Yes", "No", "Cancel"}; result = dialog (outer_window, font, "Save changes before quitting?", "", strings, 3, DialogInputHandler); switch (result) { case 0: /* "yes" */ if (WriteOutput()) exit(0); else return; case 1: /* "no" */ exit(0); default: /* "cancel" */ return; } } exit(0); } HighlightHotSpot() { /* Draw a diamond in the hot spot square */ /* x1 and y1 are the center of the hot spot square */ register int x1 = x_hot_spot*square_size + square_size/2; register int y1 = y_hot_spot*square_size + square_size/2; register int radius = square_size/6; XPoint points[5]; points[0].x = points[2].x = points[4].x = x1; points[1].x = x1 + radius; points[3].x = x1 - radius; points[0].y = points[4].y = y1 + radius; points[1].y = points[3].y = y1; points[2].y = y1 - radius; XSetLineAttributes (d, gc, 0, LineSolid, CapNotLast, JoinMiter); XSetState (d, gc, 1L, 0L, GXinvert, highlightplane); XDrawLines (d, grid_window, gc, points, 5, CoordModeOrigin); } ExThroughRectangle (x1, y1, x2, y2) register int x1, y1, x2, y2; { register int x, y; for (x=x1;x<=x2;x++) for (y=y1;y<=y2;y++) ExThroughSquare (x, y); FlushLineBuffer(); } ExThroughSquare (x, y) register int x, y; { register int x1 = x*square_size; register int y1 = y*square_size; LineIntoBuffer (x1+1, y1+1, x1+square_size, y1+square_size); LineIntoBuffer (x1+square_size-1, y1+1, x1, y1+square_size); } PlusThroughRectangle (x1, y1, x2, y2) register int x1, y1, x2, y2; { register int x, y; for (x=x1;x<=x2;x++) for (y=y1;y<=y2;y++) PlusThroughSquare (x, y); FlushLineBuffer(); } PlusThroughSquare (x, y) register int x, y; { register int x1 = x*square_size; register int y1 = y*square_size; LineIntoBuffer (x1+square_size/2, y1+1, x1+square_size/2, y1+square_size); LineIntoBuffer (x1+1, y1+square_size/2, x1+square_size, y1+square_size/2); } #define BUFFER_MAXLENGTH 100 static XSegment buffer [BUFFER_MAXLENGTH]; static int buffer_length = 0; LineIntoBuffer (x1, y1, x2, y2) { register XSegment *seg = &buffer[buffer_length]; seg->x1 = x1; seg->y1 = y1; seg->x2 = x2; seg->y2 = y2; if (++buffer_length == BUFFER_MAXLENGTH) FlushLineBuffer(); } FlushLineBuffer () { XSetLineAttributes (d, gc, 0, LineSolid, CapNotLast, JoinMiter); XSetState (d, gc, 1L, 0L, GXinvert, highlightplane); XDrawSegments (d, grid_window, gc, buffer, buffer_length); buffer_length = 0; }