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