/* * $XConsortium: folder.c,v 2.20 89/10/06 15:02:30 converse Exp $ * * * COPYRIGHT 1987, 1989 * DIGITAL EQUIPMENT CORPORATION * MAYNARD, MASSACHUSETTS * ALL RIGHTS RESERVED. * * THE INFORMATION IN THIS SOFTWARE IS SUBJECT TO CHANGE WITHOUT NOTICE AND * SHOULD NOT BE CONSTRUED AS A COMMITMENT BY DIGITAL EQUIPMENT CORPORATION. * DIGITAL MAKES NO REPRESENTATIONS ABOUT THE SUITABILITY OF THIS SOFTWARE FOR * ANY PURPOSE. IT IS SUPPLIED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY. * * IF THE SOFTWARE IS MODIFIED IN A MANNER CREATING DERIVATIVE COPYRIGHT * RIGHTS, APPROPRIATE LEGENDS MAY BE PLACED ON THE DERIVATIVE WORK IN * ADDITION TO THAT SET FORTH ABOVE. * * Permission to use, copy, modify, and distribute this software and its * documentation for any purpose and without fee is hereby granted, provided * that the above copyright notice appear in all copies and that both that * copyright notice and this permission notice appear in supporting * documentation, and that the name of Digital Equipment Corporation not be * used in advertising or publicity pertaining to distribution of the software * without specific, written prior permission. */ /* folder.c -- implement buttons relating to folders and other globals. */ #include #include #include #include #include "xmh.h" #include "bboxint.h" #include "tocintrnl.h" #include typedef struct { /* client data structure for callbacks */ Scrn scrn; /* the xmh scrn of action */ Toc toc; /* the toc of the selected folder */ Toc original_toc; /* the toc of the current folder */ } DeleteDataRec, *DeleteData; static void CreateFolderMenu(); static void AddFolderMenuEntry(); static void DeleteFolderMenuEntry(); /* Close this toc&view scrn. If this is the last toc&view, quit xmh. */ /*ARGSUSED*/ void DoClose(widget, client_data, call_data) Widget widget; XtPointer client_data; XtPointer call_data; { Scrn scrn = (Scrn) client_data; register int i, count; Toc toc; Display *dpy; XtCallbackRec confirm_callbacks[2]; extern void exit(); count = 0; for (i=0 ; ikind == STtocAndView && scrnList[i]->mapped) count++; confirm_callbacks[0].callback = (XtCallbackProc) DoClose; confirm_callbacks[0].closure = (XtPointer) scrn; confirm_callbacks[1].callback = (XtCallbackProc) NULL; confirm_callbacks[1].closure = (XtPointer) NULL; if (count <= 1) { for (i = numScrns - 1; i >= 0; i--) if (scrnList[i] != scrn) { if (MsgSetScrn((Msg) NULL, scrnList[i], confirm_callbacks, (XtCallbackList) NULL) == NEEDS_CONFIRMATION) return; } for (i = 0; i < numFolders; i++) { toc = folderList[i]; if (TocConfirmCataclysm(toc, confirm_callbacks, (XtCallbackList) NULL)) return; } /* if (MsgSetScrn((Msg) NULL, scrn)) * return; * %%% * for (i = 0; i < numFolders; i++) { * toc = folderList[i]; * if (toc->scanfile && toc->curmsg) * CmdSetSequence(toc, "cur", MakeSingleMsgList(toc->curmsg)); * } */ dpy = XtDisplay(scrn->parent); XtUnmapWidget(scrn->parent); XCloseDisplay(dpy); exit(0); } else { if (MsgSetScrn((Msg) NULL, scrn, confirm_callbacks, (XtCallbackList) NULL) == NEEDS_CONFIRMATION) return; DestroyScrn(scrn); /* doesn't destroy first toc&view scrn */ } } /*ARGSUSED*/ void XmhClose(w, event, params, num_params) Widget w; XEvent *event; /* unused */ String *params; /* unused */ Cardinal *num_params; /* unused */ { Scrn scrn = ScrnFromWidget(w); DoClose(w, (XtPointer) scrn, (XtPointer) NULL); } /* Open the selected folder in this screen. */ /* ARGSUSED*/ void DoOpenFolder(widget, client_data, call_data) Widget widget; XtPointer client_data; XtPointer call_data; { Scrn scrn = (Scrn) client_data; Toc toc = SelectedToc(scrn); TocSetScrn(toc, scrn); } /*ARGSUSED*/ void XmhOpenFolder(w, event, params, num_params) Widget w; XEvent *event; /* unused */ String *params; /* unused */ Cardinal *num_params; /* unused */ { Scrn scrn = ScrnFromWidget(w); DoOpenFolder(w, (XtPointer) scrn, (XtPointer) NULL); } /* Compose a new message. */ /*ARGSUSED*/ void DoComposeMessage(widget, client_data, call_data) Widget widget; XtPointer client_data; XtPointer call_data; { Scrn scrn = NewCompScrn(); Msg msg = TocMakeNewMsg(DraftsFolder); MsgLoadComposition(msg); MsgSetTemporary(msg); MsgSetReapable(msg); MsgSetScrnForComp(msg, scrn); MapScrn(scrn); } /*ARGSUSED*/ void XmhComposeMessage(w, event, params, num_params) Widget w; /* unused */ XEvent *event; /* unused */ String *params; /* unused */ Cardinal *num_params; /* unused */ { DoComposeMessage(w, (XtPointer) NULL, (XtPointer) NULL); } /* Make a new scrn displaying the given folder. */ /*ARGSUSED*/ void DoOpenFolderInNewWindow(widget, client_data, call_data) Widget widget; XtPointer client_data; XtPointer call_data; { Scrn scrn = (Scrn) client_data; Toc toc = SelectedToc(scrn); scrn = CreateNewScrn(STtocAndView); TocSetScrn(toc, scrn); MapScrn(scrn); } /*ARGSUSED*/ void XmhOpenFolderInNewWindow(w, event, params, num_params) Widget w; XEvent *event; /* unused */ String *params; /* unused */ Cardinal *num_params; /* unused */ { Scrn scrn = ScrnFromWidget(w); DoOpenFolderInNewWindow(w, (XtPointer) scrn, (XtPointer) NULL); } /* Create a new folder with the given name. */ static void CreateFolder(name) char *name; { Toc toc; int i; for (i=0 ; name[i] > ' ' ; i++) ; name[i] = '\0'; toc = TocGetNamed(name); if (toc || i == 0) { Feep(); return; } toc = TocCreateFolder(name); if (toc == NULL) { Feep(); return; } for (i = 0; i < numScrns; i++) if (scrnList[i]->folderbuttons) { char *c; Button button; if (c = index(name, '/')) { /* if is subfolder */ c[0] = '\0'; button = BBoxFindButtonNamed(scrnList[i]->folderbuttons, name); c[0] = '/'; if (button) AddFolderMenuEntry(button, name); } else BBoxAddButton(scrnList[i]->folderbuttons, name, menuButtonWidgetClass, True); } } /* Create a new folder. Requires the user to name the new folder. */ /*ARGSUSED*/ void DoCreateFolder(widget, client_data, call_data) Widget widget; /* unused */ XtPointer client_data; /* unused */ XtPointer call_data; /* unused */ { PopupPrompt("Create folder named:", CreateFolder); } /*ARGSUSED*/ void XmhCreateFolder(w, event, params, num_params) Widget w; /* unused */ XEvent *event; /* unused */ String *params; /* unused */ Cardinal *num_params; /* unused */ { DoCreateFolder(w, (XtPointer) NULL, (XtPointer) NULL); } /*ARGSUSED*/ void CancelDeleteFolder(widget, client_data, call_data) Widget widget; /* unused */ XtPointer client_data; XtPointer call_data; /* unused */ { DeleteData deleteData = (DeleteData) client_data; TocClearDeletePending(deleteData->toc); /* When the delete request is made, the toc currently being viewed is * changed if necessary to be the toc under consideration for deletion. * Once deletion has been confirmed or cancelled, we revert to display * the toc originally under view, unless the toc originally under * view has been deleted. */ if (deleteData->original_toc != NULL) TocSetScrn(deleteData->original_toc, deleteData->scrn); XtFree((char *) deleteData); } /*ARGSUSED*/ void CheckAndConfirmDeleteFolder(widget, client_data, call_data) Widget widget; /* unreliable; sometimes NULL */ XtPointer client_data; /* data structure */ XtPointer call_data; /* unused */ { DeleteData deleteData = (DeleteData) client_data; Scrn scrn = deleteData->scrn; Toc toc = deleteData->toc; char str[300]; XtCallbackRec confirms[2]; XtCallbackRec cancels[2]; void CheckAndDeleteFolder(); static XtCallbackRec yes_callbacks[] = { {CheckAndDeleteFolder, (XtPointer) NULL}, {(XtCallbackProc) NULL, (XtPointer) NULL} }; static XtCallbackRec no_callbacks[] = { {CancelDeleteFolder, (XtPointer) NULL}, {(XtCallbackProc) NULL, (XtPointer) NULL} }; /* Display the toc of the folder to be deleted. */ TocSetScrn(toc, scrn); /* Check for pending delete, copy, move, or edits on messages in the * folder to be deleted, and ask for confirmation if they are found. */ confirms[0].callback = (XtCallbackProc) CheckAndConfirmDeleteFolder; confirms[0].closure = client_data; confirms[1].callback = (XtCallbackProc) NULL; confirms[1].closure = (XtPointer) NULL; cancels[0].callback = (XtCallbackProc) CancelDeleteFolder; cancels[0].closure = client_data; cancels[1].callback = (XtCallbackProc) NULL; cancels[1].closure = (XtPointer) NULL; if (TocConfirmCataclysm(toc, confirms, cancels) == NEEDS_CONFIRMATION) return; /* Ask the user for confirmation on destroying the folder. */ yes_callbacks[0].closure = client_data; no_callbacks[0].closure = client_data; (void) sprintf(str, "Are you sure you want to destroy %s?", TocName(toc)); PopupConfirm(scrn->tocwidget, str, yes_callbacks, no_callbacks); } /*ARGSUSED*/ void CheckAndDeleteFolder(widget, client_data, call_data) Widget widget; /* unused */ XtPointer client_data; /* data structure */ XtPointer call_data; /* unused */ { DeleteData deleteData = (DeleteData) client_data; Scrn scrn = deleteData->scrn; Toc toc = deleteData->toc; XtCallbackRec confirms[2]; XtCallbackRec cancels[2]; int i; char *foldername; /* Check for changes occurring after the popup was first presented. */ confirms[0].callback = (XtCallbackProc) CheckAndConfirmDeleteFolder; confirms[0].closure = client_data; confirms[1].callback = (XtCallbackProc) NULL; confirms[1].closure = (XtPointer) NULL; cancels[0].callback = (XtCallbackProc) CancelDeleteFolder; cancels[0].closure = client_data; cancels[1].callback = (XtCallbackProc) NULL; cancels[1].closure = (XtPointer) NULL; if (TocConfirmCataclysm(toc, confirms, cancels) == NEEDS_CONFIRMATION) return; /* Delete. Restore the previously viewed toc, if it wasn't deleted. */ foldername = TocName(toc); TocSetScrn(toc, (Scrn) NULL); TocDeleteFolder(toc); for (i=0 ; ifolderbuttons) { if (IsSubfolder(foldername)) { char *parent_folder = MakeParentFolderName(foldername); /* Since menus are built upon demand, and are a per-screen resource, * resources, not all toc & view screens will have the same menus built. * So the menu entry deletion routines must be able to handle a button * whose menu field is null. It would be better to share folder menus * between screens, but accelerators call action procedures which depend * upon being able to get the screen from the widget argument. */ DeleteFolderMenuEntry ( BBoxFindButtonNamed( scrnList[i]->folderbuttons, parent_folder), foldername); XtFree(parent_folder); } else { BBoxDeleteButton (BBoxFindButtonNamed( scrnList[i]->folderbuttons, foldername)); } /* If we've deleted the current folder, show the Initial Folder */ if ((! strcmp(scrnList[i]->curfolder, foldername)) && (BBoxNumButtons(scrnList[i]->folderbuttons)) && (strcmp(foldername, app_resources.initial_folder_name))) TocSetScrn(InitialFolder, scrnList[i]); } XtFree(foldername); if (deleteData->original_toc != NULL) TocSetScrn(deleteData->original_toc, scrn); XtFree((char *) deleteData); } /* Delete the selected folder. Requires confirmation! */ /*ARGSUSED*/ void DoDeleteFolder(w, client_data, call_data) Widget w; XtPointer client_data; XtPointer call_data; { Scrn scrn = (Scrn) client_data; Toc toc = SelectedToc(scrn); DeleteData deleteData; /* Prevent more than one confirmation popup on the same folder. * TestAndSet returns true if there is a delete pending on this folder. */ if (TocTestAndSetDeletePending(toc)) { Feep(); return; } deleteData = XtNew(DeleteDataRec); deleteData->scrn = scrn; deleteData->toc = toc; deleteData->original_toc = CurrentToc(scrn); if (deleteData->original_toc == toc) deleteData->original_toc = (Toc) NULL; CheckAndConfirmDeleteFolder(w, (XtPointer) deleteData, (XtPointer) NULL); } /*ARGSUSED*/ void XmhDeleteFolder(w, event, params, num_params) Widget w; XEvent *event; /* unused */ String *params; /* unused */ Cardinal *num_params; /* unused */ { Scrn scrn = ScrnFromWidget(w); DoDeleteFolder(w, (XtPointer) scrn, (XtPointer) NULL); } /*----- Notes on MenuButtons as folder buttons --------------------------- * * I assume that the name of the button is identical to the name of the folder. * Only top-level folders have buttons. * Only top-level folders may have subfolders. * Top-level folders and their subfolders may have messages. * */ static char filename[500]; /* for IsFolder() and for callback */ static int flen = 0; /* length of a substring of filename */ /* Function name: IsFolder * Description: determines if a file is an mh subfolder. */ static int IsFolder(ent) struct direct *ent; { register int i, len; char *name = ent->d_name; struct stat buf; /* mh does not like subfolder names to be strings of digits */ if (isdigit(name[0]) || name[0] == '#') { len = strlen(name); for(i=1; i < len && isdigit(name[i]); i++) ; if (i == len) return FALSE; } else if (name[0] == '.') return FALSE; (void) sprintf(filename + flen, "/%s", name); if (stat(filename, &buf) /* failed */) return False; return (buf.st_mode & S_IFMT) == S_IFDIR; } /* menu entry selection callback for folder menus. */ /*ARGSUSED*/ static void DoSelectFolder(w, closure, data) Widget w; /* the menu entry object */ XtPointer closure; /* foldername */ XtPointer data; { Scrn scrn = ScrnFromWidget(w); SetCurrentFolderName(scrn, (char *) closure); } /* Function name: AddFolderMenuEntry * Description: * Add an entry to a menu. If the menu is not already created, * create it, including the (already existing) new subfolder directory. * If the menu is already created, add the new entry. */ static void AddFolderMenuEntry(button, entryname) Button button; /* the corresponding menu button */ char *entryname; /* the new entry, relative to MailDir */ { Cardinal n = 0; Arg args[3]; char * name; static XtCallbackRec callbacks[] = { { DoSelectFolder, (XtPointer) NULL }, { (XtCallbackProc) NULL, (XtPointer) NULL} }; /* The menu must be created before we can add an entry to it. */ if (button->menu == NULL || button->menu == NoMenuForButton) { CreateFolderMenu(button); return; } name = XtNewString(entryname); callbacks[0].closure = (XtPointer) name; XtSetArg(args[n], XtNcallback, callbacks); n++; /* When a subfolder and its parent folder have identical names, * we create a label for the subfolder to distinguish it. */ if (IsSubfolder(entryname)) { char *parent = MakeParentFolderName(entryname); char *subfolder = MakeSubfolderName(entryname); if (strcmp(parent, subfolder) == 0) { XtSetArg(args[n], XtNlabel, MakeSubfolderLabel(subfolder)); n++; XtFree(subfolder); } else name = subfolder; XtFree(parent); } XtCreateManagedWidget(name, smeBSBObjectClass, button->menu, args, n); } /* Function name: CreateFolderMenu * Description: * Menus are created for folder * buttons if the folder has at least one subfolder. For the directory * given by the concatentation of app_resources.mail_path, '/', and the * name of the button, CreateFolderMenu creates the menu whose entries are * the subdirectories which do not begin with '.' and do not have * names which are all digits, and do not have names which are a '#' * followed by all digits. The first entry is always the name of the * parent folder. Remaining entries are alphabetized. */ static void CreateFolderMenu(button) Button button; { struct direct **namelist; register int i, n, length; extern alphasort(); char directory[500]; DEBUG1("Building menu for %s...", button->name) n = strlen(app_resources.mail_path); (void) strncpy(directory, app_resources.mail_path, n); directory[n++] = '/'; (void) strcpy(directory + n, button->name); flen = strlen(directory); /* for IsFolder */ (void) strcpy(filename, directory); /* for IsFolder */ n = scandir(directory, &namelist, IsFolder, alphasort); if (n <= 0) { DEBUG(" no entries.\n") /* no subfolders, therefore no menu */ button->menu = NoMenuForButton; return; } /* Create the menu widget, allowing the menu to show entry changes. */ /* %%% memory leak. We don't free entry callback closures when * the entire menu is destroyed; there should be a destroy callback. */ CreateMenu(button, True); /* The first entry is always the parent folder */ AddFolderMenuEntry(button, button->name); /* Build the menu by adding all the current entries to the new menu. */ length = strlen(button->name); (void) strncpy(directory, button->name, length); directory[length++] = '/'; for (i=0; i < n; i++) { (void) strcpy(directory + length, namelist[i]->d_name); XtFree((char *) namelist[i]); AddFolderMenuEntry(button, directory); } XtFree((char *) namelist); DEBUG(" done.\n") } /* Function: DeleteFolderMenuEntry * Description: Remove a subfolder from a menu. */ static void DeleteFolderMenuEntry(button, foldername) Button button; char *foldername; /* guaranteed to be a subfolder */ { int n; Arg args[2]; if (button == NULL || button->menu == NULL) return; XtSetArg(args[0], XtNnumChildren, &n); XtGetValues(button->menu, args, (Cardinal) 1); if (n <= 2 ) { /*%%% If there's a label, this number ought to be 3 */ /*%%% memory leak on the entry's callback closure */ XtDestroyWidget(button->menu); button->menu = NoMenuForButton; } else { char *subfolder = MakeSubfolderName(foldername); Widget entry; if (strcmp(button->name, subfolder) == 0) { char * label = MakeSubfolderLabel(subfolder); if ((entry = XtNameToWidget(button->menu, label)) != NULL) XtDestroyWidget(entry); XtFree(label); } else { if ((entry = XtNameToWidget(button->menu, subfolder)) != NULL) XtDestroyWidget(entry); } XtFree(subfolder); } } static Widget LastMenuButtonPressed = NULL; /* to `toggle' menu buttons */ /* Function Name: PopupFolderMenu * Description: This action should alwas be taken when the user * selects a folder button. A folder button represents a folder * and zero or more subfolders. The menu of subfolders is built upon * the first reference to it, by this routine. If there are no * subfolders, this routine will mark the folder as having no * subfolders, and no menu will be built. In that case, the menu * button emulates a command button. Wwhen subfolders exist, * the menu will popup, using the menu button action PopupMenu. */ /*ARGSUSED*/ void XmhPopupFolderMenu(w, event, vector, count) Widget w; XEvent *event; /* unused */ String *vector; /* unused */ Cardinal *count; /* unused */ { Button button; Scrn scrn; if (! XtIsSubclass(w, menuButtonWidgetClass)) return; scrn = ScrnFromWidget(w); if ((button = BBoxFindButton(scrn->folderbuttons, w)) == NULL) return; if (button->menu == NULL) CreateFolderMenu(button); if (button->menu == NoMenuForButton) LastMenuButtonPressed = w; else { XtCallActionProc(button->widget, "PopupMenu", (XEvent *) NULL, (String *) NULL, (Cardinal) 0); XtCallActionProc(button->widget, "reset", (XEvent *) NULL, (String *) NULL, (Cardinal) 0); } } /* Function Name: XmhSetCurrentFolder * Description: This action procedure allows menu buttons to * emulate toggle buttons as folder selection buttons. Because of * this, mh folders with no subfolders will not be represented by * a menu with one entry. Sets the current folder without a menu * callback. */ /*ARGSUSED*/ void XmhSetCurrentFolder(w, event, vector, count) Widget w; XEvent *event; /* unused */ String *vector; /* unused */ Cardinal *count; /* unused */ { Button button; Scrn scrn; /* The MenuButton widget has a button grab currently active; the * menu entry selection callback routine will be invoked if the * user selects a menu entry. */ if (w != LastMenuButtonPressed || (! XtIsSubclass(w, menuButtonWidgetClass))) return; scrn = ScrnFromWidget(w); if ((button = BBoxFindButton(scrn->folderbuttons, w)) == NULL) return; SetCurrentFolderName(scrn, button->name); } /*ARGSUSED*/ void XmhLeaveFolderButton(w, event, vector, count) Widget w; XEvent *event; String *vector; Cardinal *count; { LastMenuButtonPressed = NULL; } /*ARGSUSED*/ void XmhOpenFolderFromMenu(w, event, vector, count) Widget w; XEvent *event; String *vector; Cardinal *count; { Position x, y; Dimension width, height; Arg args[2]; /* Open the folder upon a button event within the widget's window. */ if (event->type != ButtonRelease && event->type != ButtonPress) return; x = event->xbutton.x; y = event->xbutton.y; XtSetArg(args[0], XtNwidth, &width); XtSetArg(args[1], XtNheight, &height); XtGetValues(w, args, TWO); if ((x < 0) || (x >= width) || (y < 0) || (y >= height)) return; XmhOpenFolder(w, event, vector, count); } static void Push(scrn, data) Scrn scrn; char *data; { Stack new = XtNew(StackRec); new->data = data; new->next = scrn->stack; scrn->stack = new; } static char * Pop(scrn) Scrn scrn; { Stack top; char *data = NULL; if ((top = scrn->stack) != NULL) { data = top->data; scrn->stack = top->next; XtFree((char *) top); } return data; } /* Parameters are taken as names of folders to be pushed on the stack. * With no parameters, the currently selected folder is pushed. */ /*ARGSUSED*/ void XmhPushFolder(w, event, params, count) Widget w; XEvent *event; String *params; Cardinal *count; { Scrn scrn = ScrnFromWidget(w); int i; for (i=0; i < *count; i++) { Push(scrn, params[i]); } if (*count == 0 && scrn->curfolder) Push(scrn, scrn->curfolder); } /* Pop the stack & take that folder to be the currently selected folder. */ /*ARGSUSED*/ void XmhPopFolder(w, event, params, count) Widget w; XEvent *event; String *params; Cardinal *count; { Scrn scrn = ScrnFromWidget(w); char *folder; if ((folder = Pop(scrn)) != NULL) SetCurrentFolderName(scrn, folder); }