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