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