1 /* Copyright (C) 1993, 1992 Nathan Sidwell */
2 /* RCS $Id: menubar.c,v 4.6 1994/01/06 10:20:24 nathan Stable $ */
3 /*{{{  includes*/
4 #include "xmred.h"
5 #include <X11/StringDefs.h>
6 #include <X11/Xaw/Label.h>
7 #include <X11/Xaw/Paned.h>
8 #include <X11/Xaw/MenuButton.h>
9 #include <X11/Xaw/SimpleMenu.h>
10 #include <X11/Xaw/SmeBSB.h>
11 /*}}}*/
12 /*{{{  defines*/
13 /*{{{  gizmos*/
14 #define GIZMO_FILE        0
15 #define GIZMO_FILE_MENU   1
16 #define GIZMO_FILE_BASE   2
17 #define GIZMO_FILE_CLEAR  2
18 #define GIZMO_FILE_LOAD   3
19 #define GIZMO_FILE_INSERT 4
20 #define GIZMO_FILE_SAVE   5
21 #define GIZMO_FILE_SAVEAS 6
22 #define GIZMO_FILE_QUIT   7
23 #define GIZMO_GARDEN      8
24 #define GIZMO_GARDEN_MENU 9
25 #define GIZMO_GARDEN_BASE 10
26 #define GIZMO_GARDEN_NEW  10
27 #define GIZMO_GARDEN_CLEAR  11
28 #define GIZMO_GARDEN_UNDO   12
29 #define GIZMO_GARDEN_REDO   13
30 #define GIZMO_INFO_FILE   14
31 #define GIZMO_INFO_TOTAL  15
32 #define GIZMO_INFO_GARDEN 16
33 /*}}}*/
34 /*}}}*/
35 /*{{{  prototypes*/
36 static VOIDFUNC copy_stack PROTOARG((unsigned));
37 static VOIDFUNC file_load PROTOARG((Widget, XtPointer, XtPointer));
38 static VOIDFUNC file_new PROTOARG((Widget, XtPointer, XtPointer));
39 static VOIDFUNC file_quit PROTOARG((Widget, XtPointer, XtPointer));
40 static VOIDFUNC file_save PROTOARG((Widget, XtPointer, XtPointer));
41 static VOIDFUNC garden_blank PROTOARG((BOARD *));
42 static VOIDFUNC garden_clear PROTOARG((Widget, XtPointer, XtPointer));
43 static VOIDFUNC garden_new PROTOARG((Widget, XtPointer, XtPointer));
44 static VOIDFUNC garden_redo PROTOARG((Widget, XtPointer, XtPointer));
45 static VOIDFUNC garden_undo PROTOARG((Widget, XtPointer, XtPointer));
46 static VOIDFUNC menu_filename PROTOARG((char CONST *));
47 static VOIDFUNC stack_sensitivity PROTOARG((VOIDARG));
48 /*}}}*/
49 /*{{{  statics*/
50 static char *filename;
51 static struct
52 {
53   BOARD     *stack[UNDO_DEPTH + 1];
54   unsigned  tos;
55   unsigned  offset;
56 } stack;
57 /*}}}*/
58 /*{{{  tables*/
59 /*{{{  static XtCallbackProc file_callbacks[] =*/
60 static XtCallbackProc file_callbacks[] =
61 {
62   file_new,
63   file_load, file_load,
64   file_save, file_save,
65   file_quit,
66 };
67 /*}}}*/
68 /*{{{  static XtCallbackProc garden_callbacks[] =*/
69 static XtCallbackProc garden_callbacks[] =
70 {
71   garden_new,
72   garden_clear,
73   garden_undo,
74   garden_redo,
75 };
76 /*}}}*/
77 /*{{{  static Arg arg_sensitive[] =*/
78 static Arg arg_sensitive[] =
79 {
80   {XtNsensitive},
81 };
82 /*}}}*/
83 /*{{{  static Arg arg_blank[] =*/
84 static Arg arg_blank[] =
85 {
86   {XtNlabel, (XtArgVal)""},
87 };
88 /*}}}*/
89 /*{{{  static GIZMO gizmos[] =*/
90 static GIZMO gizmos[] =
91 {
92   {"file", -1, &menuButtonWidgetClass},
93   {"menu", GIZMO_FILE, &simpleMenuWidgetClass},
94   {"clear", GIZMO_FILE_MENU, &smeBSBObjectClass},
95   {"load", GIZMO_FILE_MENU, &smeBSBObjectClass},
96   {"insert", GIZMO_FILE_MENU, &smeBSBObjectClass},
97   {"save", GIZMO_FILE_MENU, &smeBSBObjectClass},
98   {"saveas", GIZMO_FILE_MENU, &smeBSBObjectClass},
99   {"quit", GIZMO_FILE_MENU, &smeBSBObjectClass},
100   {"garden", -1, &menuButtonWidgetClass},
101   {"menu", GIZMO_GARDEN, &simpleMenuWidgetClass},
102   {"new", GIZMO_GARDEN_MENU, &smeBSBObjectClass},
103   {"clear", GIZMO_GARDEN_MENU, &smeBSBObjectClass},
104   {"undo", GIZMO_GARDEN_MENU, &smeBSBObjectClass},
105   {"redo", GIZMO_GARDEN_MENU, &smeBSBObjectClass},
106   {"filename", -1, &labelWidgetClass, arg_blank, XtNumber(arg_blank)},
107   {"total", -1, &labelWidgetClass, arg_blank, XtNumber(arg_blank)},
108   {"current", -1, &labelWidgetClass, arg_blank, XtNumber(arg_blank)},
109 };
110 /*}}}*/
111 /*}}}*/
112 /*{{{  void copy_stack(ix)*/
113 static VOIDFUNC copy_stack
114 FUNCARG((ix),
115 	unsigned  ix
116 )
117 /* copy stack offset to current garden
118  */
119 {
120   assert(state.edit);
121   memcpy(state.edit->board, stack.stack[ix], sizeof(BOARD));
122   state.edit->board->fill = state.fill;
123   state.edit->board->colors = state.color;
124   changed_flag = (changed_flag & ~state.change) | stack.stack[ix]->fill;
125   update_garden();
126   paint_garden_icon(state.edit);
127   repaint_garden_icon();
128   return;
129 }
130 /*}}}*/
131 /*{{{  void file_load(widget, client, call)*/
132 static VOIDFUNC file_load
133 FUNCARG((widget, client, call),
134 	Widget  widget
135 ARGSEP  XtPointer client
136 ARGSEP  XtPointer call
137 )
138 /* load and insert menu
139  * We check for unsaved changes, and then prompt and load
140  * a file. Continue, until success or abort
141  */
142 {
143   char CONST *title;
144   char CONST *result;
145   unsigned  option;
146   int       dialog;
147   char  CONST *error;
148   int       insert;
149 
150   if((unsigned)client == GIZMO_FILE_LOAD - GIZMO_FILE_BASE)
151     {
152       if(!check_saved(CHANGED_ANY))
153 	return;
154       insert = 0;
155     }
156   else
157     insert = 1;
158   XtVaGetValues(widget, XtNlabel, &title, NULL);
159   dialog = DIALOG_FILENAME;
160   error = NULL;
161   do
162     {
163       option = dialog_wait(dialog, title, error, filename, &result);
164       if(option & DIALOG_AGREE && result)
165 	{
166 	  dialog = DIALOG_FILE_ERROR;
167 	  title = "Cannot load";
168 	  menu_filename(result);
169 	  if((unsigned)client == GIZMO_FILE_LOAD - GIZMO_FILE_BASE)
170 	    free_descriptors();
171 	  error = load_boards(filename, insert);
172 	}
173       else
174 	break;
175     }
176   while(error);
177   if((unsigned)client == GIZMO_FILE_LOAD - GIZMO_FILE_BASE)
178     changed_flag = 0;
179   return;
180 }
181 /*}}}*/
182 /*{{{  void file_new(widget, client, call)*/
183 static VOIDFUNC file_new
184 FUNCARG((widget, client, call),
185 	Widget  widget
186 ARGSEP  XtPointer client
187 ARGSEP  XtPointer call
188 )
189 /* clear menu
190  * check for unsaved changes, and if ok, clear everything, and
191  * return to the default edit descriptors
192  */
193 {
194   if(check_saved(CHANGED_ANY | CHANGED_GARDEN))
195     {
196       free_descriptors();
197       new_descriptors();
198       changed_flag = 0;
199     }
200   return;
201 }
202 /*}}}*/
203 /*{{{  void file_quit(widget, client, call)*/
204 static VOIDFUNC file_quit
205 FUNCARG((widget, client, call),
206 	Widget  widget
207 ARGSEP  XtPointer client
208 ARGSEP  XtPointer call
209 )
210 /* quit menu, after checking for unsaved changes
211  */
212 {
213   if(check_saved(CHANGED_ANY))
214     exit(1);
215   return;
216 }
217 /*}}}*/
218 /*{{{  void file_save(widget, client, call)*/
219 static VOIDFUNC file_save
220 FUNCARG((widget, client, call),
221 	Widget  widget
222 ARGSEP  XtPointer client
223 ARGSEP  XtPointer call
224 )
225 /* save and saveas menus
226  * save the descriptors, and repeat until success or abort
227  * clears the appropriate changed flags
228  */
229 {
230   char CONST *title;
231   char CONST *result;
232   unsigned  option;
233   int       dialog;
234   char CONST *error;
235 
236   XtVaGetValues(widget, XtNlabel, &title, NULL);
237   dialog = filename && *filename && (unsigned)client ==
238       GIZMO_FILE_SAVE - GIZMO_FILE_BASE ? -1 : DIALOG_FILENAME;
239   error = NULL;
240   option = DIALOG_NONE;
241   result = NULL;
242   do
243   {
244     if(dialog >= 0)
245       option = dialog_wait(dialog, title, error, filename, &result);
246     if(dialog < 0 || (option & DIALOG_AGREE && result))
247       {
248 	char      *oresult;
249 
250 	oresult = NULL;
251 	while(result && dialog >= 0 &&
252 	    (oresult ? strcmp(result, oresult) :
253 	    !filename || strcmp(result, filename)) &&
254 	    option & DIALOG_AGREE && check_exists(result))
255 	  {
256 	    if(result)
257 	      free_dup(&oresult, result);
258 	    option = dialog_wait(DIALOG_FILENAME, "File exists", NULL,
259 		oresult, &result);
260 	  }
261 	XtFree(oresult);
262 	if(option & DIALOG_DISAGREE)
263 	  break;
264 	dialog = DIALOG_FILE_ERROR;
265 	title = "Cannot save";
266 	if(result)
267 	  menu_filename(result);
268 	error = save_boards(filename);
269       }
270     else
271       break;
272   }
273   while(error);
274   return;
275 }
276 /*}}}*/
277 /*{{{  void garden_blank(bptr)*/
278 static VOIDFUNC garden_blank
279 FUNCARG((bptr),
280 	BOARD     *bptr
281 )
282 /* sets garden to blank state
283  */
284 {
285   /*{{{  typedef struct Entry*/
286   typedef struct Entry
287   {
288     unsigned  offset;
289     char      c;
290   } ENTRY;
291   /*}}}*/
292   /*{{{  static ENTRY CONST table[] =*/
293   static ENTRY CONST table[] =
294   {
295     {4, GARDEN_PATH + 2},
296     {5, GARDEN_PATH + 2},
297     {6, GARDEN_PATH + 2},
298     {7, GARDEN_PATH},
299     {DEFAULT_DEN_X + DEFAULT_DEN_Y * (CELLS_ACROSS + 1),
300 	GARDEN_PATH + GARDEN_PATH_DEN},
301     {DEFAULT_PLAYER_X + DEFAULT_PLAYER_Y * (CELLS_ACROSS + 1),
302 	GARDEN_PATH + GARDEN_PATH_PLAYER},
303   };
304   /*}}}*/
305   unsigned  ix;
306   char      *cptr;
307   ENTRY CONST *tptr;
308 
309   for(ix = CELLS_DOWN, cptr = (char *)bptr->map; ix--;
310       cptr += CELLS_ACROSS + 1)
311     {
312       cptr[CELLS_ACROSS] = 0;
313       memset(cptr, ix ? GARDEN_RANDOM : GARDEN_NOAPPLE, CELLS_ACROSS);
314     }
315   bptr->apples = DEFAULT_APPLES;
316   for(tptr = table, ix = XtNumber(table); ix--; tptr++)
317     {
318       ((char *)bptr->map)[tptr->offset] = tptr->c;
319       if(tptr->offset > CELLS_ACROSS)
320 	((char *)bptr->map)[tptr->offset - CELLS_ACROSS - 1] = GARDEN_NOAPPLE;
321     }
322   return;
323 }
324 /*}}}*/
325 /*{{{  void garden_clear(widget, client, call)*/
326 static VOIDFUNC garden_clear
327 FUNCARG((widget, client, call),
328 	Widget  widget
329 ARGSEP  XtPointer client
330 ARGSEP  XtPointer call
331 )
332 /* clear the current edit garden
333  */
334 {
335   if(state.edit)
336     {
337       save_garden();
338       garden_blank(state.edit->board);
339       free_dup(&state.edit->comment, NULL);
340       update_garden();
341       paint_garden_icon(state.edit);
342       repaint_garden_icon();
343       changed_flag |= state.change;
344     }
345   return;
346 }
347 /*}}}*/
348 /*{{{  void garden_new(widget, client, call)*/
349 static VOIDFUNC garden_new
350 FUNCARG((widget, client, call),
351 	Widget  widget
352 ARGSEP  XtPointer client
353 ARGSEP  XtPointer call
354 )
355 /* creates a new blank edit garden
356  */
357 {
358   if(state.source != SOURCE_UNIQUE || check_saved(CHANGED_GARDEN))
359     {
360       DESCRIPTOR  *dptr;
361 
362       dptr = unique_garden();
363       garden_blank(dptr->board);
364       set_garden(dptr, SOURCE_UNIQUE, CHANGED_GARDEN);
365       paint_garden_icon(dptr);
366       repaint_garden_icon();
367     }
368   return;
369 }
370 /*}}}*/
371 /*{{{  void garden_redo(widget, client, call)*/
372 static VOIDFUNC garden_redo
373 FUNCARG((widget, client, call),
374 	Widget  widget
375 ARGSEP  XtPointer client
376 ARGSEP  XtPointer call
377 )
378 /* redoes the last undone change
379  */
380 {
381   if(stack.offset != stack.tos)
382     {
383       stack.offset++;
384       copy_stack(stack.offset);
385       stack_sensitivity();
386     }
387   return;
388 }
389 /*}}}*/
390 /*{{{  void garden_undo(widget, client, call)*/
391 static VOIDFUNC garden_undo
392 FUNCARG((widget, client, call),
393 	Widget  widget
394 ARGSEP  XtPointer client
395 ARGSEP  XtPointer call
396 )
397 /* undo change menu option
398  * rolls back the stack
399  */
400 {
401   if(stack.offset)
402     {
403       /*{{{  first undo?*/
404       if(stack.offset == stack.tos)
405 	{
406 	  BOARD   *bptr;
407 
408 	  if(!stack.stack[stack.offset])
409 	    stack.stack[stack.offset] = (BOARD *)XtMalloc(sizeof(BOARD));
410 	  bptr = stack.stack[stack.offset];
411 	  memcpy(bptr, state.edit->board, sizeof(BOARD));
412 	  bptr->fill = changed_flag & state.change;
413 	}
414       /*}}}*/
415       stack.offset--;
416       copy_stack(stack.offset);
417       stack_sensitivity();
418     }
419   return;
420 }
421 /*}}}*/
422 /*{{{  void menu_garden(string)*/
423 extern VOIDFUNC menu_garden
424 FUNCARG((string),
425 	char CONST *string
426 )
427 {
428   XtVaSetValues(gizmos[GIZMO_INFO_GARDEN].widget, XtNlabel, string, NULL);
429   return;
430 }
431 /*}}}*/
432 /*{{{  void menu_filename(name)*/
433 static VOIDFUNC menu_filename
434 FUNCARG((name),
435 	char CONST *name
436 )
437 {
438   XtVaSetValues(gizmos[GIZMO_INFO_FILE].widget, XtNlabel,
439       name ? name : "<no name>", NULL);
440   free_dup(&filename, name);
441   return;
442 }
443 /*}}}*/
444 /*{{{  void menu_total(total)*/
445 extern VOIDFUNC menu_total
446 FUNCARG((total),
447 	unsigned  total
448 )
449 {
450   static char string[5];
451 
452   itoa(string, total, 0);
453   XtVaSetValues(gizmos[GIZMO_INFO_TOTAL].widget,
454       XtNlabel, total ? string : "<None>", NULL);
455   arg_sensitive[0].value = (XtArgVal)(total ? True : False);
456   XtSetValues(gizmos[GIZMO_FILE_SAVE].widget,
457       arg_sensitive, XtNumber(arg_sensitive));
458   XtSetValues(gizmos[GIZMO_FILE_SAVEAS].widget,
459       arg_sensitive, XtNumber(arg_sensitive));
460   XtSetValues(gizmos[GIZMO_FILE_CLEAR].widget,
461       arg_sensitive, XtNumber(arg_sensitive));
462   return;
463 }
464 /*}}}*/
465 /*{{{  void install_menubar(root)*/
466 extern VOIDFUNC install_menubar
467 FUNCARG((root),
468 	Widget    root
469 )
470 /* install menu bar objects
471  */
472 {
473   unsigned  ix;
474 
475   create_gizmos(root, gizmos, XtNumber(gizmos));
476   for(ix = XtNumber(file_callbacks); ix--;)
477     XtAddCallback(gizmos[GIZMO_FILE_BASE + ix].widget,
478 	XtNcallback, file_callbacks[ix], (XtPointer)ix);
479   for(ix = XtNumber(garden_callbacks); ix--;)
480     XtAddCallback(gizmos[GIZMO_GARDEN_BASE + ix].widget,
481 	XtNcallback, garden_callbacks[ix], (XtPointer)ix);
482   menu_filename(NULL);
483   menu_total(0);
484   return;
485 }
486 /*}}}*/
487 /*{{{  void reset_garden_stack()*/
488 extern VOIDFUNC reset_garden_stack FUNCARGVOID
489 {
490   stack.tos = 0;
491   stack.offset = 0;
492   stack_sensitivity();
493   return;
494 }
495 /*}}}*/
496 /*{{{  void save_garden()*/
497 extern VOIDFUNC save_garden FUNCARGVOID
498 {
499   if(state.edit)
500     {
501       BOARD   *bptr;
502 
503       if(stack.offset == UNDO_DEPTH)
504 	/*{{{  rotate*/
505 	{
506 	  unsigned  ix;
507 
508 	  bptr = stack.stack[0];
509 	  for(ix = 0; ix != UNDO_DEPTH - 1; ix++)
510 	    stack.stack[ix] = stack.stack[ix + 1];
511 	  stack.stack[UNDO_DEPTH - 1] = bptr;
512 	}
513 	/*}}}*/
514       else
515 	/*{{{  push*/
516 	{
517 	  if(!stack.stack[stack.offset])
518 	    stack.stack[stack.offset] = (BOARD *)XtMalloc(sizeof(BOARD));
519 	  bptr = stack.stack[stack.offset];
520 	  stack.offset++;
521 	  stack.tos = stack.offset;
522 	}
523 	/*}}}*/
524       memcpy(bptr, state.edit->board, sizeof(BOARD));
525       bptr->fill = changed_flag & state.change;
526       stack_sensitivity();
527     }
528   return;
529 }
530 /*}}}*/
531 /*{{{  void stack_sensitivity()*/
532 static VOIDFUNC stack_sensitivity FUNCARGVOID
533 {
534   arg_sensitive[0].value = (XtArgVal)(stack.offset ? True : False);
535   XtSetValues(gizmos[GIZMO_GARDEN_UNDO].widget,
536       arg_sensitive, XtNumber(arg_sensitive));
537   arg_sensitive[0].value = (XtArgVal)(stack.offset != stack.tos ?
538       True : False);
539   XtSetValues(gizmos[GIZMO_GARDEN_REDO].widget,
540       arg_sensitive, XtNumber(arg_sensitive));
541   return;
542 }
543 /*}}}*/
544