1 /* Copyright (C) 1993, 1992 Nathan Sidwell */
2 /* RCS $Id: control.c,v 4.15 1993/12/10 11:52:23 nathan Stable $ */
3 /*{{{  includes*/
4 #include "xmred.h"
5 #include <X11/StringDefs.h>
6 #include "Icon.h"
7 #include "Drag.h"
8 #include <X11/Xaw/Scrollbar.h>
9 #include <X11/Xaw/Form.h>
10 #include <X11/Xaw/Label.h>
11 #include <math.h>
12 /*}}}*/
13 #define XtROption "Option"
14 /*{{{  defines*/
15 /*{{{  control gizmos*/
16 #define GIZMO_CONTROL          0
17 #define GIZMO_TOTAL            1
18 #define GIZMO_COMMENT          2
19 #define GIZMO_DISPLAY          3
20 #define GIZMO_DISPLAY_TITLE    4
21 #define GIZMO_DISPLAY_BASE     5
22 #define GIZMO_DISPLAY_COMBINED (5 + MODE_COMBINED)
23 #define GIZMO_DISPLAY_SEPARATE (5 + MODE_SEPARATE)
24 #define GIZMO_DISPLAY_RANDOM   (5 + MODE_RANDOM)
25 #define GIZMO_CONTROL_OPTIONS  8
26 #define GIZMO_CONTROL_FILLS    9
27 #define GIZMO_CONTROL_COLORS  10
28 #define GIZMO_CONTROL_BUTTONS 11
29 #define GIZMO_OPTION_LABEL    12
30 #define GIZMO_FILL_LABEL      13
31 #define GIZMO_COLOR_LABEL     14
32 #define GIZMO_BUTTON_LABEL    15
33 #define GIZMO_TOTAL_LABEL     16
34 #define GIZMO_TOTAL_BOXES     17
35 #define GIZMO_TOTAL_ICONS     27
36 #define GIZMO_TOTAL_COUNTS    37
37 #define GIZMO_TOTAL_WARNINGS  47
38 #define GIZMO_OPTION_DRAG     57
39 #define GIZMO_BUTTON_DRAG     58
40 #define GIZMO_OPTIONS         59
41 #define GIZMO_FILLS           (GIZMO_OPTIONS + OPTIONS)
42 #define GIZMO_COLORS          (GIZMO_FILLS + FILLS)
43 #define GIZMO_BUTTONS         (GIZMO_COLORS + BACKGROUNDS)
44 #define GIZMOS                (GIZMO_BUTTONS + BUTTONS)
45 /*}}}*/
46 #define VALUE_LOCK    256
47 /*}}}*/
48 /*{{{  structs*/
49 /*{{{  typedef struct Icon*/
50 typedef struct Icon
51 {
52   int       gizmo;    /* gizmo number */
53   COORD     size;     /* size of pixmap */
54   unsigned  sprite;   /* sprite source */
55   unsigned  fill;     /* background fill pattern */
56   unsigned  color;    /* background color */
57   Pixmap    pixmap;   /* displayed pixmap */
58 } ICON;
59 /*}}}*/
60 /*}}}*/
61 /*{{{  tables*/
62 /*{{{  static Arg arg_apple[] =*/
63 static Arg arg_apple[] =
64 {
65   {MredNcolumns, (XtArgVal)2},
66   {MredNrows, (XtArgVal)2},
67 };
68 /*}}}*/
69 /*{{{  static Arg arg_notapple[] =*/
70 static Arg arg_notapple[] =
71 {
72   {MredNcolumns, (XtArgVal)1},
73   {MredNrows, (XtArgVal)1},
74 };
75 /*}}}*/
76 /*{{{  static Arg arg_nohighlight[] =*/
77 static Arg arg_nohighlight[] =
78 {
79   {MredNhighlightThickness, (XtArgVal)0},
80 };
81 /*}}}*/
82 /*{{{  static Arg arg_nodrag[] =*/
83 static Arg arg_nodrag[] =
84 {
85   {MredNdragSensitivity, (XtArgVal)0},
86 };
87 /*}}}*/
88 /*{{{  static Arg arg_font[] =*/
89 static Arg arg_count[] =
90 {
91   {XtNfont, (XtArgVal)NULL},
92 };
93 /*}}}*/
94 /*{{{  static Arg arg_warning[] =*/
95 static Arg arg_warning[] =
96 {
97   {XtNbitmap, (XtArgVal)NULL},
98 };
99 /*}}}*/
100 /*{{{  static GIZMO gizmos[GIZMOS] =*/
101 static GIZMO gizmos[GIZMOS] =
102 {
103   {"controls", -1, &formWidgetClass},
104   {"totals", -1, &formWidgetClass},
105   {"comment", -1, &iconWidgetClass,
106       arg_nodrag, XtNumber(arg_nodrag)},
107   {"mode", -1, &formWidgetClass},
108   {"label", GIZMO_DISPLAY, &labelWidgetClass},
109   {"combined", GIZMO_DISPLAY, &iconWidgetClass,
110       arg_nodrag, XtNumber(arg_nodrag)},
111   {"separate", GIZMO_DISPLAY, &iconWidgetClass,
112       arg_nodrag, XtNumber(arg_nodrag)},
113   {"random", GIZMO_DISPLAY, &iconWidgetClass,
114       arg_nodrag, XtNumber(arg_nodrag)},
115   {"options", GIZMO_CONTROL, &formWidgetClass},
116   {"fills", GIZMO_CONTROL, &formWidgetClass},
117   {"colors", GIZMO_CONTROL, &formWidgetClass},
118   {"buttons", GIZMO_CONTROL, &formWidgetClass},
119   {"label", GIZMO_CONTROL_OPTIONS, &labelWidgetClass},
120   {"label", GIZMO_CONTROL_BUTTONS, &labelWidgetClass},
121   {"label", GIZMO_CONTROL_FILLS, &labelWidgetClass},
122   {"label", GIZMO_CONTROL_COLORS, &labelWidgetClass},
123   {"label", GIZMO_TOTAL, &labelWidgetClass},
124   {"total0", GIZMO_TOTAL, &formWidgetClass},
125   {"total1", GIZMO_TOTAL, &formWidgetClass},
126   {"total2", GIZMO_TOTAL, &formWidgetClass},
127   {"total3", GIZMO_TOTAL, &formWidgetClass},
128   {"total4", GIZMO_TOTAL, &formWidgetClass},
129   {"total5", GIZMO_TOTAL, &formWidgetClass},
130   {"total6", GIZMO_TOTAL, &formWidgetClass},
131   {"total7", GIZMO_TOTAL, &formWidgetClass},
132   {"total8", GIZMO_TOTAL, &formWidgetClass},
133   {"total9", GIZMO_TOTAL, &formWidgetClass},
134   {"icon", GIZMO_TOTAL_BOXES + 0, &iconWidgetClass,
135       arg_nohighlight, XtNumber(arg_nohighlight)},
136   {"icon", GIZMO_TOTAL_BOXES + 1, &iconWidgetClass,
137       arg_nohighlight, XtNumber(arg_nohighlight)},
138   {"icon", GIZMO_TOTAL_BOXES + 2, &iconWidgetClass,
139       arg_nohighlight, XtNumber(arg_nohighlight)},
140   {"icon", GIZMO_TOTAL_BOXES + 3, &iconWidgetClass,
141       arg_nohighlight, XtNumber(arg_nohighlight)},
142   {"icon", GIZMO_TOTAL_BOXES + 4, &iconWidgetClass,
143       arg_nohighlight, XtNumber(arg_nohighlight)},
144   {"icon", GIZMO_TOTAL_BOXES + 5, &iconWidgetClass,
145       arg_nohighlight, XtNumber(arg_nohighlight)},
146   {"icon", GIZMO_TOTAL_BOXES + 6, &iconWidgetClass,
147       arg_nohighlight, XtNumber(arg_nohighlight)},
148   {"icon", GIZMO_TOTAL_BOXES + 7, &iconWidgetClass,
149       arg_nohighlight, XtNumber(arg_nohighlight)},
150   {"icon", GIZMO_TOTAL_BOXES + 8, &iconWidgetClass,
151       arg_nohighlight, XtNumber(arg_nohighlight)},
152   {"icon", GIZMO_TOTAL_BOXES + 9, &iconWidgetClass,
153       arg_nohighlight, XtNumber(arg_nohighlight)},
154   {"count", GIZMO_TOTAL_BOXES + 0, &labelWidgetClass,
155       arg_count, XtNumber(arg_count)},
156   {"count", GIZMO_TOTAL_BOXES + 1, &labelWidgetClass,
157       arg_count, XtNumber(arg_count)},
158   {"count", GIZMO_TOTAL_BOXES + 2, &labelWidgetClass,
159       arg_count, XtNumber(arg_count)},
160   {"count", GIZMO_TOTAL_BOXES + 3, &labelWidgetClass,
161       arg_count, XtNumber(arg_count)},
162   {"count", GIZMO_TOTAL_BOXES + 4, &labelWidgetClass,
163       arg_count, XtNumber(arg_count)},
164   {"count", GIZMO_TOTAL_BOXES + 5, &labelWidgetClass,
165       arg_count, XtNumber(arg_count)},
166   {"count", GIZMO_TOTAL_BOXES + 6, &labelWidgetClass,
167       arg_count, XtNumber(arg_count)},
168   {"count", GIZMO_TOTAL_BOXES + 7, &labelWidgetClass,
169       arg_count, XtNumber(arg_count)},
170   {"count", GIZMO_TOTAL_BOXES + 8, &labelWidgetClass,
171       arg_count, XtNumber(arg_count)},
172   {"count", GIZMO_TOTAL_BOXES + 9, &labelWidgetClass,
173       arg_count, XtNumber(arg_count)},
174   {"warning", GIZMO_TOTAL_BOXES + 0, &labelWidgetClass,
175       arg_warning, XtNumber(arg_warning)},
176   {"warning", GIZMO_TOTAL_BOXES + 1, &labelWidgetClass,
177       arg_warning, XtNumber(arg_warning)},
178   {"warning", GIZMO_TOTAL_BOXES + 2, &labelWidgetClass,
179       arg_warning, XtNumber(arg_warning)},
180   {"warning", GIZMO_TOTAL_BOXES + 3, &labelWidgetClass,
181       arg_warning, XtNumber(arg_warning)},
182   {"warning", GIZMO_TOTAL_BOXES + 4, &labelWidgetClass,
183       arg_warning, XtNumber(arg_warning)},
184   {"scrollbar", GIZMO_TOTAL_BOXES + 5, &scrollbarWidgetClass},
185   {"warning", GIZMO_TOTAL_BOXES + 6, &labelWidgetClass,
186       arg_warning, XtNumber(arg_warning)},
187   {"warning", GIZMO_TOTAL_BOXES + 7, &labelWidgetClass,
188       arg_warning, XtNumber(arg_warning)},
189   {"warning", GIZMO_TOTAL_BOXES + 8, &labelWidgetClass,
190       arg_warning, XtNumber(arg_warning)},
191   {"warning", GIZMO_TOTAL_BOXES + 9, &labelWidgetClass,
192       arg_warning, XtNumber(arg_warning)},
193   {"drag", GIZMO_CONTROL_OPTIONS, &dragWidgetClass},
194   {"drag", GIZMO_CONTROL_BUTTONS, &dragWidgetClass},
195   {"apple", GIZMO_CONTROL_OPTIONS, &iconWidgetClass,
196       arg_apple, XtNumber(arg_apple)},
197   {"random", GIZMO_CONTROL_OPTIONS, &iconWidgetClass},
198   {"cherry", GIZMO_CONTROL_OPTIONS, &iconWidgetClass},
199   {"path", GIZMO_CONTROL_OPTIONS, &iconWidgetClass},
200   {"player", GIZMO_CONTROL_OPTIONS, &iconWidgetClass},
201   {"den", GIZMO_CONTROL_OPTIONS, &iconWidgetClass},
202   {"fill0", GIZMO_CONTROL_FILLS, &iconWidgetClass,
203       arg_nodrag, XtNumber(arg_nodrag)},
204   {"fill1", GIZMO_CONTROL_FILLS, &iconWidgetClass,
205       arg_nodrag, XtNumber(arg_nodrag)},
206   {"fill2", GIZMO_CONTROL_FILLS, &iconWidgetClass,
207       arg_nodrag, XtNumber(arg_nodrag)},
208   {"fill3", GIZMO_CONTROL_FILLS, &iconWidgetClass,
209       arg_nodrag, XtNumber(arg_nodrag)},
210   {"color0", GIZMO_CONTROL_COLORS, &iconWidgetClass,
211       arg_nodrag, XtNumber(arg_nodrag)},
212   {"color1", GIZMO_CONTROL_COLORS, &iconWidgetClass,
213       arg_nodrag, XtNumber(arg_nodrag)},
214   {"color2", GIZMO_CONTROL_COLORS, &iconWidgetClass,
215       arg_nodrag, XtNumber(arg_nodrag)},
216   {"button1", GIZMO_CONTROL_BUTTONS, &iconWidgetClass},
217   {"button2", GIZMO_CONTROL_BUTTONS, &iconWidgetClass},
218   {"button3", GIZMO_CONTROL_BUTTONS, &iconWidgetClass},
219   {"button4", GIZMO_CONTROL_BUTTONS, &iconWidgetClass},
220   {"button5", GIZMO_CONTROL_BUTTONS, &iconWidgetClass},
221 };
222 /*}}}*/
223 /*{{{  static ICON icons[] =*/
224 static ICON icons[] =
225 {
226   {GIZMO_DISPLAY_COMBINED, {CELL_WIDTH, CELL_HEIGHT}, SPRITE_APPLES + 4},
227   {GIZMO_DISPLAY_SEPARATE, {CELL_WIDTH, CELL_HEIGHT}, SPRITE_APPLES},
228   {GIZMO_DISPLAY_RANDOM, {CELL_WIDTH, CELL_HEIGHT}, SPRITE_BIG_APPLE + 1},
229   {GIZMO_COMMENT, {CELL_WIDTH, CELL_HEIGHT}, SPRITE_ICON_I},
230   {GIZMO_TOTAL_ICONS + COUNT_APPLES + 0,
231       {CELL_WIDTH, CELL_HEIGHT}, SPRITE_APPLES + 0},
232   {GIZMO_TOTAL_ICONS + COUNT_APPLES + 1,
233       {CELL_WIDTH, CELL_HEIGHT}, SPRITE_APPLES + 1},
234   {GIZMO_TOTAL_ICONS + COUNT_APPLES + 2,
235       {CELL_WIDTH, CELL_HEIGHT}, SPRITE_APPLES + 2},
236   {GIZMO_TOTAL_ICONS + COUNT_APPLES + 3,
237       {CELL_WIDTH, CELL_HEIGHT}, SPRITE_APPLES + 3},
238   {GIZMO_TOTAL_ICONS + COUNT_CHERRY, {CELL_WIDTH, CELL_HEIGHT}, SPRITE_CHERRY},
239   {GIZMO_TOTAL_ICONS + COUNT_RANDOM,
240       {CELL_WIDTH, CELL_HEIGHT}, SPRITE_BIG_APPLE},
241   {GIZMO_TOTAL_ICONS + COUNT_SPACES,
242       {CELL_WIDTH, CELL_HEIGHT}, SPRITE_BIG_APPLE + 1},
243   {GIZMO_TOTAL_ICONS + COUNT_FALL,
244       {CELL_WIDTH, CELL_HEIGHT}, SPRITE_APPLE_DROP},
245   {GIZMO_TOTAL_ICONS + COUNT_PLAYER, {CELL_WIDTH, CELL_HEIGHT}, SPRITE_PLAYER},
246   {GIZMO_TOTAL_ICONS + COUNT_DEN, {CELL_WIDTH, CELL_HEIGHT}, SPRITE_DEN},
247   {GIZMO_OPTIONS + OPTION_APPLES, {CELL_WIDTH, CELL_HEIGHT}, SPRITE_APPLES},
248   {GIZMO_OPTIONS + OPTION_RANDOM,
249       {CELL_WIDTH, CELL_HEIGHT}, SPRITE_BIG_APPLE + 1},
250   {GIZMO_OPTIONS + OPTION_CHERRY, {CELL_WIDTH, CELL_HEIGHT}, SPRITE_CHERRY},
251   {GIZMO_OPTIONS + OPTION_PATH, {CELL_WIDTH, CELL_HEIGHT}, SPRITE_PATH},
252   {GIZMO_OPTIONS + OPTION_PLAYER, {CELL_WIDTH, CELL_HEIGHT}, SPRITE_PLAYER},
253   {GIZMO_OPTIONS + OPTION_DEN, {CELL_WIDTH, CELL_HEIGHT}, SPRITE_DEN},
254   {GIZMO_FILLS + 0, {CELL_WIDTH, CELL_HEIGHT}, 0, VALUE_LOCK | 0},
255   {GIZMO_FILLS + 1, {CELL_WIDTH, CELL_HEIGHT}, 0, VALUE_LOCK | 1},
256   {GIZMO_FILLS + 2, {CELL_WIDTH, CELL_HEIGHT}, 0, VALUE_LOCK | 2},
257   {GIZMO_FILLS + 3, {CELL_WIDTH, CELL_HEIGHT}, 0, VALUE_LOCK | 3},
258   {GIZMO_COLORS + 0, {CELL_WIDTH, CELL_HEIGHT}, SPRITE_COLORS + 0,
259       0, VALUE_LOCK | 0},
260   {GIZMO_COLORS + 1, {CELL_WIDTH, CELL_HEIGHT}, SPRITE_COLORS + 1,
261       0, VALUE_LOCK | 1},
262   {GIZMO_COLORS + 2, {CELL_WIDTH, CELL_HEIGHT}, SPRITE_COLORS + 2,
263       0, VALUE_LOCK | 2},
264   {GIZMO_BUTTONS + 0, {CELL_WIDTH, CELL_HEIGHT}, 0},
265   {GIZMO_BUTTONS + 1, {CELL_WIDTH, CELL_HEIGHT}, 0},
266   {GIZMO_BUTTONS + 2, {CELL_WIDTH, CELL_HEIGHT}, 0},
267   {GIZMO_BUTTONS + 3, {CELL_WIDTH, CELL_HEIGHT}, 0},
268   {GIZMO_BUTTONS + 4, {CELL_WIDTH, CELL_HEIGHT}, 0},
269 };
270 /*}}}*/
271 static char count_text[COUNTS][4];
272 /*}}}*/
273 /*{{{  prototypes*/
274 static VOIDFUNC apple_jump PROTOARG((Widget, XtPointer, XtPointer));
275 static VOIDFUNC apple_scroll PROTOARG((Widget, XtPointer, XtPointer));
276 static VOIDFUNC apple_set_thumb PROTOARG((VOIDARG));
277 static VOIDFUNC bind_button PROTOARG((unsigned, int));
278 static VOIDFUNC command_button PROTOARG((Widget, XtPointer, XtPointer));
279 static VOIDFUNC command_color PROTOARG((Widget, XtPointer, XtPointer));
280 static VOIDFUNC command_fill PROTOARG((Widget, XtPointer, XtPointer));
281 static VOIDFUNC command_mode PROTOARG((Widget, XtPointer, XtPointer));
282 static VOIDFUNC command_option PROTOARG((Widget, XtPointer, XtPointer));
283 static Boolean convert_string2option PROTOARG((Display *, XrmValue *,
284     Cardinal *, XrmValue *, XrmValue *, XtPointer *));
285 static VOIDFUNC drag_button PROTOARG((Widget, XtPointer, XtPointer));
286 static VOIDFUNC drag_option PROTOARG((Widget, XtPointer, XtPointer));
287 static VOIDFUNC garden_comment PROTOARG((Widget, XtPointer, XtPointer));
288 static VOIDFUNC generate_icon PROTOARG((ICON *));
289 static ICON *gizmo2icon PROTOARG((GIZMO *));
290 static unsigned select_apple PROTOARG((unsigned));
291 static VOIDFUNC set_count_flag PROTOARG((unsigned, unsigned));
292 static VOIDFUNC set_icon_color PROTOARG((unsigned));
293 static VOIDFUNC set_icon_fill PROTOARG((unsigned));
294 /*}}}*/
295 /*{{{  void adjust_count(count, delta)*/
296 extern VOIDFUNC adjust_count
297 FUNCARG((count, delta),
298 	unsigned  count   /* which count to alter */
299 ARGSEP  int       delta   /* amount to alter it by */
300 )
301 /* alters a count and updates the display
302  * adds or removes the appropriate warning flag(s)
303  */
304 {
305   size_t    ix;
306 
307   assert(count < COUNTS && (delta >= 0 || state.counts[count] >= -delta));
308   state.counts[count] += delta;
309   for(ix = itoa(count_text[count], state.counts[count], 0); ix != 3; ix++)
310     count_text[count][ix] = ' ';
311   count_text[count][4] = 0;
312   XtVaSetValues(gizmos[GIZMO_TOTAL_COUNTS + count].widget,
313       XtNlabel, (XtArgVal)count_text[count], NULL);
314   if(INRANGE(count, COUNT_APPLES, 4))
315     set_count_flag(count, state.counts[count] != state.counts[COUNT_RANDOM]);
316   else if(count == COUNT_CHERRY)
317     set_count_flag(COUNT_CHERRY, !state.counts[COUNT_CHERRY]);
318   else if(count == COUNT_DEN)
319     set_count_flag(COUNT_DEN, !state.counts[COUNT_DEN]);
320   else if(count == COUNT_PLAYER)
321     set_count_flag(COUNT_PLAYER, state.counts[COUNT_PLAYER] != 1);
322   else if(count == COUNT_RANDOM)
323     {
324       unsigned  ix;
325 
326       if(state.edit && delta)
327 	{
328 	  state.edit->board->apples = state.counts[COUNT_RANDOM];
329 	  changed_flag |= state.change;
330 	}
331       for(ix = 4; ix--;)
332 	set_count_flag(COUNT_APPLES + ix,
333 	    state.counts[COUNT_APPLES + ix] != state.counts[COUNT_RANDOM]);
334       set_count_flag(COUNT_SPACES,
335 	  state.counts[COUNT_SPACES] < state.counts[COUNT_RANDOM]);
336     }
337   else if(count == COUNT_SPACES)
338     set_count_flag(COUNT_SPACES,
339 	state.counts[COUNT_SPACES] < state.counts[COUNT_RANDOM]);
340   return;
341 }
342 /*}}}*/
343 /*{{{  void apple_jump(widget, client, call)*/
344 static VOIDFUNC apple_jump
345 FUNCARG((widget, client, call),
346 	Widget  widget
347 ARGSEP  XtPointer client
348 ARGSEP  XtPointer call
349 )
350 /* jump scroll callback for setting number of apples to display
351  * Note this slider is upsidedown.
352  */
353 {
354   int     count;
355 
356   count = APPLE_LIMIT - (int)floor(*(float *)call * (float)APPLE_LIMIT);
357   if(count != state.counts[5])
358     adjust_count(5, count - state.counts[5]);
359   return;
360 }
361 /*}}}*/
362 /*{{{  void apple_scroll(widget, client, call)*/
363 static VOIDFUNC apple_scroll
364 FUNCARG((widget, client, call),
365 	Widget  widget
366 ARGSEP  XtPointer client
367 ARGSEP  XtPointer call
368 )
369 /* smooth scroll callback on apple set callback
370  * note that this slider is upsidedown to normal, so
371  * we get slider = 1.0 gives 0 apples and slider = 0.0 gives APPLE_LIMIT
372  */
373 {
374   int     delta;
375 
376   delta = (int)call ? (int)call < 0 ? -1 : 1 : 0;
377   if((delta >= 0 || state.counts[5]) && (delta <= 0 ||
378       state.counts[5] != APPLE_LIMIT))
379     adjust_count(5, delta);
380   apple_set_thumb();
381   return;
382 }
383 /*}}}*/
384 /*{{{  void apple_set_thumb()*/
385 static VOIDFUNC apple_set_thumb FUNCARGVOID
386 /* sets the apple selectionn scrollbar to the current
387  * count value
388  */
389 {
390   XawScrollbarSetThumb(gizmos[GIZMO_TOTAL_WARNINGS + 5].widget,
391       (float)(APPLE_LIMIT - state.counts[5]) / (float)APPLE_LIMIT, -1.0);
392   return;
393 }
394 /*}}}*/
395 /*{{{  void bind_button(button, option)*/
396 static VOIDFUNC bind_button
397 FUNCARG((button, option),
398 	unsigned  button    /* button to bind */
399 ARGSEP  int       option    /* option to bind */
400 )
401 /* binds an option to a button
402  */
403 {
404   GIZMO     *gptr;
405   ICON      *iptr;
406 
407   state.button[button] = option;
408   gptr = &gizmos[GIZMO_BUTTONS + button];
409   XtSetValues(gptr->widget, option == OPTION_APPLES ?
410       arg_apple : arg_notapple, option == OPTION_APPLES ?
411       XtNumber(arg_apple) : XtNumber(arg_notapple));
412   iptr = gizmo2icon(gptr);
413   iptr->sprite = option < 0 ? 0 :
414       gizmo2icon(&gizmos[GIZMO_OPTIONS + option])->sprite;
415   generate_icon(iptr);
416   IconRepaint(gptr->widget);
417   return;
418 }
419 /*}}}*/
420 /*{{{  void command_button(widget, client, call)*/
421 static VOIDFUNC command_button
422 FUNCARG((widget, client, call),
423 	Widget  widget
424 ARGSEP  XtPointer client
425 ARGSEP  XtPointer call
426 )
427 /* button press callback on a command button widget.
428  * Copy the selected widget's option to the pressed button's widget
429  */
430 {
431   IconCallback *call_data;
432   int       option;
433 
434   option = state.button[(unsigned)client];
435   call_data = (IconCallback *)call;
436   if(option == OPTION_APPLES)
437     select_apple(call_data->selection);
438   else if(call_data->button >= Button1 && call_data->button <= Button5)
439     bind_button(call_data->button - Button1, option);
440   return;
441 }
442 /*}}}*/
443 /*{{{  void command_color(widget, client, call)*/
444 static VOIDFUNC command_color
445 FUNCARG((widget, client, call),
446 	Widget  widget
447 ARGSEP  XtPointer client
448 ARGSEP  XtPointer call
449 )
450 /* color widget selection
451  * set the garden's color to the selected one.
452  * update all the other icon widgets
453  */
454 {
455   if(state.edit)
456     {
457       state.edit->board->colors = (unsigned)client;
458       paint_garden_icon(state.edit);
459       paint_garden_image();
460       repaint_garden_icon();
461       changed_flag |= state.change;
462     }
463   set_icon_color((unsigned)client);
464   return;
465 }
466 /*}}}*/
467 /*{{{  void command_fill(widget, client, call)*/
468 static VOIDFUNC command_fill
469 FUNCARG((widget, client, call),
470 	Widget  widget
471 ARGSEP  XtPointer client
472 ARGSEP  XtPointer call
473 )
474 /* fill widget selection
475  * set garden's fill pattern and update all the other iconn widgets
476  */
477 {
478   if(state.edit)
479     {
480       state.edit->board->fill = (unsigned)client;
481       paint_garden_icon(state.edit);
482       paint_garden_image();
483       repaint_garden_icon();
484       changed_flag |= state.change;
485     }
486   set_icon_fill((unsigned)client);
487   return;
488 }
489 /*}}}*/
490 /*{{{  void command_mode(widget, client, call)*/
491 static VOIDFUNC command_mode
492 FUNCARG((widget, client, call),
493 	Widget  widget
494 ARGSEP  XtPointer client
495 ARGSEP  XtPointer call
496 )
497 /* display mode set callback.
498  * change the display mode and update the garden
499  */
500 {
501   if(state.mode != (unsigned)client)
502     {
503       state.mode = (unsigned)client;
504       paint_garden_icon(state.edit);
505       paint_garden_image();
506       repaint_garden_icon();
507     }
508   return;
509 }
510 /*}}}*/
511 /*{{{  void command_option(widget, client, call)*/
512 static VOIDFUNC command_option
513 FUNCARG((widget, client, call),
514 	Widget  widget
515 ARGSEP  XtPointer client
516 ARGSEP  XtPointer call
517 )
518 /* option callback. If the selected option is the apples,
519  * set the current apple to the selected one.
520  * Otherwise, or if no current button has any apples
521  * set the pressed button's action to the selected option.
522  * Otherwise update the apple icons
523  */
524 {
525   IconCallback *call_data;
526   unsigned  option;
527 
528   option = (unsigned)client;
529   call_data = (IconCallback *)call;
530   if(option == OPTION_APPLES)
531     if(select_apple(call_data->selection))
532       return;
533   if(call_data->button >= Button1 && call_data->button <= Button5)
534     bind_button(call_data->button - Button1, option);
535   return;
536 }
537 /*}}}*/
538 /*{{{  Boolean convert_string2option(display, args, num_args, from, to, data)*/
539 static Boolean convert_string2option
540 /* ARGSUSED */
541 FUNCARG((display, args, num_args, from, to, data),
542 	Display   *display
543 ARGSEP  XrmValue  *args
544 ARGSEP  Cardinal  *num_args
545 ARGSEP  XrmValue  *from
546 ARGSEP  XrmValue  *to
547 ARGSEP  XtPointer *data
548 )
549 /*
550  * converts an option string to option number
551  */
552 {
553   static char CONST *options[] =
554     {"apple", "random", "cherry", "path", "player", "den", NULL};
555   char CONST **ptr;
556   static int result;
557 
558   if(to->size < sizeof(int))
559     {
560       to->size = sizeof(int);
561       return False;
562     }
563   for(ptr = options; *ptr; ptr++)
564     if(!strcmp(*ptr, (char CONST *)from->addr))
565       {
566 	to->size = sizeof(int);
567 	result = ptr - options;
568 	if(to->addr)
569 	   *(int *)to->addr = result;
570 	else
571 	  to->addr = (XtPointer)&result;
572 	return True;
573       }
574   XtDisplayStringConversionWarning(display, from->addr, XtROption);
575   return False;
576 }
577 /*}}}*/
578 /*{{{  void drag_button(widget, client, call)*/
579 static VOIDFUNC drag_button
580 FUNCARG((widget, client, call),
581 	Widget  widget
582 ARGSEP  XtPointer client
583 ARGSEP  XtPointer call
584 )
585 /* drag callback for button selection.
586  * Copy the initiating button's option to the selected button.
587  */
588 {
589   DragCallback *call_data;
590   int       option;
591   unsigned  ix;
592   GIZMO     *gptr;
593 
594   call_data = (DragCallback *)call;
595   for(gptr = &gizmos[GIZMO_BUTTONS + BUTTONS - 1], option = BUTTONS;
596       option--; gptr--)
597     if(gptr->widget == call_data->invoker)
598       {
599 	option = state.button[option];
600 	break;
601       }
602   for(gptr = &gizmos[GIZMO_BUTTONS + BUTTONS - 1], ix = BUTTONS;
603       ix--; gptr--)
604     if(gptr->widget == call_data->selected)
605       {
606 	bind_button(ix, option);
607 	break;
608       }
609   return;
610 }
611 /*}}}*/
612 /*{{{  void drag_option(widget, client, call)*/
613 static VOIDFUNC drag_option
614 FUNCARG((widget, client, call),
615 	Widget  widget
616 ARGSEP  XtPointer client
617 ARGSEP  XtPointer call
618 )
619 /* drag callback from option.
620  * set the selected button's action from the initiating option
621  */
622 {
623   DragCallback *call_data;
624   int       option;
625   unsigned  ix;
626   GIZMO     *gptr;
627 
628   call_data = (DragCallback *)call;
629   for(gptr = &gizmos[GIZMO_OPTIONS + OPTIONS - 1], option = OPTIONS;
630       option--; gptr--)
631     if(gptr->widget == call_data->invoker)
632       break;
633   for(gptr = &gizmos[GIZMO_BUTTONS + BUTTONS - 1], ix = BUTTONS;
634       ix--; gptr--)
635     if(gptr->widget == call_data->selected)
636       {
637 	bind_button(ix, option);
638 	break;
639       }
640   return;
641 }
642 /*}}}*/
643 /*{{{  void generate_icon(iptr)*/
644 static VOIDFUNC generate_icon
645 FUNCARG((iptr),
646 	ICON      *iptr
647 )
648 /* generate an icon pixmap.
649  * Copies its background an blats on its sprite
650  */
651 {
652   XGCValues gcv;
653 
654   gcv.fill_style = FillOpaqueStippled;
655   gcv.background = data.mono != False ? display.white :
656       colors[backgrounds[iptr->color & ~VALUE_LOCK][0]].pixel;
657   gcv.foreground = data.mono != False ? display.black :
658       colors[backgrounds[iptr->color & ~VALUE_LOCK][1]].pixel;
659   gcv.stipple = fills[iptr->fill & ~VALUE_LOCK].mask;
660   XChangeGC(display.display, GCN(GC_BOARD),
661       GCFillStyle | GCForeground | GCBackground | GCStipple, &gcv);
662   XFillRectangle(display.display, iptr->pixmap,
663       GCN(iptr->sprite == SPRITE_PLAYER || iptr->sprite == SPRITE_DEN ?
664 	GC_CLEAR : GC_BOARD), 0, 0, iptr->size.x, iptr->size.y);
665   if(iptr->sprite && sprites[iptr->sprite].image)
666     {
667       XCopyArea(display.display, sprites[iptr->sprite].mask, iptr->pixmap,
668 	  GCN(GC_MASK), 0, 0, iptr->size.x, iptr->size.y, 0, 0);
669       XCopyArea(display.display, sprites[iptr->sprite].image, iptr->pixmap,
670 	  GCN(GC_OR), 0, 0, iptr->size.x, iptr->size.y, 0, 0);
671     }
672   return;
673 }
674 /*}}}*/
675 /*{{{  void garden_comment(widget, client, call)*/
676 static VOIDFUNC garden_comment
677 FUNCARG((widget, client, call),
678 	Widget  widget
679 ARGSEP  XtPointer client
680 ARGSEP  XtPointer call
681 )
682 {
683   if(state.edit)
684     all_garden_comment(state.edit, state.change);
685   return;
686 }
687 /*}}}*/
688 /*{{{  ICON gizmo2icon(gptr)*/
689 static ICON *gizmo2icon
690 FUNCARG((gptr),
691 	GIZMO     *gptr
692 )
693 /* find the icon associated with a gizmo.
694  * returns NULL if none is
695  */
696 {
697   unsigned    ix;
698   ICON        *iptr;
699 
700   for(iptr = icons, ix = XtNumber(icons); ix--; iptr++)
701     if(iptr->gizmo == gptr - gizmos)
702       return iptr;
703   return NULL;
704 }
705 /*}}}*/
706 /*{{{  void install_control(root)*/
707 extern VOIDFUNC install_control
708 FUNCARG((root),
709 	Widget    root
710 )
711 /* create the widgets for the control panel
712  */
713 {
714   unsigned  ix;
715   GIZMO     *gptr;
716 
717   /*{{{  initialize arg_**/
718   {
719     XFontStruct *ptr;
720 
721     ptr = XQueryFont(display.display, data.font);
722     arg_count[0].value = (XtArgVal)ptr;
723     arg_warning[0].value = (XtArgVal)sprites[SPRITE_WARNING].image;
724   }
725   /*}}}*/
726   create_gizmos(root, gizmos, XtNumber(gizmos));
727   /*{{{  initialize pixmaps*/
728   {
729     ICON      *iptr;
730 
731     for(iptr = icons, ix = XtNumber(icons); ix--; iptr++)
732       {
733 	iptr->pixmap = XCreatePixmap(display.display, display.copy,
734 	    iptr->size.x, iptr->size.y, display.depth);
735 	generate_icon(iptr);
736 	XtVaSetValues(gizmos[iptr->gizmo].widget,
737 	    XtNpixmap, (XtArgVal)iptr->pixmap, NULL);
738       }
739   }
740   /*}}}*/
741   /*{{{  get additional resources*/
742   {
743     /*{{{  static XtResource button_resources[] =*/
744     static XtResource button_resources[] =
745     {
746       {"option", "Option", XtROption, sizeof(int),
747 	  0, XtRImmediate, (XtPointer)-1},
748     };
749     /*}}}*/
750     /*{{{  static XtResource index_resources[] =*/
751     static XtResource index_resources[] =
752     {
753       {"index", "Index", XtRInt, sizeof(int),
754 	  0, XtRImmediate, (XtPointer)0},
755     };
756     /*}}}*/
757     /*{{{  typedef struct _Option*/
758     typedef struct _Option
759     {
760       unsigned  gizmo;
761       unsigned  *dest;
762       unsigned  range;
763     } OPTION;
764     /*}}}*/
765     /*{{{  static OPTION options[] =*/
766     static CONST OPTION options[] =
767     {
768       {GIZMO_OPTIONS + 0, &initial_board[0].apples, 4},
769       {GIZMO_CONTROL_FILLS, &initial_board[0].fill, FILLS},
770       {GIZMO_CONTROL_COLORS, &initial_board[0].colors, BACKGROUNDS},
771       {GIZMO_DISPLAY, &state.mode, MODES},
772     };
773     /*}}}*/
774     OPTION CONST *optr;
775 
776     XtAppSetTypeConverter(display.context, XtRString, XtROption,
777 	convert_string2option, (XtConvertArgRec *)NULL, 0, XtCacheNone,
778 	(void (*)PROTOARG((XtAppContext, XrmValue *, XtPointer,
779 	    XrmValue *, Cardinal *)))NULL);
780     for(gptr = &gizmos[GIZMO_BUTTONS + BUTTONS - 1], ix = BUTTONS;
781 	ix--; gptr--)
782       {
783 	unsigned  binding;
784 
785 	XtGetApplicationResources(gptr->widget, (XtPointer)&binding,
786 	    button_resources, XtNumber(button_resources), NULL, 0);
787 	bind_button(ix, binding >= OPTIONS ? -1 : binding);
788       }
789     for(optr = options, ix = XtNumber(options); ix--; optr++)
790       {
791 	XtGetApplicationResources(gizmos[optr->gizmo].widget,
792 	    optr->dest, index_resources, XtNumber(index_resources), NULL, 0);
793 	*optr->dest %= optr->range;
794       }
795     gizmo2icon(&gizmos[GIZMO_OPTIONS])->sprite += state.apple;
796     for(ix = BUTTONS; ix--;)
797       if(state.button[ix] >= 0)
798 	gizmo2icon(&gizmos[GIZMO_BUTTONS + ix])->sprite =
799 	    gizmo2icon(&gizmos[GIZMO_OPTIONS + state.button[ix]])->sprite;
800   }
801   /*}}}*/
802   /*{{{  set option callback*/
803   for(gptr = &gizmos[GIZMO_OPTIONS + OPTIONS - 1], ix = OPTIONS;
804       ix--; gptr--)
805     XtAddCallback(gptr->widget, XtNcallback, command_option, (XtPointer)ix);
806   /*}}}*/
807   /*{{{  set fill callback*/
808   for(gptr = &gizmos[GIZMO_FILLS + FILLS - 1], ix = FILLS; ix--; gptr--)
809     XtAddCallback(gptr->widget, XtNcallback, command_fill, (XtPointer)ix);
810   /*}}}*/
811   /*{{{  set color callback*/
812   for(gptr = &gizmos[GIZMO_COLORS + BACKGROUNDS - 1], ix = BACKGROUNDS;
813       ix--; gptr--)
814     XtAddCallback(gptr->widget, XtNcallback, command_color, (XtPointer)ix);
815   /*}}}*/
816   /*{{{  set mode callback*/
817   for(gptr = &gizmos[GIZMO_DISPLAY_BASE + MODES - 1], ix = MODES;
818       ix--; gptr--)
819     XtAddCallback(gptr->widget, XtNcallback, command_mode, (XtPointer)ix);
820   /*}}}*/
821   /*{{{  set button callback*/
822   for(gptr = &gizmos[GIZMO_BUTTONS + BUTTONS - 1], ix = BUTTONS;
823       ix--; gptr--)
824     XtAddCallback(gptr->widget, XtNcallback, command_button, (XtPointer)ix);
825   /*}}}*/
826   /*{{{  setup apple scrollbar*/
827   {
828     float     shown;
829     Arg       arg[1];
830 
831     gptr = &gizmos[GIZMO_TOTAL_WARNINGS + 5];
832     shown = (float)1.0;
833     XtSetArg(arg[0], XtNshown, sizeof(float) > sizeof(XtArgVal) ?
834 	(XtArgVal)&shown : *(XtArgVal *)&shown);
835     XtSetValues(gptr->widget, arg, 1);
836     XtAddCallback(gptr->widget, XtNjumpProc, apple_jump, (XtPointer)NULL);
837     XtAddCallback(gptr->widget, XtNscrollProc, apple_scroll, (XtPointer)NULL);
838   }
839   /*}}}*/
840   /*{{{  set drags into buttons*/
841   {
842     static Widget list[5];
843 
844     for(ix = XtNumber(list); ix--;)
845       list[ix] = gizmos[GIZMO_BUTTONS + ix].widget;
846     XtVaSetValues(gizmos[GIZMO_OPTION_DRAG].widget,
847 	MredNwidgetChoices, (XtArgVal)list,
848 	MredNnumWidgetChoices, (XtArgVal)XtNumber(list), NULL);
849     XtVaSetValues(gizmos[GIZMO_BUTTON_DRAG].widget,
850 	MredNwidgetChoices, (XtArgVal)list,
851 	MredNnumWidgetChoices, (XtArgVal)XtNumber(list), NULL);
852     XtAddCallback(gizmos[GIZMO_BUTTON_DRAG].widget, XtNcallback,
853 	drag_button, (XtPointer)NULL);
854     XtAddCallback(gizmos[GIZMO_OPTION_DRAG].widget, XtNcallback,
855 	drag_option, (XtPointer)NULL);
856   }
857   /*}}}*/
858   XtAddCallback(gizmos[GIZMO_COMMENT].widget, XtNcallback,
859       garden_comment, (XtPointer)NULL);
860   return;
861 }
862 /*}}}*/
863 /*{{{  unsigned select_apple(apple)*/
864 static unsigned select_apple
865 FUNCARG((apple),
866 	unsigned  apple
867 )
868 /* sets explicit apple and ipdates buttons bound to it
869  * returns 1 if at least one button bound
870  */
871 {
872   GIZMO     *gptr;
873   ICON      *iptr;
874   unsigned  ix;
875   unsigned  found;
876   unsigned  sprite;
877 
878   sprite = SPRITE_APPLES + apple;
879   if(state.apple != apple)
880     {
881       state.apple = apple;
882       gptr = &gizmos[GIZMO_OPTIONS];
883       iptr = gizmo2icon(gptr);
884       iptr->sprite = sprite;
885       generate_icon(iptr);
886       IconRepaint(gptr->widget);
887       gptr = &gizmos[GIZMO_DISPLAY_SEPARATE];
888       iptr = gizmo2icon(gptr);
889       iptr->sprite = sprite;
890       generate_icon(iptr);
891       IconRepaint(gptr->widget);
892       if(state.mode == MODE_SEPARATE)
893 	paint_garden_image();
894     }
895   found = 0;
896   for(ix = BUTTONS; ix--;)
897     if(!state.button[ix])
898       {
899 	gptr = &gizmos[GIZMO_BUTTONS + ix];
900 	iptr = gizmo2icon(gptr);
901 	iptr->sprite = sprite;
902 	generate_icon(iptr);
903 	IconRepaint(gptr->widget);
904 	found = 1;
905       }
906   return found;
907 }
908 /*}}}*/
909 /*{{{  void set_count_flag(count, flag)*/
910 static VOIDFUNC set_count_flag
911 FUNCARG((count, flag),
912 	unsigned  count
913 ARGSEP  unsigned  flag
914 )
915 {
916   static unsigned state;
917 
918   if(flag != !!(state & (1 << count)))
919     /*{{{  change flag*/
920     {
921       state ^= 1 << count;
922       XtVaSetValues(gizmos[GIZMO_TOTAL_WARNINGS + count].widget,
923 	  XtNbitmap, sprites[SPRITE_WARNING + flag].image, NULL);
924     }
925     /*}}}*/
926   return;
927 }
928 /*}}}*/
929 /*{{{  void set_icon_color(color)*/
930 static VOIDFUNC set_icon_color
931 FUNCARG((color),
932 	unsigned  color
933 )
934 {
935   ICON      *iptr;
936   unsigned  ix;
937 
938   state.color = color;
939   for(iptr = icons, ix = XtNumber(icons); ix--; iptr++)
940     if(!(iptr->color & VALUE_LOCK))
941       {
942 	iptr->color = color;
943 	generate_icon(iptr);
944 	IconRepaint(gizmos[iptr->gizmo].widget);
945       }
946   return;
947 }
948 /*}}}*/
949 /*{{{  void set_icon_fill(fill)*/
950 static VOIDFUNC set_icon_fill
951 FUNCARG((fill),
952 	unsigned  fill
953 )
954 {
955   ICON      *iptr;
956   unsigned  ix;
957 
958   state.fill = fill;
959   for(iptr = icons, ix = XtNumber(icons); ix--; iptr++)
960     if(!(iptr->fill & VALUE_LOCK))
961       {
962 	iptr->fill = fill;
963 	generate_icon(iptr);
964 	IconRepaint(gizmos[iptr->gizmo].widget);
965       }
966   return;
967 }
968 /*}}}*/
969 /*{{{  void set_garden(dptr, source, change)*/
970 extern VOIDFUNC set_garden
971 FUNCARG((dptr, source, change),
972 	DESCRIPTOR *dptr    /* garden descriptor to edit */
973 ARGSEP  int       source    /* source value */
974 ARGSEP  unsigned  change    /* change mask */
975 )
976 /* set up for a new garden to edit
977  */
978 {
979   BOARD     *bptr;
980 
981   assert(dptr);
982   state.edit = dptr;
983   state.source = source;
984   state.change = change;
985   bptr = dptr->board;
986   assert(bptr);
987   set_icon_fill(bptr->fill);
988   set_icon_color(bptr->colors);
989   update_garden();
990   reset_garden_stack();
991   return;
992 }
993 /*}}}*/
994 /*{{{  void set_garden_source(source, change)*/
995 extern VOIDFUNC set_garden_source
996 FUNCARG((source, change),
997 	int       source
998 ARGSEP  unsigned  change
999 )
1000 {
1001   state.source = source;
1002   state.change = change;
1003   paint_garden_source();
1004   return;
1005 }
1006 /*}}}*/
1007 /*{{{  void update_garden()*/
1008 extern VOIDFUNC update_garden FUNCARGVOID
1009 /* updates counts and repaints the current garden from scratch
1010  */
1011 {
1012   char      *cptr;
1013   unsigned  ix;
1014 
1015   memset(state.counts, 0, sizeof(state.counts));
1016   state.counts[COUNT_RANDOM] = state.edit->board->apples;
1017   /*{{{  set count values*/
1018   for(ix = CELLS_DOWN; ix--;)
1019     for(cptr = state.edit->board->map[ix]; *cptr; cptr++)
1020       {
1021 	if(*cptr == GARDEN_CHERRY || ISPATHCHERRY(*cptr))
1022 	  state.counts[COUNT_CHERRY]++;
1023 	else if(ISPATHDEN(*cptr))
1024 	  state.counts[COUNT_DEN]++;
1025 	else if(ISPATHPLAYER(*cptr))
1026 	  state.counts[COUNT_PLAYER]++;
1027 	else if(ISAPPLE(*cptr))
1028 	  {
1029 	    unsigned  value;
1030 	    unsigned  ix;
1031 
1032 	    value = GARDENAPPLE(*cptr);
1033 	    for(ix = 4; ix--;)
1034 	      if(value & (1 << ix))
1035 		state.counts[COUNT_APPLES + ix]++;
1036 	    state.counts[COUNT_SPACES]++;
1037 	  }
1038 	else if(*cptr == GARDEN_RANDOM)
1039 	  state.counts[COUNT_SPACES]++;
1040 	if(ix != CELLS_DOWN - 1 && ISPATH(cptr[CELLS_ACROSS + 1]) &&
1041 	    (*cptr == GARDEN_RANDOM || ISAPPLE(*cptr)))
1042 	  state.counts[COUNT_FALL]++;
1043       }
1044   /*}}}*/
1045   apple_set_thumb();
1046   for(ix = COUNTS; ix--;)
1047     adjust_count(ix, 0);
1048   paint_garden_image();
1049   return;
1050 }
1051 /*}}}*/
1052