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