1 /* Copyright (C) 1993, 1992 Nathan Sidwell */ 2 /* RCS $Id: all.c,v 4.18 1995/12/14 13:53:27 nathan Exp $ */ 3 /*{{{ includes*/ 4 #include "xmred.h" 5 #include <X11/StringDefs.h> 6 #include <X11/Xaw/Paned.h> 7 #include <X11/Xaw/Form.h> 8 #include <X11/Xaw/Scrollbar.h> 9 #include <sys/stat.h> 10 #include "Drag.h" 11 #include "Icon.h" 12 #include "PixmapList.h" 13 /*}}}*/ 14 /*{{{ defines*/ 15 /*{{{ board load error masks*/ 16 #define ERROR_FILL 0x0001 17 #define ERROR_BACKGROUND 0x0002 18 #define ERROR_APPLES 0x0004 19 #define ERROR_APPLE_POSITION 0x0008 20 #define ERROR_PATH 0x0010 21 #define ERROR_CHAR 0x0020 22 #define ERROR_STRING 0x0040 23 #define ERROR_INCONSISTANT 0x0080 24 #define ERROR_NO_CHERRIES 0x0100 25 #define ERROR_TOO_FEW_APPLES 0x0200 26 #define ERROR_NO_DEN 0x0400 27 #define ERROR_NO_PLAYER 0x0800 28 #define ERROR_BAD_APPLES 0x1000 29 #define ERROR_SYNTAX 0x2000 30 #define ERROR_EOF 0x4000 31 #define ERROR_READING 0x8000 32 #define ERROR_FIX_MASK 0x00FF 33 #define ERROR_FATAL_MASK 0xE000 34 #define ERROR_KEEP_MASK 0xC000 35 /*}}}*/ 36 /*{{{ gizmos*/ 37 #define GIZMO_DRAG_BASE 0 38 #define GIZMO_BUFFER_BOX 0 39 #define GIZMO_GARDENS_BOX 1 40 #define GIZMO_EDIT_BOX 2 41 #define GIZMO_EDIT_BASE 3 42 #define GIZMO_GARDEN 3 43 #define GIZMO_INCLUDE 4 44 #define GIZMO_COMMENT 5 45 #define GIZMO_GARDENS 6 46 #define GIZMO_SCROLL 7 47 #define GIZMO_BUFFER 8 48 #define GIZMO_DRAG 9 49 /*}}}*/ 50 /*}}}*/ 51 /*{{{ structs*/ 52 /*{{{ typedef struct List*/ 53 typedef struct List 54 { 55 DESCRIPTOR **list; /* pointers to all descriptors */ 56 unsigned descriptors; /* number of descriptors */ 57 unsigned limit; /* descriptor limit */ 58 } LIST; 59 /*}}}*/ 60 /*}}}*/ 61 /*{{{ board*/ 62 struct 63 { 64 LIST list; /* display list */ 65 LIST insert; /* insert list */ 66 DESCRIPTOR *edit[3]; /* edit descriptors */ 67 int source[3]; /* edit sources */ 68 DESCRIPTOR *buffer; /* buffer */ 69 } board; 70 /*}}}*/ 71 /*{{{ prototypes*/ 72 static VOIDFUNC all_buffer PROTOARG((Widget, XtPointer, XtPointer)); 73 static VOIDFUNC all_drag PROTOARG((Widget, XtPointer, XtPointer)); 74 static VOIDFUNC all_comment PROTOARG((Widget, XtPointer, XtPointer)); 75 static VOIDFUNC all_gardens PROTOARG((Widget, XtPointer, XtPointer)); 76 static VOIDFUNC all_include PROTOARG((Widget, XtPointer, XtPointer)); 77 static unsigned check_board PROTOARG((BOARD *)); 78 static char CONST *close_file PROTOARG((FILE *)); 79 static DESCRIPTOR *dup_descriptor PROTOARG((DESCRIPTOR CONST *)); 80 static unsigned edit_descriptor PROTOARG((int, unsigned)); 81 static VOIDFUNC free_descriptor PROTOARG((DESCRIPTOR *)); 82 static VOIDFUNC insert_descriptor PROTOARG((DESCRIPTOR *, int, unsigned)); 83 static DESCRIPTOR *malloc_descriptor PROTOARG((unsigned, char *, BOARD *)); 84 static VOIDFUNC refresh_garden_copy PROTOARG((VOIDARG)); 85 static DESCRIPTOR *remove_descriptor PROTOARG((int)); 86 static VOIDFUNC save_comment PROTOARG((FILE *, char CONST *, unsigned)); 87 static int strdiff PROTOARG((char CONST *, char CONST *)); 88 /*}}}*/ 89 /*{{{ tables*/ 90 /*{{{ static Arg arg_buffer[] =*/ 91 static Arg arg_buffer[] = 92 { 93 {XtNpixmap, (XtArgVal)0}, 94 }; 95 /*}}}*/ 96 /*{{{ static char CONST *initial_text =*/ 97 static char CONST *initial_text = 98 #if __STDC__ 99 "xmred " XMREDVERSION " " DATE "\n" 100 "Copyright 1993 Nathan Sidwell\n" 101 ; 102 #else 103 "xmred %s %s\nCopyright 1993 Nathan Sidwell\n"; 104 #endif /* __STDC__ */ 105 /*}}}*/ 106 /*{{{ static char CONST *text_error[] =*/ 107 static char CONST *text_error[] = 108 { 109 "Bad fill pattern", 110 "Bad background colour", 111 "Too many apples", 112 "Bad apple position", 113 "Required paths missing", 114 "Bad character", 115 "String wrong length", 116 "Inconistant paths", 117 "No cherries", 118 "Too few apple locations", 119 "No den", 120 "No player", 121 "Inconsistant apples", 122 "Syntax error", 123 "Unexpected EOF", 124 "Error reading file" 125 }; 126 /*}}}*/ 127 /*{{{ static XtCallbackProc edit_callbacks[] =*/ 128 static XtCallbackProc edit_callbacks[] = 129 { 130 NULL, 131 all_include, 132 all_comment, 133 }; 134 /*}}}*/ 135 /*{{{ static GIZMO gizmos[] =*/ 136 static GIZMO gizmos[] = 137 { 138 {"buffer", -1, &formWidgetClass}, 139 {"gardens", -1, &panedWidgetClass}, 140 {"edit", -1, &formWidgetClass}, 141 {"garden", GIZMO_EDIT_BOX, &iconWidgetClass}, 142 {"include", GIZMO_EDIT_BOX, &iconWidgetClass}, 143 {"comment", GIZMO_EDIT_BOX, &iconWidgetClass}, 144 {"gardens", GIZMO_GARDENS_BOX, &pixmapListWidgetClass}, 145 {"scrollbar", GIZMO_GARDENS_BOX, &scrollbarWidgetClass}, 146 {"buffer", GIZMO_BUFFER_BOX, &iconWidgetClass, 147 arg_buffer, XtNumber(arg_buffer)}, 148 {"drag", -1, &dragWidgetClass}, 149 }; 150 /*}}}*/ 151 /*}}}*/ 152 /*{{{ void all_buffer(widget, client, call)*/ 153 static VOIDFUNC all_buffer 154 FUNCARG((widget, client, call), 155 Widget widget 156 ARGSEP XtPointer client 157 ARGSEP XtPointer call 158 ) 159 /* callback on the garden buffer, 160 * copy the buffer contents to an edit buffer 161 */ 162 { 163 if(!board.insert.list) 164 edit_descriptor(SOURCE_BUFFER, 1); 165 return; 166 } 167 /*}}}*/ 168 /*{{{ void all_drag(widget, client, call)*/ 169 static VOIDFUNC all_drag 170 FUNCARG((widget, client, call), 171 Widget widget 172 ARGSEP XtPointer client 173 ARGSEP XtPointer call 174 ) 175 /* drag callback 176 * determine the source and destination of the drag 177 * and then do the appropriate thing. 178 * This generally means moving the descriptor around, 179 * except when the source is the buffer or edit, in 180 * which case a copy is done. 181 */ 182 { 183 DragCallback *dc; 184 int dst; 185 int src; 186 187 dc = (DragCallback *)call; 188 /*{{{ set dst*/ 189 if(dc->selected == gizmos[GIZMO_EDIT_BOX].widget) 190 dst = SOURCE_UNIQUE; 191 else if(dc->selected == gizmos[GIZMO_BUFFER_BOX].widget) 192 dst = SOURCE_BUFFER; 193 else 194 { 195 static Position x, y; 196 static Dimension width, height; 197 /*{{{ static Arg args[] =*/ 198 static Arg args[] = 199 { 200 {XtNx, (XtArgVal)&x}, 201 {XtNy, (XtArgVal)&y}, 202 {XtNwidth, (XtArgVal)&width}, 203 {XtNheight, (XtArgVal)&height}, 204 }; 205 /*}}}*/ 206 207 assert(dc->selected == gizmos[GIZMO_GARDENS_BOX].widget); 208 XtGetValues(gizmos[GIZMO_GARDENS].widget, args, XtNumber(args)); 209 dc->offset_x -= x; 210 dc->offset_y -= y; 211 dst = PixmapListQueryOffset(gizmos[GIZMO_GARDENS].widget, 212 dc->offset_x, dc->offset_y, True); 213 assert(dst >= 0); 214 } 215 /*}}}*/ 216 /*{{{ set src*/ 217 if(XtParent(dc->invoker) == gizmos[GIZMO_EDIT_BOX].widget) 218 src = SOURCE_UNIQUE; 219 else if(dc->invoker == gizmos[GIZMO_BUFFER].widget) 220 src = SOURCE_BUFFER; 221 else if(dc->invoker == gizmos[GIZMO_GARDENS].widget) 222 { 223 src = PixmapListQueryDrag(gizmos[GIZMO_GARDENS].widget); 224 if(src < 0) 225 return; 226 } 227 else 228 return; 229 /*}}}*/ 230 if(src == SOURCE_BUFFER && 231 (!board.buffer || board.buffer->type == DESCRIPTOR_NONE)) 232 return; 233 if(src == SOURCE_UNIQUE) 234 /*{{{ from edit*/ 235 { 236 int ix; 237 DESCRIPTOR *dptr; 238 239 if(dst == SOURCE_UNIQUE) 240 return; 241 for(ix = 0; ix != 3; ix++) 242 if(dc->invoker == gizmos[GIZMO_EDIT_BASE + ix].widget) 243 { 244 dptr = dup_descriptor(board.edit[ix]); 245 if(dst != SOURCE_BUFFER) 246 changed_flag &= ~(1 << ix); 247 insert_descriptor(dptr, dst, 1); 248 break; 249 } 250 } 251 /*}}}*/ 252 else if(src == SOURCE_BUFFER) 253 /*{{{ from buffer*/ 254 { 255 if(board.insert.list) 256 { 257 if(dst >= 0) 258 { 259 unsigned ix; 260 261 for(ix = board.insert.descriptors; ix--;) 262 { 263 insert_descriptor(board.insert.list[ix], dst, 0); 264 board.insert.list[ix] = NULL; 265 } 266 free_descriptor(remove_descriptor(SOURCE_BUFFER)); 267 } 268 } 269 else if(dst == SOURCE_UNIQUE) 270 edit_descriptor(SOURCE_BUFFER, 1); 271 else if(dst == SOURCE_BUFFER) 272 return; 273 else if(board.buffer) 274 { 275 DESCRIPTOR *dptr; 276 277 dptr = dup_descriptor(board.buffer); 278 insert_descriptor(dptr, dst, 279 board.source[dptr->type] == SOURCE_BUFFER); 280 } 281 } 282 /*}}}*/ 283 else 284 /*{{{ from gardens*/ 285 { 286 if(dst == SOURCE_UNIQUE) 287 edit_descriptor(src, 0); 288 else if(dst == SOURCE_BUFFER || (src + 1 != dst && src != dst)) 289 { 290 unsigned copy; 291 DESCRIPTOR *dptr; 292 293 if(dst == SOURCE_BUFFER) 294 { 295 for(copy = 0; copy != 3; copy++) 296 if(board.source[copy] == SOURCE_BUFFER) 297 break; 298 if(copy == 3 && !check_saved(CHANGED_BUFFER)) 299 return; 300 } 301 copy = board.source[board.list.list[src]->type] == src; 302 dptr = remove_descriptor(src); 303 if(dst > src) 304 dst--; 305 insert_descriptor(dptr, dst, copy); 306 } 307 } 308 /*}}}*/ 309 return; 310 } 311 /*}}}*/ 312 /*{{{ void all_comment(widget, client, call)*/ 313 static VOIDFUNC all_comment 314 FUNCARG((widget, client, call), 315 Widget widget 316 ARGSEP XtPointer client 317 ARGSEP XtPointer call 318 ) 319 /* callback on the comment edit. 320 * Popup the dialog and await response. 321 * Sets the appropriate changed flag 322 */ 323 { 324 char CONST *result; 325 unsigned option; 326 327 option = dialog_wait(DIALOG_COMMENT, "General Comment", NULL, 328 board.edit[DESCRIPTOR_COMMENT]->comment, &result); 329 if(option & DIALOG_AGREE && strdiff(result, 330 board.edit[DESCRIPTOR_COMMENT]->comment)) 331 { 332 if(board.source[DESCRIPTOR_COMMENT] >= 0) 333 { 334 assert(board.list.list[board.source[DESCRIPTOR_COMMENT]]->type == 335 DESCRIPTOR_COMMENT); 336 free_dup(&board.list.list[board.source[DESCRIPTOR_COMMENT]]->comment, 337 result); 338 changed_flag |= CHANGED_ALL; 339 } 340 else if(board.source[DESCRIPTOR_COMMENT] == SOURCE_BUFFER) 341 { 342 assert(board.buffer->type == DESCRIPTOR_COMMENT); 343 free_dup(&board.buffer->comment, result); 344 changed_flag |= CHANGED_BUFFER; 345 } 346 changed_flag |= CHANGED_COMMENT; 347 free_dup(&board.edit[DESCRIPTOR_COMMENT]->comment, result); 348 } 349 return; 350 } 351 /*}}}*/ 352 /*{{{ void all_gardens(widget, client, call)*/ 353 static VOIDFUNC all_gardens 354 FUNCARG((widget, client, call), 355 Widget widget 356 ARGSEP XtPointer client 357 ARGSEP XtPointer call 358 ) 359 /* callback on the gardens list. 360 * copy the selected garden to an edit buffer. 361 */ 362 { 363 edit_descriptor(((PixmapListCallback *)call)->selection, 1); 364 return; 365 } 366 /*}}}*/ 367 /*{{{ void all_garden_comment(dptr, change)*/ 368 extern VOIDFUNC all_garden_comment 369 FUNCARG((dptr, change), 370 DESCRIPTOR *dptr /* the garden descriptor */ 371 ARGSEP unsigned change /* change mask to apply */ 372 ) 373 /* do the garden comment for the control stuff. 374 * Popup the dialog and await response. 375 * Sets the appropriate changed flag 376 */ 377 { 378 char CONST *result; 379 unsigned option; 380 381 option = dialog_wait(DIALOG_COMMENT, "Garden Comment", NULL, 382 dptr->comment, &result); 383 if(option & DIALOG_AGREE && strdiff(result, dptr->comment)) 384 { 385 changed_flag |= change; 386 free_dup(&dptr->comment, result); 387 } 388 return; 389 } 390 /*}}}*/ 391 /*{{{ void all_include(widget, client, call)*/ 392 static VOIDFUNC all_include 393 FUNCARG((widget, client, call), 394 Widget widget 395 ARGSEP XtPointer client 396 ARGSEP XtPointer call 397 ) 398 /* callback on the include edit. 399 * Popup the dialog and await response. 400 * Sets the appropriate changed flag 401 */ 402 { 403 char CONST *result; 404 unsigned option; 405 406 option = dialog_wait(DIALOG_INCLUDE, "Include", NULL, 407 board.edit[DESCRIPTOR_INCLUDE]->comment, &result); 408 if(option & (DIALOG_AGREE | DIALOG_DEFAULT) && strdiff(result, 409 board.edit[DESCRIPTOR_INCLUDE]->comment)) 410 { 411 if(board.source[DESCRIPTOR_INCLUDE] >= 0) 412 { 413 assert(board.list.list[board.source[DESCRIPTOR_INCLUDE]]->type == 414 DESCRIPTOR_INCLUDE); 415 free_dup(&board.list.list[board.source[DESCRIPTOR_INCLUDE]]->comment, 416 result); 417 changed_flag |= CHANGED_ALL; 418 } 419 else if(board.source[DESCRIPTOR_COMMENT] == SOURCE_BUFFER) 420 { 421 assert(board.buffer->type == DESCRIPTOR_INCLUDE); 422 free_dup(&board.buffer->comment, result); 423 changed_flag |= CHANGED_BUFFER; 424 } 425 changed_flag |= CHANGED_INCLUDE; 426 free_dup(&board.edit[DESCRIPTOR_INCLUDE]->comment, result); 427 } 428 return; 429 } 430 /*}}}*/ 431 /*{{{ unsigned check_board(map)*/ 432 static unsigned check_board 433 FUNCARG((map), 434 BOARD *map 435 ) 436 /* checks a board is ok 437 * and fix if possible 438 * returns a mask of the errors 439 */ 440 { 441 unsigned error; 442 443 error = 0; 444 /*{{{ check characters*/ 445 { 446 char *cptr; 447 unsigned ix; 448 449 for(ix = CELLS_DOWN; ix--;) 450 for(cptr = map->map[ix]; *cptr; cptr++) 451 if(*cptr != GARDEN_RANDOM && *cptr != GARDEN_NOAPPLE && 452 *cptr != GARDEN_CHERRY && !ISPATH(*cptr) && !ISAPPLE(*cptr)) 453 { 454 error |= ERROR_CHAR; 455 *cptr = GARDEN_RANDOM; 456 } 457 } 458 /*}}}*/ 459 /*{{{ check fill*/ 460 if(map->fill >= FILLS) 461 { 462 error |= ERROR_FILL; 463 map->fill %= FILLS; 464 } 465 /*}}}*/ 466 /*{{{ check background*/ 467 if(map->colors >= BACKGROUNDS) 468 { 469 error |= ERROR_BACKGROUND; 470 map->colors %= BACKGROUNDS; 471 } 472 /*}}}*/ 473 /*{{{ check apples number*/ 474 if(map->apples > APPLE_LIMIT) 475 { 476 error |= ERROR_APPLES; 477 map->apples = APPLE_LIMIT; 478 } 479 /*}}}*/ 480 /*{{{ check apple positioning*/ 481 { 482 char *cptr; 483 484 for(cptr = map->map[CELLS_DOWN - 1]; *cptr; cptr++) 485 if(ISAPPLE(*cptr) || *cptr == GARDEN_RANDOM) 486 { 487 *cptr = GARDEN_NOAPPLE; 488 error |= ERROR_APPLE_POSITION; 489 } 490 } 491 /*}}}*/ 492 /*{{{ check required paths*/ 493 { 494 static CONST unsigned path[][2] = 495 { 496 {4, 0x2}, {5, 0x2}, {6, 0x2}, {7, 0x0}, 497 }; 498 unsigned ix; 499 500 for(ix = XtNumber(path); ix--;) 501 { 502 char *cptr; 503 unsigned c; 504 505 cptr = (char *)(map->map) + path[ix][0]; 506 c = *cptr; 507 if(!ISPATH(c)) 508 { 509 error |= ERROR_PATH; 510 *cptr = GARDEN_PATH + path[ix][1]; 511 } 512 else if((GARDENPATH(c) & path[ix][1]) != path[ix][1]) 513 { 514 error |= ERROR_PATH; 515 *cptr = (GARDENPATH(c) | path[ix][1]) + GARDEN_PATH; 516 } 517 } 518 } 519 /*}}}*/ 520 /*{{{ check integrity*/ 521 { 522 unsigned row; 523 char *cptr; 524 525 for(row = CELLS_DOWN; row--;) 526 for(cptr = map->map[row]; *cptr; cptr++) 527 /*{{{ check a cell*/ 528 { 529 char c; 530 531 c = *cptr; 532 if(ISPATH(c)) 533 { 534 unsigned paths; 535 536 paths = 0; 537 if(ISPATH(cptr[1])) 538 paths |= 2; 539 if(row != CELLS_DOWN - 1 && 540 ISPATH(cptr[CELLS_ACROSS + 1])) 541 paths |= 1; 542 if((GARDENPATH(c) & paths) != (GARDENPATH(c) & 3)) 543 { 544 error |= ERROR_INCONSISTANT; 545 *cptr = ((GARDENPATH(c) & 0xC) | paths) + GARDEN_PATH; 546 } 547 } 548 } 549 /*}}}*/ 550 } 551 /*}}}*/ 552 /*{{{ check totals*/ 553 { 554 char *cptr; 555 unsigned count; 556 unsigned totals[8]; 557 558 for(count = 8; count--;) 559 totals[count] = 0; 560 for(count = (CELLS_ACROSS + 1) * CELLS_DOWN, 561 cptr = (char *)map->map; count--; cptr++) 562 if(ISPATHCHERRY(*cptr) || *cptr == GARDEN_CHERRY) 563 totals[4]++; 564 else if(ISPATHDEN(*cptr)) 565 totals[5]++; 566 else if(ISPATHPLAYER(*cptr)) 567 totals[6]++; 568 else if(ISAPPLE(*cptr)) 569 { 570 unsigned value; 571 unsigned ix; 572 573 value = GARDENAPPLE(*cptr); 574 for(ix = 4; ix--; value >>= 1) 575 if(value & 1) 576 totals[ix]++; 577 totals[7]++; 578 } 579 else if(*cptr == GARDEN_RANDOM) 580 totals[7]++; 581 if(!totals[5]) 582 error |= ERROR_NO_DEN; 583 if(!totals[6]) 584 error |= ERROR_NO_PLAYER; 585 if(!totals[4]) 586 error |= ERROR_NO_CHERRIES; 587 if(totals[7] < map->apples) 588 error |= ERROR_TOO_FEW_APPLES; 589 for(count = 4; count--;) 590 if(totals[count] != map->apples) 591 error |= ERROR_BAD_APPLES; 592 } 593 /*}}}*/ 594 return error; 595 } 596 /*}}}*/ 597 /*{{{ unsigned check_exists(filename)*/ 598 extern unsigned check_exists 599 FUNCARG((filename), 600 char CONST *filename 601 ) 602 /* check if a filename exists 603 * returns 1 if found 604 */ 605 { 606 struct stat buffer; 607 608 assert(filename); 609 return !stat(filename, &buffer); 610 } 611 /*}}}*/ 612 /*{{{ unsigned check_saved(mask)*/ 613 extern unsigned check_saved 614 FUNCARG((mask), 615 unsigned mask /* changed mask to check */ 616 ) 617 /* check changes are saved, or that its ok to lose them 618 * pops up a dialog to query if changed. 619 * returns 1 if saved 620 */ 621 { 622 unsigned unsaved; 623 624 unsaved = changed_flag & mask; 625 if(unsaved) 626 unsaved = dialog_wait(DIALOG_NOT_SAVED, NULL, NULL, NULL, NULL) & 627 DIALOG_DISAGREE; 628 return !unsaved; 629 } 630 /*}}}*/ 631 /*{{{ char CONST *close_file(stream)*/ 632 static char CONST *close_file 633 FUNCARG((stream), 634 FILE *stream 635 ) 636 /* close the file and check error status 637 * returns the error string, or NULL 638 */ 639 { 640 #ifndef __FreeBSD__ 641 extern int sys_nerr; 642 extern char *sys_errlist[]; 643 #endif 644 645 int error; 646 647 if(!stream) 648 error = errno; 649 else if(ferror(stream)) 650 { 651 error = errno; 652 fclose(stream); 653 } 654 else if(fclose(stream)) 655 error = errno; 656 else 657 return NULL; 658 return error >= 0 && error < sys_nerr ? sys_errlist[error] : "Unknown error"; 659 } 660 /*}}}*/ 661 /*{{{ DESCRIPTOR *dup_descriptor(src)*/ 662 static DESCRIPTOR *dup_descriptor 663 FUNCARG((src), 664 DESCRIPTOR CONST *src 665 ) 666 /* duplicates a descriptor and its pixmap etc 667 */ 668 { 669 DESCRIPTOR *dptr; 670 671 dptr = malloc_descriptor(src->type, XtNewString(src->comment), 672 src->type == DESCRIPTOR_GARDEN ? 673 memcpy(XtMalloc(sizeof(BOARD)), src->board, sizeof(BOARD)): NULL); 674 if(src->type == DESCRIPTOR_GARDEN) 675 XCopyArea(display.display, src->pixmap, dptr->pixmap, GCN(GC_COPY), 676 0, 0, ICON_WIDTH, ICON_HEIGHT, 0, 0); 677 return dptr; 678 } 679 /*}}}*/ 680 /*{{{ unsigned edit_descriptor(src, dup)*/ 681 static unsigned edit_descriptor 682 FUNCARG((src, dup), 683 int src /* descriptor source */ 684 ARGSEP unsigned dup /* duplicate or remove source? */ 685 ) 686 /* edit the descriptor at src 687 * this creates a copy of the descriptor 688 * if !dup the source is removed 689 * otherwise the edit.source is set to src 690 * the previous edit descriptor is removed. 691 * A check is made if that has unsaved changes 692 * returns 1 if suceeded, 0 for abort du to unsaved changes 693 */ 694 { 695 DESCRIPTOR *dptr; 696 697 dptr = src < 0 ? board.buffer : board.list.list[src]; 698 if(!dptr || dptr->type == DESCRIPTOR_NONE || 699 (board.source[dptr->type] == SOURCE_UNIQUE && 700 !check_saved(1 << dptr->type))) 701 return 0; 702 if(dptr->type == DESCRIPTOR_GARDEN) 703 refresh_garden_copy(); 704 free_descriptor(board.edit[dptr->type]); 705 changed_flag &= ~(1 << dptr->type); 706 if(dup) 707 dptr = dup_descriptor(dptr); 708 board.edit[dptr->type] = dptr; 709 XtVaSetValues(gizmos[GIZMO_EDIT_BASE + dptr->type].widget, 710 XtNpixmap, (XtArgVal)board.edit[dptr->type]->pixmap, NULL); 711 if(dup) 712 board.source[dptr->type] = src; 713 else 714 { 715 board.source[dptr->type] = SOURCE_UNIQUE; 716 remove_descriptor(src); 717 } 718 if(dptr->type == DESCRIPTOR_GARDEN) 719 { 720 set_garden(dptr, board.source[DESCRIPTOR_GARDEN], 721 dup ? src == SOURCE_BUFFER ? CHANGED_BUFFER | CHANGED_GARDEN : 722 CHANGED_ALL | CHANGED_GARDEN : CHANGED_GARDEN); 723 if(dup) 724 changed_flag &= ~CHANGED_GARDEN; 725 else 726 changed_flag |= CHANGED_GARDEN; 727 } 728 return 1; 729 } 730 /*}}}*/ 731 /*{{{ void free_descriptor(dptr)*/ 732 static VOIDFUNC free_descriptor 733 FUNCARG((dptr), 734 DESCRIPTOR *dptr 735 ) 736 /* frees the descriptor */ 737 { 738 if(!dptr) 739 return; 740 XtFree(dptr->comment); 741 if(dptr->type == DESCRIPTOR_GARDEN) 742 { 743 XFreePixmap(display.display, dptr->pixmap); 744 XtFree((VOID *)dptr->board); 745 } 746 XtFree((VOID *)dptr); 747 return; 748 } 749 /*}}}*/ 750 /*{{{ void free_descriptors()*/ 751 extern VOIDFUNC free_descriptors FUNCARGVOID 752 /* remove and free all the display descriptors 753 */ 754 { 755 while(board.list.descriptors) 756 { 757 DESCRIPTOR *dptr; 758 759 dptr = remove_descriptor(board.list.descriptors - 1); 760 free_descriptor(dptr); 761 } 762 return; 763 } 764 /*}}}*/ 765 /*{{{ void free_dup(dptr, src)*/ 766 extern VOIDFUNC free_dup 767 FUNCARG((dptr, src), 768 char **dptr 769 ARGSEP char CONST *src 770 ) 771 /* copy a string, freeing the original destination value */ 772 { 773 XtFree(*dptr); 774 *dptr = XtNewString(src); 775 return; 776 } 777 /*}}}*/ 778 /*{{{ void insert_descriptor(dptr, place, copy)*/ 779 static VOIDFUNC insert_descriptor 780 FUNCARG((dptr, place, copy), 781 DESCRIPTOR *dptr /* descriptor */ 782 ARGSEP int place /* place to insert */ 783 ARGSEP unsigned copy /* is an edit copy */ 784 ) 785 /* insert a descriptor into the display list at the given place 786 * if the destination is the buffer, then the orignal buffer 787 * is emptied. 788 * Sets the edit source if copy != 0 789 */ 790 { 791 unsigned count; 792 793 if(place == SOURCE_BUFFER) 794 { 795 if(board.buffer) 796 free_descriptor(remove_descriptor(SOURCE_BUFFER)); 797 board.buffer = dptr; 798 XtVaSetValues(gizmos[GIZMO_BUFFER].widget, 799 XtNpixmap, dptr->pixmap, NULL); 800 changed_flag &= ~CHANGED_BUFFER; 801 } 802 else 803 { 804 DESCRIPTOR **ddptr; 805 806 if(place > board.list.descriptors) 807 place = board.list.descriptors; 808 board.list.descriptors++; 809 if(board.list.descriptors > board.list.limit) 810 { 811 board.list.limit += 128; 812 board.list.list = (DESCRIPTOR **)XtRealloc((VOID *)board.list.list, 813 board.list.limit * sizeof(DESCRIPTOR *)); 814 } 815 for(ddptr = &board.list.list[board.list.descriptors - 1], 816 count = board.list.descriptors - place - 1; count--; ddptr--) 817 ddptr[0] = ddptr[-1]; 818 assert(ddptr == &board.list.list[place]); 819 PixmapListInsert(gizmos[GIZMO_GARDENS].widget, place, 820 dptr->pixmap); 821 *ddptr = dptr; 822 for(count = 3; count--;) 823 if(board.source[count] >= place) 824 { 825 board.source[count]++; 826 if(count == DESCRIPTOR_GARDEN && 827 (!copy || dptr->type != DESCRIPTOR_GARDEN)) 828 { 829 set_garden_source(board.source[DESCRIPTOR_GARDEN], 830 CHANGED_GARDEN | CHANGED_ALL); 831 } 832 } 833 changed_flag |= CHANGED_ALL; 834 menu_total(board.list.descriptors); 835 } 836 if(copy) 837 { 838 board.source[dptr->type] = place; 839 if(place == SOURCE_BUFFER && changed_flag & (1 << dptr->type)) 840 changed_flag |= CHANGED_BUFFER; 841 if(dptr->type == DESCRIPTOR_GARDEN) 842 set_garden_source(place, place == SOURCE_BUFFER ? 843 CHANGED_GARDEN | CHANGED_BUFFER : CHANGED_GARDEN | CHANGED_ALL); 844 } 845 return; 846 } 847 /*}}}*/ 848 /*{{{ void install_all(root)*/ 849 extern VOIDFUNC install_all 850 FUNCARG((root), 851 Widget root 852 ) 853 /* creates the allbox widgets 854 * adds the callbacks 855 * initializes the garden list and edit boxes. 856 */ 857 { 858 GIZMO *gptr; 859 unsigned ix; 860 861 #if !__STDC__ 862 /*{{{ string build*/ 863 { 864 char *text; 865 866 text = XtMalloc(strlen(initial_text) + 867 strlen(XMREDVERSION) + strlen(DATE) + 1); 868 sprintf(text, initial_text, XMREDVERSION, DATE); 869 initial_text = text; 870 } 871 /*}}}*/ 872 #endif /* __STDC__ */ 873 arg_buffer[0].value = (XtArgVal)XCreatePixmap(display.display, display.copy, 874 ICON_WIDTH, ICON_HEIGHT, display.depth); 875 XFillRectangle(display.display, (Pixmap)arg_buffer[0].value, GCN(GC_CLEAR), 876 0, 0, ICON_WIDTH, ICON_HEIGHT); 877 create_gizmos(root, gizmos, XtNumber(gizmos)); 878 /*{{{ set drags into all*/ 879 { 880 static Widget list[3]; 881 882 for(ix = XtNumber(list); ix--;) 883 list[ix] = gizmos[GIZMO_DRAG_BASE + ix].widget; 884 XtVaSetValues(gizmos[GIZMO_DRAG].widget, 885 MredNwidgetChoices, (XtArgVal)list, 886 MredNnumWidgetChoices, (XtArgVal)XtNumber(list), NULL); 887 XtAddCallback(gizmos[GIZMO_DRAG].widget, XtNcallback, 888 all_drag, (XtPointer)NULL); 889 } 890 /*}}}*/ 891 /*{{{ add edit callbacks*/ 892 { 893 for(gptr = &gizmos[GIZMO_EDIT_BASE + XtNumber(edit_callbacks) - 1], 894 ix = XtNumber(edit_callbacks); ix--; gptr--) 895 if(edit_callbacks[ix]) 896 XtAddCallback(gptr->widget, XtNcallback, edit_callbacks[ix], 897 (XtPointer)NULL); 898 } 899 /*}}}*/ 900 XtAddCallback(gizmos[GIZMO_BUFFER].widget, XtNcallback, 901 all_buffer, (XtPointer)NULL); 902 XtAddCallback(gizmos[GIZMO_GARDENS].widget, XtNcallback, 903 all_gardens, (XtPointer)NULL); 904 PixmapListSetScroll(gizmos[GIZMO_GARDENS].widget, 905 gizmos[GIZMO_SCROLL].widget); 906 for(ix = 3; ix--;) 907 board.source[ix] = SOURCE_UNIQUE; 908 new_descriptors(); 909 return; 910 } 911 /*}}}*/ 912 /*{{{ char CONST *load_boards(filename, insert)*/ 913 extern char CONST *load_boards 914 FUNCARG((filename, insert), 915 char CONST *filename 916 ARGSEP unsigned insert /* insert flag */ 917 ) 918 /* load a file up. 919 * This is inserted or appended to the display list 920 * returns NULL or the error text. 921 */ 922 { 923 FILE *stream; 924 unsigned popped; 925 926 popped = 0; 927 stream = fopen(filename, "r"); 928 if(stream) 929 { 930 unsigned error_line; 931 unsigned line; 932 unsigned start; 933 unsigned error; 934 size_t limit; 935 char *text; 936 DESCRIPTOR *last; 937 938 error = error_line = 0; 939 line = start = 0; 940 limit = 80; 941 text = XtMalloc(limit); 942 last = NULL; 943 if(insert) 944 { 945 insert_descriptor(malloc_descriptor(DESCRIPTOR_INCLUDE, NULL, NULL), 946 SOURCE_BUFFER, 0); 947 board.insert.limit = 128; 948 board.insert.list = (DESCRIPTOR **)XtMalloc(board.insert.limit * 949 sizeof(DESCRIPTOR *)); 950 } 951 do 952 /*{{{ read a descriptor*/ 953 { 954 BOARD *bptr; 955 char *tptr; 956 size_t tlen; 957 unsigned type; 958 unsigned append; 959 960 append = 0; 961 bptr = NULL; 962 tptr = NULL; 963 tlen = 0; 964 type = DESCRIPTOR_NONE; 965 /*{{{ read descriptor*/ 966 { 967 /* state 0 new block 968 * state 1 comment 969 * state 2-4 numbers 970 * state 5 open brace 971 * state 6 close brace 972 * state 7 close brace 973 * state 10+ lines 974 */ 975 unsigned state; 976 977 state = 0; 978 start = line + 1; 979 do 980 { 981 char *ptr; 982 983 /*{{{ read line*/ 984 { 985 size_t length; 986 char *ptr; 987 char *lptr; 988 989 line++; 990 length = 0; 991 ptr = text; 992 /*{{{ read line*/ 993 while((lptr = fgets(ptr, limit - length, stream))) 994 { 995 ptr += strlen(ptr); 996 length = ptr - text; 997 if(length && *(ptr - 1) == '\n') 998 break; 999 if(length + 1 < limit) 1000 break; 1001 limit += 128; 1002 text = XtRealloc(text, limit); 1003 ptr = text + length; 1004 } 1005 /*}}}*/ 1006 /*{{{ error?*/ 1007 if(!lptr) 1008 { 1009 if(feof(stream)) 1010 *text = 0; 1011 else 1012 error |= ERROR_READING; 1013 } 1014 /*}}}*/ 1015 if(!error) 1016 error_line = line; 1017 else if(error & ERROR_READING) 1018 break; 1019 } 1020 /*}}}*/ 1021 ptr = text; 1022 if(!*ptr) 1023 { 1024 if(state) 1025 error |= ERROR_EOF; 1026 break; 1027 } 1028 while(*ptr == ' ' || *ptr == '\t') 1029 ptr++; 1030 if(*ptr == '\n') 1031 continue; 1032 /*{{{ new block*/ 1033 if(state == 0) 1034 { 1035 if(ptr[0] == '/' && ptr[1] == '*') 1036 { 1037 ptr += 2; 1038 state = 1; 1039 if(*ptr == '!') 1040 ptr++; 1041 else if(last) 1042 { 1043 tptr = last->comment; 1044 tlen = strlen(tptr); 1045 append = 1; 1046 } 1047 else 1048 type = DESCRIPTOR_COMMENT; 1049 } 1050 else if(!strncmp(ptr, "#include", 8)) 1051 /*{{{ include*/ 1052 { 1053 ptr += 8; 1054 while(*ptr == ' ' || *ptr == '\t') 1055 ptr++; 1056 if(*ptr == '\"') 1057 { 1058 char *name; 1059 1060 name = ++ptr; 1061 while(*ptr && *ptr != '\"') 1062 ptr++; 1063 *ptr = 0; 1064 free_dup(&tptr, name); 1065 type = DESCRIPTOR_INCLUDE; 1066 } 1067 else 1068 { 1069 error = ERROR_SYNTAX; 1070 break; 1071 } 1072 } 1073 /*}}}*/ 1074 else if(*ptr == '{') 1075 { 1076 last = NULL; 1077 ptr++; 1078 state = 2; 1079 bptr = (BOARD *)XtMalloc(sizeof(BOARD)); 1080 } 1081 else 1082 error = ERROR_SYNTAX; 1083 } 1084 /*}}}*/ 1085 /*{{{ in comment*/ 1086 if(state == 1) 1087 { 1088 char *start; 1089 char *end; 1090 1091 for(start = ptr; *start; start++) 1092 if(*start != ' ' && *start != '\t') 1093 break; 1094 ptr = start; 1095 while(*ptr && state) 1096 { 1097 while(*ptr && *ptr != '*') 1098 ptr++; 1099 if(*ptr) 1100 { 1101 if(ptr[1] == '/') 1102 { 1103 ptr += 2; 1104 state = 0; 1105 break; 1106 } 1107 else 1108 ptr++; 1109 } 1110 } 1111 end = state ? ptr : ptr - 2; 1112 while(end != start && end[-1] == ' ') 1113 end--; 1114 while(start != end && (*start == ' ' || *start == '*')) 1115 start++; 1116 if(end != start && *start != '\n') 1117 { 1118 size_t length; 1119 1120 *end = 0; 1121 length = end - start; 1122 tptr = (char *)XtRealloc((VOID *)tptr, 1123 tlen + length + 2); 1124 strcpy(&tptr[tlen], start); 1125 tlen += length; 1126 if(tptr[tlen - 1] != '\n') 1127 { 1128 tptr[tlen++] = '\n'; 1129 tptr[tlen] = 0; 1130 } 1131 } 1132 } 1133 /*}}}*/ 1134 /*{{{ reading board*/ 1135 if(state >= 2) 1136 { 1137 while(state) 1138 { 1139 if(*ptr == ' ' || *ptr == '\t') 1140 ptr++; 1141 if(*ptr == '\n') 1142 break; 1143 switch(state) 1144 { 1145 /*{{{ case 2: case 3: case 4:*/ 1146 case 2: case 3: case 4: 1147 { 1148 char *eptr; 1149 unsigned value; 1150 1151 value = strtol(ptr, &eptr, 0); 1152 if(eptr == ptr) 1153 { 1154 error = ERROR_SYNTAX; 1155 state = 7; 1156 } 1157 else 1158 { 1159 switch(state) 1160 { 1161 case 2: 1162 bptr->fill = value; 1163 /* CASCADE */ 1164 case 3: 1165 bptr->colors = value; 1166 /* CASCADE */ 1167 case 4: 1168 bptr->apples = value; 1169 /* CASCADE */ 1170 } 1171 ptr = eptr; 1172 state++; 1173 } 1174 break; 1175 } 1176 /*}}}*/ 1177 /*{{{ case 5:*/ 1178 case 5: 1179 if(*ptr == '{') 1180 { 1181 state = 10; 1182 ptr++; 1183 } 1184 else 1185 { 1186 state = 7; 1187 error = ERROR_SYNTAX; 1188 } 1189 break; 1190 /*}}}*/ 1191 /*{{{ case 6:*/ 1192 case 6: 1193 if(*ptr == '}') 1194 { 1195 ptr++; 1196 state = 7; 1197 } 1198 else if(*ptr == '"') 1199 { 1200 char *eptr; 1201 1202 eptr = strchr(ptr + 1, '\"'); 1203 if(eptr) 1204 ptr = eptr + 1; 1205 else 1206 { 1207 ptr++; 1208 error = ERROR_SYNTAX; 1209 } 1210 } 1211 else 1212 { 1213 ptr++; 1214 error = ERROR_SYNTAX; 1215 } 1216 break; 1217 /*}}}*/ 1218 /*{{{ case 7:*/ 1219 case 7: 1220 if(*ptr == '}') 1221 { 1222 state = 0; 1223 if(!(error & ERROR_FATAL_MASK)) 1224 type = DESCRIPTOR_GARDEN; 1225 ptr++; 1226 } 1227 else if(*ptr++ == '{') 1228 state = 6; 1229 else 1230 error = ERROR_SYNTAX; 1231 break; 1232 /*}}}*/ 1233 /*{{{ default:*/ 1234 default: 1235 { 1236 char *eptr; 1237 1238 assert(state >= 10 && state < 10 + CELLS_DOWN); 1239 eptr = strchr(ptr + 1, '\"'); 1240 if(*ptr != '\"' || !eptr) 1241 { 1242 error = ERROR_SYNTAX; 1243 state = 6; 1244 } 1245 else 1246 { 1247 *eptr = 0; 1248 if(eptr - ptr > CELLS_ACROSS + 1) 1249 { 1250 error = ERROR_STRING; 1251 ptr[1 + CELLS_ACROSS] = 0; 1252 } 1253 strcpy(bptr->map[state - 10], ptr + 1); 1254 if(eptr - ptr < CELLS_ACROSS + 1) 1255 { 1256 error = ERROR_STRING; 1257 memset(&bptr->map[state - 10] 1258 [eptr - ptr - 1], '.', 1259 CELLS_ACROSS + 1 - (eptr - ptr)); 1260 } 1261 bptr->map[state - 10][CELLS_ACROSS] = 0; 1262 state++; 1263 ptr = eptr + 1; 1264 if(state == 10 + CELLS_DOWN) 1265 state = 6; 1266 } 1267 break; 1268 } 1269 /*}}}*/ 1270 } 1271 if(*ptr == ',') 1272 ptr++; 1273 } 1274 } 1275 /*}}}*/ 1276 } 1277 while(state || (tptr && type == DESCRIPTOR_NONE && !append)); 1278 if(!error && !*text) 1279 break; 1280 } 1281 /*}}}*/ 1282 if(append) 1283 { 1284 last->comment = tptr; 1285 assert(type == DESCRIPTOR_NONE); 1286 } 1287 if(bptr) 1288 error |= check_board(bptr); 1289 /*{{{ store descriptor?*/ 1290 if(type != DESCRIPTOR_NONE) 1291 { 1292 DESCRIPTOR *dptr; 1293 1294 dptr = malloc_descriptor(type, tptr, bptr); 1295 if(type == DESCRIPTOR_GARDEN) 1296 paint_garden_icon(dptr); 1297 if(insert) 1298 { 1299 board.insert.descriptors++; 1300 if(board.insert.descriptors > board.insert.limit) 1301 { 1302 board.insert.limit += 128; 1303 board.insert.list = 1304 (DESCRIPTOR **)XtRealloc((VOID *)board.insert.list, 1305 board.insert.limit * sizeof(DESCRIPTOR *)); 1306 } 1307 board.insert.list[board.insert.descriptors - 1] = dptr; 1308 } 1309 else 1310 insert_descriptor(dptr, board.list.descriptors, 0); 1311 last = type == DESCRIPTOR_COMMENT ? dptr : NULL; 1312 } 1313 /*}}}*/ 1314 /*{{{ error?*/ 1315 if(error) 1316 { 1317 char string[80]; 1318 size_t length; 1319 unsigned ix; 1320 1321 sprintf(string, "Line %d", error_line); 1322 length = strlen(string); 1323 if(start != line) 1324 { 1325 sprintf(&string[length], " garden %d (lines %d-%d)", 1326 (insert ? board.insert.descriptors : 1327 board.list.descriptors) - 1, start, line); 1328 length += strlen(&string[length]); 1329 } 1330 string[length++] = '\n'; 1331 string[length] = 0; 1332 /*{{{ pop up error box?*/ 1333 if(!popped) 1334 { 1335 dialog_popup(DIALOG_LOAD_ERROR, NULL, "", NULL, XtGrabNone); 1336 popped = 1; 1337 } 1338 /*}}}*/ 1339 dialog_append(DIALOG_LOAD_ERROR, string); 1340 for(ix = 16; ix--;) 1341 if(error & 1 << ix) 1342 { 1343 sprintf(string, "%s%s\n", text_error[ix], 1344 (1 << ix) & ERROR_FIX_MASK ? " (fixed)" : ""); 1345 dialog_append(DIALOG_LOAD_ERROR, string); 1346 } 1347 error &= ERROR_KEEP_MASK; 1348 } 1349 /*}}}*/ 1350 } 1351 /*}}}*/ 1352 while(!error); 1353 XtFree(text); 1354 } 1355 return close_file(stream); 1356 } 1357 /*}}}*/ 1358 /*{{{ DESCRIPTOR *malloc_descriptor(type, text, board)*/ 1359 static DESCRIPTOR *malloc_descriptor 1360 FUNCARG((type, text, board), 1361 unsigned type /* descriptor type */ 1362 ARGSEP char *text /* comment text, or NULL */ 1363 ARGSEP BOARD *board /* board or NULL */ 1364 ) 1365 /* malloc a descriptor, initializing it appropriately 1366 * The comment text and board are not duplicated 1367 * The garden pixmap is not initialized, but is allocated 1368 */ 1369 { 1370 DESCRIPTOR *dptr; 1371 1372 dptr = (DESCRIPTOR *)XtMalloc(sizeof(DESCRIPTOR)); 1373 dptr->type = type; 1374 dptr->comment = text; 1375 switch(type) 1376 { 1377 case DESCRIPTOR_GARDEN: 1378 { 1379 dptr->pixmap = XCreatePixmap(display.display, display.copy, 1380 ICON_WIDTH, ICON_HEIGHT, display.depth); 1381 dptr->board = board; 1382 break; 1383 } 1384 case DESCRIPTOR_COMMENT: 1385 { 1386 dptr->pixmap = sprites[SPRITE_BOARD_I].image; 1387 break; 1388 } 1389 case DESCRIPTOR_INCLUDE: 1390 { 1391 dptr->pixmap = sprites[SPRITE_INCLUDE].image; 1392 break; 1393 } 1394 default: 1395 assert(0); 1396 } 1397 return dptr; 1398 } 1399 /*}}}*/ 1400 /*{{{ void new_descriptors()*/ 1401 extern VOIDFUNC new_descriptors FUNCARGVOID 1402 /* setup the default edit descriptors 1403 * clears the changed flag 1404 * clears the buffer 1405 */ 1406 { 1407 unsigned ix; 1408 1409 free_descriptor(remove_descriptor(SOURCE_BUFFER)); 1410 for(ix = 3; ix--;) 1411 { 1412 free_descriptor(board.edit[ix]); 1413 assert(board.source[ix] == SOURCE_UNIQUE); 1414 board.edit[ix] = malloc_descriptor(ix, ix == DESCRIPTOR_INCLUDE ? NULL : 1415 XtNewString(initial_text), ix != DESCRIPTOR_GARDEN ? NULL : 1416 memcpy(XtMalloc(sizeof(BOARD)), &initial_board[0], sizeof(BOARD))); 1417 if(ix == DESCRIPTOR_GARDEN) 1418 paint_garden_icon(board.edit[ix]); 1419 XtVaSetValues(gizmos[GIZMO_EDIT_BASE + ix].widget, 1420 XtNpixmap, (XtArgVal)board.edit[ix]->pixmap, NULL); 1421 } 1422 set_garden(board.edit[DESCRIPTOR_GARDEN], SOURCE_UNIQUE, 0); 1423 changed_flag = 0; 1424 return; 1425 } 1426 /*}}}*/ 1427 /*{{{ void refresh_garden_copy()*/ 1428 static VOIDFUNC refresh_garden_copy FUNCARGVOID 1429 /* update the edit garden's source from the edit garden 1430 */ 1431 { 1432 if(board.source[DESCRIPTOR_GARDEN] != SOURCE_UNIQUE) 1433 { 1434 DESCRIPTOR *dptr; 1435 1436 dptr = board.source[DESCRIPTOR_GARDEN] == SOURCE_BUFFER ? board.buffer : 1437 board.list.list[board.source[DESCRIPTOR_GARDEN]]; 1438 assert(dptr->type == DESCRIPTOR_GARDEN); 1439 memcpy(dptr->board, board.edit[DESCRIPTOR_GARDEN]->board, sizeof(BOARD)); 1440 free_dup(&dptr->comment, board.edit[DESCRIPTOR_GARDEN]->comment); 1441 } 1442 return; 1443 } 1444 /*}}}*/ 1445 /*{{{ DESCRIPTOR *remove_descriptor(place)*/ 1446 static DESCRIPTOR *remove_descriptor 1447 FUNCARG((place), 1448 int place /* descriptor to remove */ 1449 ) 1450 /* removes the specified descriptor 1451 * If this is the buffer, then the blank buffer is set 1452 * updates the edit sources as appropriate 1453 * returns the removed descriptor, this must be freed by the caller 1454 */ 1455 { 1456 DESCRIPTOR *dptr; 1457 DESCRIPTOR **ddptr; 1458 unsigned ix; 1459 1460 for(ix = 3; ix--;) 1461 if(board.source[ix] == place) 1462 { 1463 board.source[ix] = SOURCE_UNIQUE; 1464 if(ix == DESCRIPTOR_GARDEN) 1465 set_garden_source(SOURCE_UNIQUE, CHANGED_GARDEN); 1466 } 1467 if(place == SOURCE_BUFFER) 1468 { 1469 dptr = board.buffer; 1470 if(dptr) 1471 { 1472 XtSetValues(gizmos[GIZMO_BUFFER].widget, 1473 arg_buffer, XtNumber(arg_buffer)); 1474 board.buffer = NULL; 1475 changed_flag &= ~CHANGED_BUFFER; 1476 if(board.insert.list) 1477 { 1478 unsigned ix; 1479 1480 free_descriptor(dptr); 1481 dptr = NULL; 1482 for(ix = board.insert.descriptors; ix--;) 1483 free_descriptor(board.insert.list[ix]); 1484 XtFree((char *)board.insert.list); 1485 board.insert.list = NULL; 1486 board.insert.descriptors = 0; 1487 board.insert.limit = 0; 1488 } 1489 } 1490 } 1491 else 1492 { 1493 assert(place < board.list.descriptors); 1494 dptr = board.list.list[place]; 1495 board.list.descriptors--; 1496 for(ddptr = &board.list.list[place], ix = board.list.descriptors - place; 1497 ix--; ddptr++) 1498 ddptr[0] = ddptr[1]; 1499 for(ix = 3; ix--;) 1500 if(board.source[ix] > (int)place) 1501 { 1502 board.source[ix]--; 1503 if(ix == DESCRIPTOR_GARDEN) 1504 set_garden_source(board.source[ix], 1505 CHANGED_ALL | CHANGED_GARDEN); 1506 } 1507 PixmapListRemove(gizmos[GIZMO_GARDENS].widget, place); 1508 changed_flag |= CHANGED_ALL; 1509 menu_total(board.list.descriptors); 1510 } 1511 return dptr; 1512 } 1513 /*}}}*/ 1514 /*{{{ void repaint_garden_icon()*/ 1515 extern VOIDFUNC repaint_garden_icon FUNCARGVOID 1516 /* callback from the garden editor to repaint the edit icon, 1517 * and update the icon of its copy 1518 */ 1519 { 1520 DESCRIPTOR *dptr; 1521 1522 IconRepaint(gizmos[GIZMO_GARDEN].widget); 1523 dptr = board.source[DESCRIPTOR_GARDEN] == SOURCE_BUFFER ? board.buffer : 1524 board.source[DESCRIPTOR_GARDEN] >= 0 ? 1525 board.list.list[board.source[DESCRIPTOR_GARDEN]] : NULL; 1526 if(dptr) 1527 { 1528 XCopyArea(display.display, board.edit[DESCRIPTOR_GARDEN]->pixmap, 1529 dptr->pixmap, GCN(GC_COPY), 0, 0, ICON_WIDTH, ICON_HEIGHT, 0, 0); 1530 if(board.source[DESCRIPTOR_GARDEN] == SOURCE_BUFFER) 1531 IconRepaint(gizmos[GIZMO_BUFFER].widget); 1532 else 1533 PixmapListRepaint(gizmos[GIZMO_GARDENS].widget, 1534 board.source[DESCRIPTOR_GARDEN]); 1535 } 1536 return; 1537 } 1538 /*}}}*/ 1539 /*{{{ char CONST *save_boards(filename)*/ 1540 extern char CONST *save_boards 1541 FUNCARG((filename), 1542 char CONST *filename 1543 ) 1544 /* save all the descriptors 1545 * returns NULL or error message 1546 */ 1547 { 1548 char CONST *error; 1549 unsigned count; 1550 DESCRIPTOR **ddptr; 1551 FILE *stream; 1552 1553 refresh_garden_copy(); 1554 stream = fopen(filename, "w"); 1555 if(stream) 1556 for(ddptr = board.list.list, count = board.list.descriptors; 1557 count--; ddptr++) 1558 { 1559 DESCRIPTOR *dptr; 1560 1561 dptr = *ddptr; 1562 /*{{{ save a board;*/ 1563 switch(dptr->type) 1564 { 1565 /*{{{ case DESCRIPTOR_GARDEN:*/ 1566 case DESCRIPTOR_GARDEN: 1567 { 1568 BOARD *bptr; 1569 unsigned row; 1570 1571 bptr = dptr->board; 1572 if(dptr->comment) 1573 save_comment(stream, dptr->comment, 1); 1574 fprintf(stream, "{\n %d, %d, %d,\n {\n", 1575 bptr->fill, bptr->colors, bptr->apples); 1576 for(row = 0; row != CELLS_DOWN; row++) 1577 { 1578 fputs(" \"", stream); 1579 fputs(bptr->map[row], stream); 1580 fputs("\",\n", stream); 1581 } 1582 fputs(" }\n},\n", stream); 1583 break; 1584 } 1585 /*}}}*/ 1586 /*{{{ case DESCRIPTOR_INCLUDE:*/ 1587 case DESCRIPTOR_INCLUDE: 1588 fputs("#include \"", stream); 1589 if(dptr->comment) 1590 fputs(dptr->comment, stream); 1591 fputs("\"\n", stream); 1592 break; 1593 /*}}}*/ 1594 /*{{{ case DESCRIPTOR_COMMENT:*/ 1595 case DESCRIPTOR_COMMENT: 1596 if(dptr->comment) 1597 save_comment(stream, dptr->comment, 0); 1598 break; 1599 /*}}}*/ 1600 default: 1601 break; 1602 } 1603 /*}}}*/ 1604 } 1605 error = close_file(stream); 1606 if(!error) 1607 { 1608 changed_flag &= ~CHANGED_ALL; 1609 for(count = 3; count--;) 1610 if(board.source[count] >= 0) 1611 changed_flag &= ~(1 << count); 1612 } 1613 return error; 1614 } 1615 /*}}}*/ 1616 /*{{{ void save_comment(stream, comment, flag)*/ 1617 static VOIDFUNC save_comment 1618 FUNCARG((stream, comment, flag), 1619 FILE *stream 1620 ARGSEP char CONST *comment 1621 ARGSEP unsigned flag /* general or board comment */ 1622 ) 1623 /* save a comment 1624 */ 1625 { 1626 size_t length; 1627 char CONST *eptr; 1628 1629 fprintf(stream, "/*%c\n", " !"[flag]); 1630 for(eptr = comment; *eptr; comment = eptr) 1631 { 1632 eptr = strchr(comment, '\n'); 1633 if(eptr) 1634 length = eptr - comment; 1635 else 1636 { 1637 length = strlen(comment); 1638 eptr = comment + length; 1639 } 1640 fprintf(stream, " * %.*s\n", (int)length, comment); 1641 if(*eptr) 1642 eptr++; 1643 } 1644 fputs(" */\n", stream); 1645 return; 1646 } 1647 /*}}}*/ 1648 /*{{{ int strdiff(one, two)*/ 1649 static int strdiff 1650 FUNCARG((one, two), 1651 char CONST *one 1652 ARGSEP char CONST *two 1653 ) 1654 /* compares two possibly NULL strings 1655 */ 1656 { 1657 if(one == two) 1658 return 0; 1659 if(!one || !two) 1660 return 1; 1661 return strcmp(one, two); 1662 } 1663 /*}}}*/ 1664 /*{{{ DESCRIPTOR *unique_garden()*/ 1665 extern DESCRIPTOR *unique_garden FUNCARGVOID 1666 /* sets up for edit of a unique garden 1667 */ 1668 { 1669 refresh_garden_copy(); 1670 assert(board.edit[DESCRIPTOR_GARDEN]); 1671 changed_flag &= ~(1 << DESCRIPTOR_GARDEN); 1672 free_dup(&board.edit[DESCRIPTOR_GARDEN]->comment, NULL); 1673 board.source[DESCRIPTOR_GARDEN] = SOURCE_UNIQUE; 1674 return board.edit[DESCRIPTOR_GARDEN]; 1675 } 1676 /*}}}*/ 1677