folder.c revision 66d665a3
1/* 2 * $XConsortium: folder.c,v 2.44 94/08/29 20:25:49 swick Exp $ 3 * 4 * 5 * COPYRIGHT 1987, 1989 6 * DIGITAL EQUIPMENT CORPORATION 7 * MAYNARD, MASSACHUSETTS 8 * ALL RIGHTS RESERVED. 9 * 10 * THE INFORMATION IN THIS SOFTWARE IS SUBJECT TO CHANGE WITHOUT NOTICE AND 11 * SHOULD NOT BE CONSTRUED AS A COMMITMENT BY DIGITAL EQUIPMENT CORPORATION. 12 * DIGITAL MAKES NO REPRESENTATIONS ABOUT THE SUITABILITY OF THIS SOFTWARE FOR 13 * ANY PURPOSE. IT IS SUPPLIED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY. 14 * 15 * IF THE SOFTWARE IS MODIFIED IN A MANNER CREATING DERIVATIVE COPYRIGHT 16 * RIGHTS, APPROPRIATE LEGENDS MAY BE PLACED ON THE DERIVATIVE WORK IN 17 * ADDITION TO THAT SET FORTH ABOVE. 18 * 19 * Permission to use, copy, modify, and distribute this software and its 20 * documentation for any purpose and without fee is hereby granted, provided 21 * that the above copyright notice appear in all copies and that both that 22 * copyright notice and this permission notice appear in supporting 23 * documentation, and that the name of Digital Equipment Corporation not be 24 * used in advertising or publicity pertaining to distribution of the software 25 * without specific, written prior permission. 26 */ 27/* $XFree86: xc/programs/xmh/folder.c,v 1.3 2001/10/28 03:34:38 tsi Exp $ */ 28 29/* folder.c -- implement buttons relating to folders and other globals. */ 30 31#include "xmh.h" 32#include <X11/Xmu/CharSet.h> 33#include <X11/Xaw/Cardinals.h> 34#include <X11/Xatom.h> 35#include <sys/stat.h> 36#include <ctype.h> 37#include "bboxint.h" 38#include "tocintrnl.h" 39#include "actions.h" 40 41typedef struct { /* client data structure for callbacks */ 42 Scrn scrn; /* the xmh scrn of action */ 43 Toc toc; /* the toc of the selected folder */ 44 Toc original_toc; /* the toc of the current folder */ 45} DeleteDataRec, *DeleteData; 46 47 48static void CheckAndDeleteFolder(XMH_CB_ARGS); 49static void CancelDeleteFolder(XMH_CB_ARGS); 50static void CheckAndConfirmDeleteFolder(XMH_CB_ARGS); 51static void FreeMenuData(XMH_CB_ARGS); 52static void CreateFolderMenu(Button); 53static void AddFolderMenuEntry(Button, char *); 54static void DeleteFolderMenuEntry(Button, char *); 55 56#ifdef DEBUG_CLEANUP 57extern Boolean ExitLoop; 58#endif 59 60#ifndef S_ISDIR 61#define S_ISDIR(mode) ((mode & S_IFMT) == S_IFDIR) 62#endif 63 64/* Close this toc&view scrn. If this is the last toc&view, quit xmh. */ 65 66/*ARGSUSED*/ 67void DoClose( 68 Widget widget, 69 XtPointer client_data, 70 XtPointer call_data) 71{ 72 Scrn scrn = (Scrn) client_data; 73 register int i, count; 74 Toc toc; 75 XtCallbackRec confirm_callbacks[2]; 76 77 count = 0; 78 for (i=0 ; i<numScrns ; i++) 79 if (scrnList[i]->kind == STtocAndView && scrnList[i]->mapped) 80 count++; 81 82 confirm_callbacks[0].callback = (XtCallbackProc) DoClose; 83 confirm_callbacks[0].closure = (XtPointer) scrn; 84 confirm_callbacks[1].callback = (XtCallbackProc) NULL; 85 confirm_callbacks[1].closure = (XtPointer) NULL; 86 87 if (count <= 1) { 88 89 for (i = numScrns - 1; i >= 0; i--) 90 if (scrnList[i] != scrn) { 91 if (MsgSetScrn((Msg) NULL, scrnList[i], confirm_callbacks, 92 (XtCallbackList) NULL) == NEEDS_CONFIRMATION) 93 return; 94 } 95 for (i = 0; i < numFolders; i++) { 96 toc = folderList[i]; 97 98 if (TocConfirmCataclysm(toc, confirm_callbacks, 99 (XtCallbackList) NULL)) 100 return; 101 } 102/* if (MsgSetScrn((Msg) NULL, scrn)) 103 * return; 104 * %%% 105 * for (i = 0; i < numFolders; i++) { 106 * toc = folderList[i]; 107 * if (toc->scanfile && toc->curmsg) 108 * CmdSetSequence(toc, "cur", MakeSingleMsgList(toc->curmsg)); 109 * } 110 */ 111 112#ifdef DEBUG_CLEANUP 113 XtDestroyWidget(scrn->parent); 114 ExitLoop = TRUE; 115 return; 116#else 117 XtVaSetValues(scrn->parent, XtNjoinSession, (XtArgVal)False, NULL); 118 XtUnmapWidget(scrn->parent); 119 XtDestroyApplicationContext 120 (XtWidgetToApplicationContext(scrn->parent)); 121 exit(0); 122#endif 123 } 124 else { 125 if (MsgSetScrn((Msg) NULL, scrn, confirm_callbacks, 126 (XtCallbackList) NULL) == NEEDS_CONFIRMATION) 127 return; 128 DestroyScrn(scrn); /* doesn't destroy first toc&view scrn */ 129 } 130} 131 132/*ARGSUSED*/ 133void XmhClose( 134 Widget w, 135 XEvent *event, /* unused */ 136 String *params, /* unused */ 137 Cardinal *num_params) /* unused */ 138{ 139 Scrn scrn = ScrnFromWidget(w); 140 DoClose(w, (XtPointer) scrn, (XtPointer) NULL); 141} 142 143/* Open the selected folder in this screen. */ 144 145/* ARGSUSED*/ 146void DoOpenFolder( 147 Widget widget, 148 XtPointer client_data, 149 XtPointer call_data) 150{ 151 /* Invoked by the Folder menu entry "Open Folder"'s notify action. */ 152 153 Scrn scrn = (Scrn) client_data; 154 Toc toc = SelectedToc(scrn); 155 if (TocFolderExists(toc)) 156 TocSetScrn(toc, scrn); 157 else 158 PopupError(scrn->parent, "Cannot open selected folder."); 159} 160 161 162/*ARGSUSED*/ 163void XmhOpenFolder( 164 Widget w, 165 XEvent *event, /* unused */ 166 String *params, 167 Cardinal *num_params) 168{ 169 Scrn scrn = ScrnFromWidget(w); 170 171 /* This action may be invoked from folder menu buttons or from folder 172 * menus, as an action procedure on an event specified in translations. 173 * In this case, the action will open a folder only if that folder 174 * was actually selected from a folder button or menu. If the folder 175 * was selected from a folder menu, the menu entry callback procedure, 176 * which changes the selected folder, and is invoked by the "notify" 177 * action, must have already executed; and the menu entry "unhightlight" 178 * action must execute after this action. 179 * 180 * This action does not execute if invoked as an accelerator whose 181 * source widget is a menu button or a folder menu. However, it 182 * may be invoked as a keyboard accelerator of any widget other than 183 * the folder menu buttons or the folder menus. In that case, it will 184 * open the currently selected folder. 185 * 186 * If given a parameter, it will take it as the name of a folder to 187 * select and open. 188 */ 189 190 if (! UserWantsAction(w, scrn)) return; 191 if (*num_params) SetCurrentFolderName(scrn, params[0]); 192 DoOpenFolder(w, (XtPointer) scrn, (XtPointer) NULL); 193} 194 195 196/* Compose a new message. */ 197 198/*ARGSUSED*/ 199void DoComposeMessage( 200 Widget widget, 201 XtPointer client_data, 202 XtPointer call_data) 203{ 204 Scrn scrn = NewCompScrn(); 205 Msg msg = TocMakeNewMsg(DraftsFolder); 206 MsgLoadComposition(msg); 207 MsgSetTemporary(msg); 208 MsgSetReapable(msg); 209 MsgSetScrnForComp(msg, scrn); 210 MapScrn(scrn); 211} 212 213 214/*ARGSUSED*/ 215void XmhComposeMessage( 216 Widget w, 217 XEvent *event, /* unused */ 218 String *params, /* unused */ 219 Cardinal *num_params) /* unused */ 220{ 221 DoComposeMessage(w, (XtPointer) NULL, (XtPointer) NULL); 222} 223 224 225/* Make a new scrn displaying the given folder. */ 226 227/*ARGSUSED*/ 228void DoOpenFolderInNewWindow( 229 Widget widget, 230 XtPointer client_data, 231 XtPointer call_data) 232{ 233 Scrn scrn = (Scrn) client_data; 234 Toc toc = SelectedToc(scrn); 235 if (TocFolderExists(toc)) { 236 scrn = CreateNewScrn(STtocAndView); 237 TocSetScrn(toc, scrn); 238 MapScrn(scrn); 239 } else 240 PopupError(scrn->parent, "Cannot open selected folder."); 241} 242 243 244/*ARGSUSED*/ 245void XmhOpenFolderInNewWindow( 246 Widget w, 247 XEvent *event, /* unused */ 248 String *params, /* unused */ 249 Cardinal *num_params) /* unused */ 250{ 251 Scrn scrn = ScrnFromWidget(w); 252 DoOpenFolderInNewWindow(w, (XtPointer) scrn, (XtPointer) NULL); 253} 254 255 256/* Create a new folder with the given name. */ 257 258static char *previous_label = NULL; 259/*ARGSUSED*/ 260static void CreateFolder( 261 Widget widget, /* the okay button of the dialog widget */ 262 XtPointer client_data, /* the dialog widget */ 263 XtPointer call_data) 264{ 265 Toc toc; 266 register int i; 267 char *name; 268 Widget dialog = (Widget) client_data; 269 Arg args[3]; 270 char *label; 271 272 name = XawDialogGetValueString(dialog); 273 for (i=0 ; name[i] > ' ' ; i++) ; 274 name[i] = '\0'; 275 toc = TocGetNamed(name); 276 if ((toc) || (i==0) || (name[0]=='/') || ((toc = TocCreateFolder(name)) 277 == NULL)) { 278 if (toc) 279 XtAsprintf(&label, "Folder \"%s\" already exists. Try again.", 280 name); 281 else if (name[0]=='/') 282 XtAsprintf(&label, "Please specify folders relative to \"%s\".", 283 app_resources.mail_path); 284 else 285 XtAsprintf(&label, "Cannot create folder \"%s\". Try again.", 286 name); 287 288 XtSetArg(args[0], XtNlabel, label); 289 XtSetArg(args[1], XtNvalue, ""); 290 XtSetValues(dialog, args, TWO); 291 if (previous_label) 292 XtFree(previous_label); 293 previous_label = label; 294 return; 295 } 296 for (i = 0; i < numScrns; i++) 297 if (scrnList[i]->folderbuttons) { 298 char *c; 299 Button button; 300 if ((c = strchr(name, '/'))) { /* if is subfolder */ 301 c[0] = '\0'; 302 button = BBoxFindButtonNamed(scrnList[i]->folderbuttons, 303 name); 304 c[0] = '/'; 305 if (button) AddFolderMenuEntry(button, name); 306 } 307 else 308 BBoxAddButton(scrnList[i]->folderbuttons, name, 309 menuButtonWidgetClass, True); 310 } 311 DestroyPopup(widget, (XtPointer) XtParent(dialog), (XtPointer) NULL); 312} 313 314 315/* Create a new folder. Requires the user to name the new folder. */ 316 317/*ARGSUSED*/ 318void DoCreateFolder( 319 Widget widget, /* unused */ 320 XtPointer client_data, 321 XtPointer call_data) /* unused */ 322{ 323 Scrn scrn = (Scrn) client_data; 324 PopupPrompt(scrn->parent, "Create folder named:", CreateFolder); 325} 326 327 328/*ARGSUSED*/ 329void XmhCreateFolder( 330 Widget w, 331 XEvent *event, /* unused */ 332 String *params, /* unused */ 333 Cardinal *num_params) /* unused */ 334{ 335 Scrn scrn = ScrnFromWidget(w); 336 DoCreateFolder(w, (XtPointer)scrn, (XtPointer)NULL); 337} 338 339 340/*ARGSUSED*/ 341static void CancelDeleteFolder( 342 Widget widget, /* unused */ 343 XtPointer client_data, 344 XtPointer call_data) /* unused */ 345{ 346 DeleteData deleteData = (DeleteData) client_data; 347 348 TocClearDeletePending(deleteData->toc); 349 350 /* When the delete request is made, the toc currently being viewed is 351 * changed if necessary to be the toc under consideration for deletion. 352 * Once deletion has been confirmed or cancelled, we revert to display 353 * the toc originally under view, unless the toc originally under 354 * view has been deleted. 355 */ 356 357 if (deleteData->original_toc != NULL) 358 TocSetScrn(deleteData->original_toc, deleteData->scrn); 359 XtFree((char *) deleteData); 360} 361 362 363/*ARGSUSED*/ 364static void CheckAndConfirmDeleteFolder( 365 Widget widget, /* unreliable; sometimes NULL */ 366 XtPointer client_data, /* data structure */ 367 XtPointer call_data) /* unused */ 368{ 369 DeleteData deleteData = (DeleteData) client_data; 370 Scrn scrn = deleteData->scrn; 371 Toc toc = deleteData->toc; 372 char str[300]; 373 XtCallbackRec confirms[2]; 374 XtCallbackRec cancels[2]; 375 376 static XtCallbackRec yes_callbacks[] = { 377 {CheckAndDeleteFolder, (XtPointer) NULL}, 378 {(XtCallbackProc) NULL, (XtPointer) NULL} 379 }; 380 381 static XtCallbackRec no_callbacks[] = { 382 {CancelDeleteFolder, (XtPointer) NULL}, 383 {(XtCallbackProc) NULL, (XtPointer) NULL} 384 }; 385 386 /* Display the toc of the folder to be deleted. */ 387 388 TocSetScrn(toc, scrn); 389 390 /* Check for pending delete, copy, move, or edits on messages in the 391 * folder to be deleted, and ask for confirmation if they are found. 392 */ 393 394 confirms[0].callback = (XtCallbackProc) CheckAndConfirmDeleteFolder; 395 confirms[0].closure = client_data; 396 confirms[1].callback = (XtCallbackProc) NULL; 397 confirms[1].closure = (XtPointer) NULL; 398 399 cancels[0].callback = (XtCallbackProc) CancelDeleteFolder; 400 cancels[0].closure = client_data; 401 cancels[1].callback = (XtCallbackProc) NULL; 402 cancels[1].closure = (XtPointer) NULL; 403 404 if (TocConfirmCataclysm(toc, confirms, cancels) == NEEDS_CONFIRMATION) 405 return; 406 407 /* Ask the user for confirmation on destroying the folder. */ 408 409 yes_callbacks[0].closure = client_data; 410 no_callbacks[0].closure = client_data; 411 snprintf(str, sizeof(str), 412 "Are you sure you want to destroy %s?", TocName(toc)); 413 PopupConfirm(scrn->tocwidget, str, yes_callbacks, no_callbacks); 414} 415 416 417/*ARGSUSED*/ 418static void CheckAndDeleteFolder( 419 Widget widget, /* unused */ 420 XtPointer client_data, /* data structure */ 421 XtPointer call_data) /* unused */ 422{ 423 DeleteData deleteData = (DeleteData) client_data; 424 Scrn scrn = deleteData->scrn; 425 Toc toc = deleteData->toc; 426 XtCallbackRec confirms[2]; 427 XtCallbackRec cancels[2]; 428 int i; 429 char *foldername; 430 431 /* Check for changes occurring after the popup was first presented. */ 432 433 confirms[0].callback = (XtCallbackProc) CheckAndConfirmDeleteFolder; 434 confirms[0].closure = client_data; 435 confirms[1].callback = (XtCallbackProc) NULL; 436 confirms[1].closure = (XtPointer) NULL; 437 438 cancels[0].callback = (XtCallbackProc) CancelDeleteFolder; 439 cancels[0].closure = client_data; 440 cancels[1].callback = (XtCallbackProc) NULL; 441 cancels[1].closure = (XtPointer) NULL; 442 443 if (TocConfirmCataclysm(toc, confirms, cancels) == NEEDS_CONFIRMATION) 444 return; 445 446 /* Delete. Restore the previously viewed toc, if it wasn't deleted. */ 447 448 foldername = TocName(toc); 449 TocSetScrn(toc, (Scrn) NULL); 450 TocDeleteFolder(toc); 451 for (i=0 ; i<numScrns ; i++) 452 if (scrnList[i]->folderbuttons) { 453 454 if (IsSubfolder(foldername)) { 455 char parent_folder[300]; 456 char *c = strchr( strcpy(parent_folder, foldername), '/'); 457 *c = '\0'; 458 459/* Since menus are built upon demand, and are a per-xmh-screen resource, 460 * not all xmh toc & view screens will have the same menus built. 461 * So the menu entry deletion routines must be able to handle a button 462 * whose menu field is null. It would be better to share folder menus 463 * between xmh screens, but accelerators call action procedures which depend 464 * upon being able to get the xmh screen (Scrn) from the widget argument. 465 */ 466 467 DeleteFolderMenuEntry 468 ( BBoxFindButtonNamed( scrnList[i]->folderbuttons, 469 parent_folder), 470 foldername); 471 } 472 else { 473 BBoxDeleteButton 474 (BBoxFindButtonNamed( scrnList[i]->folderbuttons, 475 foldername)); 476 } 477 478 /* If we've deleted the current folder, show the Initial Folder */ 479 480 if ((! strcmp(scrnList[i]->curfolder, foldername)) 481 && (BBoxNumButtons(scrnList[i]->folderbuttons)) 482 && (strcmp(foldername, app_resources.initial_folder_name))) 483 TocSetScrn(InitialFolder, scrnList[i]); 484 } 485 XtFree(foldername); 486 if (deleteData->original_toc != NULL) 487 TocSetScrn(deleteData->original_toc, scrn); 488 XtFree((char *) deleteData); 489} 490 491 492/* Delete the selected folder. Requires confirmation! */ 493 494/*ARGSUSED*/ 495void DoDeleteFolder( 496 Widget w, 497 XtPointer client_data, 498 XtPointer call_data) 499{ 500 Scrn scrn = (Scrn) client_data; 501 Toc toc = SelectedToc(scrn); 502 DeleteData deleteData; 503 504 if (! TocFolderExists(toc)) { 505 /* Too hard to clean up xmh when the folder doesn't exist anymore. */ 506 PopupError(scrn->parent, 507 "Cannot open selected folder for confirmation to delete."); 508 return; 509 } 510 511 /* Prevent more than one confirmation popup on the same folder. 512 * TestAndSet returns true if there is a delete pending on this folder. 513 */ 514 if (TocTestAndSetDeletePending(toc)) { 515 PopupError(scrn->parent, "There is a delete pending on this folder."); 516 return; 517 } 518 519 deleteData = XtNew(DeleteDataRec); 520 deleteData->scrn = scrn; 521 deleteData->toc = toc; 522 deleteData->original_toc = CurrentToc(scrn); 523 if (deleteData->original_toc == toc) 524 deleteData->original_toc = (Toc) NULL; 525 526 CheckAndConfirmDeleteFolder(w, (XtPointer) deleteData, (XtPointer) NULL); 527} 528 529 530/*ARGSUSED*/ 531void XmhDeleteFolder( 532 Widget w, 533 XEvent *event, /* unused */ 534 String *params, /* unused */ 535 Cardinal *num_params) /* unused */ 536{ 537 Scrn scrn = ScrnFromWidget(w); 538 DoDeleteFolder(w, (XtPointer) scrn, (XtPointer) NULL); 539} 540 541 542/*----- Notes on MenuButtons as folder buttons --------------------------- 543 * 544 * I assume that the name of the button is identical to the name of the folder. 545 * Only top-level folders have buttons. 546 * Only top-level folders may have subfolders. 547 * Top-level folders and their subfolders may have messages. 548 * 549 */ 550 551static char filename[500]; /* for IsFolder() and for callback */ 552static int flen = 0; /* length of a substring of filename */ 553 554 555/* Function name: IsFolder 556 * Description: determines if a file is an mh subfolder. 557 */ 558static int IsFolder(char *name) 559{ 560 register int i, len; 561 struct stat buf; 562 563 /* mh does not like subfolder names to be strings of digits */ 564 565 if (isdigit(name[0]) || name[0] == '#') { 566 len = strlen(name); 567 for(i=1; i < len && isdigit(name[i]); i++) 568 ; 569 if (i == len) return FALSE; 570 } 571 else if (name[0] == '.') 572 return FALSE; 573 574 if (flen >= sizeof(filename)) return False; 575 if (snprintf(filename + flen, sizeof(filename) - flen, "/%s", name) 576 >= sizeof(filename)) return False; 577 if (stat(filename, &buf) /* failed */) return False; 578 return S_ISDIR(buf.st_mode); 579} 580 581 582/* menu entry selection callback for folder menus. */ 583 584/*ARGSUSED*/ 585static void DoSelectFolder( 586 Widget w, /* the menu entry object */ 587 XtPointer closure, /* foldername */ 588 XtPointer data) 589{ 590 Scrn scrn = ScrnFromWidget(w); 591 SetCurrentFolderName(scrn, (char *) closure); 592} 593 594/*ARGSUSED*/ 595static void FreeMenuData( 596 Widget w, 597 XtPointer client_data, 598 XtPointer call_data) 599{ 600 XtFree((char*) client_data); 601} 602 603/* Function name: AddFolderMenuEntry 604 * Description: 605 * Add an entry to a menu. If the menu is not already created, 606 * create it, including the (already existing) new subfolder directory. 607 * If the menu is already created, add the new entry. 608 */ 609 610static void AddFolderMenuEntry( 611 Button button, /* the corresponding menu button */ 612 char *entryname) /* the new entry, relative to MailDir */ 613{ 614 Arg args[4]; 615 char * name; 616 char * c; 617 char tmpname[300]; 618 char * label; 619 static XtCallbackRec callbacks[] = { 620 { DoSelectFolder, (XtPointer) NULL }, 621 { (XtCallbackProc) NULL, (XtPointer) NULL} 622 }; 623 static XtCallbackRec destroyCallbacks[] = { 624 { FreeMenuData, (XtPointer) NULL }, 625 { (XtCallbackProc) NULL, (XtPointer) NULL} 626 }; 627 628 /* The menu must be created before we can add an entry to it. */ 629 630 if (button->menu == NULL || button->menu == NoMenuForButton) { 631 CreateFolderMenu(button); 632 return; 633 } 634 name = XtNewString(entryname); 635 callbacks[0].closure = (XtPointer) name; 636 destroyCallbacks[0].closure = (XtPointer) name; 637 XtSetArg(args[0], XtNcallback, callbacks); /* ONE */ 638 XtSetArg(args[1], XtNdestroyCallback, destroyCallbacks); /* TWO */ 639 640 /* When a subfolder and its parent folder have identical names, 641 * the widget name of the subfolder's menu entry must be unique. 642 */ 643 label = entryname; 644 c = strchr( strcpy(tmpname, entryname), '/'); 645 if (c) { 646 *c = '\0'; 647 label = ++c; 648 if (strcmp(tmpname, c) == 0) { 649 c--; 650 *c = '_'; 651 } 652 name = c; 653 } 654 XtSetArg(args[2], XtNlabel, label); /* THREE */ 655 XtCreateManagedWidget(name, smeBSBObjectClass, button->menu, 656 args, THREE); 657} 658 659 660 661/* Function name: CreateFolderMenu 662 * Description: 663 * Menus are created for folder buttons if the folder has at least one 664 * subfolder. For the directory given by the concatenation of 665 * app_resources.mail_path, '/', and the name of the button, 666 * CreateFolderMenu creates the menu whose entries are 667 * the subdirectories which do not begin with '.' and do not have 668 * names which are all digits, and do not have names which are a '#' 669 * followed by all digits. The first entry is always the name of the 670 * parent folder. Remaining entries are alphabetized. 671 */ 672 673static void CreateFolderMenu( 674 Button button) 675{ 676 char **namelist; 677 register int i, n, length; 678 char directory[500]; 679 680 n = 0; 681 if (snprintf(directory, sizeof(directory), "%s/%s", 682 app_resources.mail_path, button->name) < sizeof(directory)) 683 { 684 flen = strlen(directory); /* for IsFolder */ 685 if (flen < sizeof(filename)) { 686 strcpy(filename, directory); /* for IsFolder */ 687 n = ScanDir(directory, &namelist, IsFolder); 688 } 689 } 690 if (n <= 0) { 691 /* no subfolders, therefore no menu */ 692 button->menu = NoMenuForButton; 693 return; 694 } 695 696 button->menu = XtCreatePopupShell("menu", simpleMenuWidgetClass, 697 button->widget, (ArgList) NULL, ZERO); 698 699 /* The first entry is always the parent folder */ 700 701 AddFolderMenuEntry(button, button->name); 702 703 /* Build the menu by adding all the current entries to the new menu. */ 704 705 length = strlen(button->name); 706 memcpy(directory, button->name, length); 707 directory[length++] = '/'; 708 for (i=0; i < n; i++) { 709 (void) strcpy(directory + length, namelist[i]); 710 free((char *) namelist[i]); 711 AddFolderMenuEntry(button, directory); 712 } 713 free((char *) namelist); 714} 715 716 717/* Function: DeleteFolderMenuEntry 718 * Description: Remove a subfolder from a menu. 719 */ 720 721static void DeleteFolderMenuEntry( 722 Button button, 723 char *foldername) 724{ 725 char * c; 726 Arg args[2]; 727 char * subfolder; 728 int n; 729 char tmpname[300]; 730 Widget entry; 731 732 if (button == NULL || button->menu == NULL) return; 733 XtSetArg(args[0], XtNnumChildren, &n); 734 XtSetArg(args[1], XtNlabel, &c); 735 XtGetValues(button->menu, args, TWO); 736 if ((n <= 3 && c) || n <= 2) { 737 XtDestroyWidget(button->menu); 738 button->menu = NoMenuForButton; 739 return; 740 } 741 742 c = strchr( strcpy(tmpname, foldername), '/'); 743 if (c) { 744 *c = '\0'; 745 subfolder = ++c; 746 if (strcmp(button->name, subfolder) == 0) { 747 c--; 748 *c = '_'; 749 subfolder = c; 750 } 751 if ((entry = XtNameToWidget(button->menu, subfolder)) != NULL) 752 XtDestroyWidget(entry); 753 } 754} 755 756/* Function Name: PopupFolderMenu 757 * Description: This action should always be taken when the user 758 * selects a folder button. A folder button represents a folder 759 * and zero or more subfolders. The menu of subfolders is built upon 760 * the first reference to it, by this routine. If there are no 761 * subfolders, this routine will mark the folder as having no 762 * subfolders, and no menu will be built. In that case, the menu 763 * button emulates a command button. When subfolders exist, 764 * the menu will popup, using the menu button action PopupMenu. 765 */ 766 767/*ARGSUSED*/ 768void XmhPopupFolderMenu( 769 Widget w, 770 XEvent *event, /* unused */ 771 String *vector, /* unused */ 772 Cardinal *count) /* unused */ 773{ 774 Button button; 775 Scrn scrn; 776 777 scrn = ScrnFromWidget(w); 778 if ((button = BBoxFindButton(scrn->folderbuttons, w)) == NULL) 779 return; 780 if (button->menu == NULL) 781 CreateFolderMenu(button); 782 783 if (button->menu == NoMenuForButton) 784 LastMenuButtonPressed = w; 785 else { 786 XtCallActionProc(button->widget, "PopupMenu", (XEvent *) NULL, 787 (String *) NULL, (Cardinal) 0); 788 XtCallActionProc(button->widget, "reset", (XEvent *) NULL, 789 (String *) NULL, (Cardinal) 0); 790 } 791} 792 793 794/* Function Name: XmhSetCurrentFolder 795 * Description: This action procedure allows menu buttons to 796 * emulate toggle widgets in their function of folder selection. 797 * Therefore, mh folders with no subfolders can be represented 798 * by a button instead of a menu with one entry. Sets the currently 799 * selected folder. 800 */ 801 802/*ARGSUSED*/ 803void XmhSetCurrentFolder( 804 Widget w, 805 XEvent *event, /* unused */ 806 String *vector, /* unused */ 807 Cardinal *count) /* unused */ 808{ 809 Button button; 810 Scrn scrn; 811 812 /* The MenuButton widget has a button grab currently active; the 813 * currently selected folder will be updated if the user has released 814 * the mouse button while the mouse pointer was on the same menu button 815 * widget that originally activated the button grab. This mechanism is 816 * insured by the XmhPopupFolderMenu action setting LastMenuButtonPressed. 817 * The action XmhLeaveFolderButton, and it's translation in the application 818 * defaults file, bound to LeaveWindow events, insures that the menu 819 * button behaves properly when the user moves the pointer out of the 820 * menu button window. 821 * 822 * This action is for menu button widgets only. 823 */ 824 825 if (w != LastMenuButtonPressed) 826 return; 827 scrn = ScrnFromWidget(w); 828 if ((button = BBoxFindButton(scrn->folderbuttons, w)) == NULL) 829 return; 830 SetCurrentFolderName(scrn, button->name); 831} 832 833 834/*ARGSUSED*/ 835void XmhLeaveFolderButton( 836 Widget w, 837 XEvent *event, 838 String *vector, 839 Cardinal *count) 840{ 841 LastMenuButtonPressed = NULL; 842} 843 844 845void Push( 846 Stack *stack_ptr, 847 const char *data) 848{ 849 Stack new = XtNew(StackRec); 850 new->data = data; 851 new->next = *stack_ptr; 852 *stack_ptr = new; 853} 854 855const char * Pop( 856 Stack *stack_ptr) 857{ 858 Stack top; 859 const char *data = NULL; 860 861 if ((top = *stack_ptr) != NULL) { 862 data = top->data; 863 *stack_ptr = top->next; 864 XtFree((char *) top); 865 } 866 return data; 867} 868 869/* Parameters are taken as names of folders to be pushed on the stack. 870 * With no parameters, the currently selected folder is pushed. 871 */ 872 873/*ARGSUSED*/ 874void XmhPushFolder( 875 Widget w, 876 XEvent *event, 877 String *params, 878 Cardinal *count) 879{ 880 Scrn scrn = ScrnFromWidget(w); 881 Cardinal i; 882 883 for (i=0; i < *count; i++) 884 Push(&scrn->folder_stack, params[i]); 885 886 if (*count == 0 && scrn->curfolder) 887 Push(&scrn->folder_stack, scrn->curfolder); 888} 889 890/* Pop the stack & take that folder to be the currently selected folder. */ 891 892/*ARGSUSED*/ 893void XmhPopFolder( 894 Widget w, 895 XEvent *event, 896 String *params, 897 Cardinal *count) 898{ 899 Scrn scrn = ScrnFromWidget(w); 900 const char *folder; 901 902 if ((folder = Pop(&scrn->folder_stack)) != NULL) 903 SetCurrentFolderName(scrn, folder); 904} 905 906static Boolean InParams( 907 String str, 908 String *p, 909 Cardinal n) 910{ 911 Cardinal i; 912 for (i=0; i < n; p++, i++) 913 if (! XmuCompareISOLatin1(*p, str)) return True; 914 return False; 915} 916 917/* generalized routine for xmh participation in WM protocols */ 918 919/*ARGSUSED*/ 920void XmhWMProtocols( 921 Widget w, /* NULL if from checkpoint timer */ 922 XEvent * event, /* NULL if from checkpoint timer */ 923 String * params, 924 Cardinal * num_params) 925{ 926 Boolean dw = False; /* will we do delete window? */ 927 Boolean sy = False; /* will we do save yourself? */ 928 static const char *WM_DELETE_WINDOW = "WM_DELETE_WINDOW"; 929 static const char *WM_SAVE_YOURSELF = "WM_SAVE_YOURSELF"; 930 931#define DO_DELETE_WINDOW InParams(WM_DELETE_WINDOW, params, *num_params) 932#define DO_SAVE_YOURSELF InParams(WM_SAVE_YOURSELF, params, *num_params) 933 934 /* Respond to a recognized WM protocol request iff 935 * event type is ClientMessage and no parameters are passed, or 936 * event type is ClientMessage and event data is matched to parameters, or 937 * event type isn't ClientMessage and parameters make a request. 938 */ 939 940 if (event && event->type == ClientMessage) { 941 if (event->xclient.message_type == wm_protocols) { 942 if (event->xclient.data.l[0] == wm_delete_window && 943 (*num_params == 0 || DO_DELETE_WINDOW)) 944 dw = True; 945 else if (event->xclient.data.l[0] == wm_save_yourself && 946 (*num_params == 0 || DO_SAVE_YOURSELF)) 947 sy = True; 948 } 949 } else { 950 if (DO_DELETE_WINDOW) 951 dw = True; 952 if (DO_SAVE_YOURSELF) 953 sy = True; 954 } 955 956#undef DO_DELETE_WINDOW 957#undef DO_SAVE_YOURSELF 958 959 if (sy) { 960 register int i; 961 for (i=0; i<numScrns; i++) 962 if (scrnList[i]->msg) 963 MsgCheckPoint(scrnList[i]->msg); 964 if (w) /* don't generate a property notify via the checkpoint timer */ 965 XChangeProperty(XtDisplay(toplevel), XtWindow(toplevel), 966 XA_WM_COMMAND, XA_STRING, 8, PropModeAppend, 967 (unsigned char *)"", 0); 968 } 969 if (dw && w) { 970 Scrn scrn; 971 972 while (w && !XtIsShell(w)) 973 w = XtParent(w); 974 if (XtIsTransientShell(w)) { 975 WMDeletePopup(w, event); 976 return; 977 } 978 scrn = ScrnFromWidget(w); 979 switch (scrn->kind) { 980 case STtocAndView: 981 DoClose(w, (XtPointer)scrn, (XtPointer)NULL); 982 break; 983 case STview: 984 case STcomp: 985 DoCloseView(w, (XtPointer)scrn, (XtPointer)NULL); 986 break; 987 case STpick: 988 DestroyScrn(scrn); 989 break; 990 } 991 } 992} 993 994 995typedef struct _InteractMsgTokenRec { 996 Scrn scrn; 997 XtCheckpointToken cp_token; 998} InteractMsgTokenRec, *InteractMsgToken; 999 1000static void CommitMsgChanges( 1001 Widget w, /* unused */ 1002 XtPointer client_data, /* InteractMsgToken */ 1003 XtPointer call_data) 1004{ 1005 Cardinal zero = 0; 1006 InteractMsgToken iToken = (InteractMsgToken) client_data; 1007 1008 XmhSave(iToken->scrn->parent, (XEvent*)NULL, (String*)NULL, &zero); 1009 1010 if (MsgChanged(iToken->scrn->msg)) 1011 iToken->cp_token->save_success = False; 1012 1013 XtSessionReturnToken(iToken->cp_token); 1014 XtFree((XtPointer)iToken); 1015} 1016 1017static void CancelMsgChanges( 1018 Widget w, /* unused */ 1019 XtPointer client_data, /* InteractMsgToken */ 1020 XtPointer call_data) 1021{ 1022 InteractMsgToken iToken = (InteractMsgToken) client_data; 1023 1024 /* don't change any msg state now; this is only a checkpoint 1025 * and the session might be continuing. */ 1026 1027 MsgCheckPoint(iToken->scrn->msg); 1028 1029 XtSessionReturnToken(iToken->cp_token); 1030 XtFree((XtPointer)iToken); 1031} 1032 1033static void CommitMsgInteract( 1034 Widget w, /* unused */ 1035 XtPointer client_data, /* Scrn */ 1036 XtPointer call_data) /* XtCheckpointToken */ 1037{ 1038 Scrn scrn = (Scrn) client_data; 1039 XtCheckpointToken cpToken = (XtCheckpointToken) call_data; 1040 char str[300]; 1041 InteractMsgToken iToken; 1042 static XtCallbackRec yes_callbacks[] = { 1043 {CommitMsgChanges, (XtPointer) NULL}, 1044 {(XtCallbackProc) NULL, (XtPointer) NULL} 1045 }; 1046 1047 static XtCallbackRec no_callbacks[] = { 1048 {CancelMsgChanges, (XtPointer) NULL}, 1049 {(XtCallbackProc) NULL, (XtPointer) NULL} 1050 }; 1051 1052 if (cpToken->interact_style != SmInteractStyleAny 1053 || cpToken->cancel_shutdown) { 1054 XtSessionReturnToken(cpToken); 1055 return; 1056 } 1057 1058 iToken = XtNew(InteractMsgTokenRec); 1059 1060 iToken->scrn = scrn; 1061 iToken->cp_token = cpToken; 1062 1063 yes_callbacks[0].closure = no_callbacks[0].closure = (XtPointer) iToken; 1064 1065 snprintf(str, sizeof(str), 1066 "Save changes to message %s?", MsgName(scrn->msg)); 1067 1068 /* %%% should add cancel button */ 1069 PopupConfirm(scrn->parent, str, yes_callbacks, no_callbacks); 1070} 1071 1072 1073typedef struct _InteractTocTokenRec { 1074 Toc toc; 1075 XtCheckpointToken cp_token; 1076} InteractTocTokenRec, *InteractTocToken; 1077 1078static void CommitTocChanges( 1079 Widget w, /* unused */ 1080 XtPointer client_data, /* InteractTocToken */ 1081 XtPointer call_data) 1082{ 1083 InteractTocToken iToken = (InteractTocToken) client_data; 1084 1085 TocCommitChanges(w, (XtPointer) iToken->toc, (XtPointer) NULL); 1086 1087 XtSessionReturnToken(iToken->cp_token); 1088 XtFree((XtPointer)iToken); 1089} 1090 1091static void CancelTocChanges( 1092 Widget w, /* unused */ 1093 XtPointer client_data, /* InteractTocToken */ 1094 XtPointer call_data) 1095{ 1096 InteractTocToken iToken = (InteractTocToken) client_data; 1097 1098 /* don't change any folder or msg state now; this is only 1099 * a checkpoint and the session might be continuing. */ 1100 1101 XtSessionReturnToken(iToken->cp_token); 1102 XtFree((XtPointer)iToken); 1103} 1104 1105static void CommitTocInteract( 1106 Widget w, /* unused */ 1107 XtPointer client_data, /* Toc */ 1108 XtPointer call_data) /* XtCheckpointToken */ 1109{ 1110 Toc toc = (Toc) client_data; 1111 XtCheckpointToken cpToken = (XtCheckpointToken) call_data; 1112 char str[300]; 1113 Widget tocwidget; 1114 Cardinal i; 1115 InteractTocToken iToken; 1116 static XtCallbackRec yes_callbacks[] = { 1117 {CommitTocChanges, (XtPointer) NULL}, 1118 {(XtCallbackProc) NULL, (XtPointer) NULL} 1119 }; 1120 1121 static XtCallbackRec no_callbacks[] = { 1122 {CancelTocChanges, (XtPointer) NULL}, 1123 {(XtCallbackProc) NULL, (XtPointer) NULL} 1124 }; 1125 1126 if (cpToken->interact_style != SmInteractStyleAny 1127 || cpToken->cancel_shutdown) { 1128 XtSessionReturnToken(cpToken); 1129 return; 1130 } 1131 1132 iToken = XtNew(InteractTocTokenRec); 1133 1134 iToken->toc = toc; 1135 iToken->cp_token = cpToken; 1136 1137 yes_callbacks[0].closure = no_callbacks[0].closure = (XtPointer) iToken; 1138 1139 snprintf(str, sizeof(str), 1140 "Commit all changes to %s folder?", toc->foldername); 1141 1142 tocwidget = NULL; 1143 for (i=0; i < toc->num_scrns; i++) 1144 if (toc->scrn[i]->mapped) { 1145 tocwidget = toc->scrn[i]->tocwidget; 1146 break; 1147 } 1148 1149 /* %%% should add cancel button */ 1150 PopupConfirm(tocwidget, str, yes_callbacks, no_callbacks); 1151} 1152 1153/* Callback for Session Manager SaveYourself */ 1154 1155/*ARGSUSED*/ 1156void DoSaveYourself( 1157 Widget w, /* unused; s/b toplevel */ 1158 XtPointer client_data, /* unused */ 1159 XtPointer call_data) /* XtCheckpointToken */ 1160{ 1161 XtCheckpointToken cpToken = (XtCheckpointToken)call_data; 1162 1163 { /* confirm any uncommitted msg changes */ 1164 int i; 1165 for (i=0 ; i<numScrns ; i++) { 1166 if (MsgChanged(scrnList[i]->msg)) { 1167 if (cpToken->interact_style == SmInteractStyleAny) 1168 XtAddCallback(toplevel, XtNinteractCallback, 1169 CommitMsgInteract, (XtPointer)scrnList[i]); 1170 else { 1171 Cardinal zero = 0; 1172 XmhSave(scrnList[i]->parent, (XEvent*)NULL, 1173 (String*)NULL, &zero); 1174 if (MsgChanged(scrnList[i]->msg)) { 1175 MsgCheckPoint(scrnList[i]->msg); 1176 cpToken->save_success = False; 1177 } 1178 } 1179 } 1180 } 1181 } 1182 1183 { /* confirm any uncommitted folder changes */ 1184 int i; 1185 for (i = 0; i < numFolders; i++) { 1186 if (TocHasChanges(folderList[i])) { 1187 if (cpToken->interact_style == SmInteractStyleAny) 1188 XtAddCallback(toplevel, XtNinteractCallback, 1189 CommitTocInteract, (XtPointer)folderList[i]); 1190 else 1191 TocCommitChanges(w, (XtPointer)folderList[i], 1192 (XtPointer) NULL); 1193 } 1194 } 1195 } 1196} 1197