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