1189 lines
32 KiB
C
1189 lines
32 KiB
C
/*
|
|
* $XConsortium: folder.c,v 2.44 94/08/29 20:25:49 swick 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.
|
|
*/
|
|
/* $XFree86: xc/programs/xmh/folder.c,v 1.4 2002/04/05 21:06:28 dickey Exp $ */
|
|
|
|
/* folder.c -- implement buttons relating to folders and other globals. */
|
|
|
|
#include "xmh.h"
|
|
#include <X11/Xmu/CharSet.h>
|
|
#include <X11/Xaw/Cardinals.h>
|
|
#include <X11/Xatom.h>
|
|
#include <sys/stat.h>
|
|
#include <ctype.h>
|
|
#include "bboxint.h"
|
|
#include "tocintrnl.h"
|
|
#include "actions.h"
|
|
|
|
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 CheckAndDeleteFolder(XMH_CB_ARGS);
|
|
static void CancelDeleteFolder(XMH_CB_ARGS);
|
|
static void CheckAndConfirmDeleteFolder(XMH_CB_ARGS);
|
|
static void FreeMenuData(XMH_CB_ARGS);
|
|
static void CreateFolderMenu(Button);
|
|
static void AddFolderMenuEntry(Button, char *);
|
|
static void DeleteFolderMenuEntry(Button, char *);
|
|
|
|
#ifdef DEBUG_CLEANUP
|
|
extern Boolean ExitLoop;
|
|
#endif
|
|
|
|
/* Close this toc&view scrn. If this is the last toc&view, quit xmh. */
|
|
|
|
/*ARGSUSED*/
|
|
void DoClose(
|
|
Widget widget,
|
|
XtPointer client_data,
|
|
XtPointer call_data)
|
|
{
|
|
Scrn scrn = (Scrn) client_data;
|
|
register int i, count;
|
|
Toc toc;
|
|
XtCallbackRec confirm_callbacks[2];
|
|
|
|
count = 0;
|
|
for (i=0 ; i<numScrns ; i++)
|
|
if (scrnList[i]->kind == 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));
|
|
* }
|
|
*/
|
|
|
|
#ifdef DEBUG_CLEANUP
|
|
XtDestroyWidget(scrn->parent);
|
|
ExitLoop = TRUE;
|
|
return;
|
|
#else
|
|
XtVaSetValues(scrn->parent, XtNjoinSession, (XtArgVal)False, NULL);
|
|
XtUnmapWidget(scrn->parent);
|
|
XtDestroyApplicationContext
|
|
(XtWidgetToApplicationContext(scrn->parent));
|
|
exit(0);
|
|
#endif
|
|
}
|
|
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(
|
|
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 widget,
|
|
XtPointer client_data,
|
|
XtPointer call_data)
|
|
{
|
|
/* Invoked by the Folder menu entry "Open Folder"'s notify action. */
|
|
|
|
Scrn scrn = (Scrn) client_data;
|
|
Toc toc = SelectedToc(scrn);
|
|
if (TocFolderExists(toc))
|
|
TocSetScrn(toc, scrn);
|
|
else
|
|
PopupError(scrn->parent, "Cannot open selected folder.");
|
|
}
|
|
|
|
|
|
/*ARGSUSED*/
|
|
void XmhOpenFolder(
|
|
Widget w,
|
|
XEvent *event, /* unused */
|
|
String *params,
|
|
Cardinal *num_params)
|
|
{
|
|
Scrn scrn = ScrnFromWidget(w);
|
|
|
|
/* This action may be invoked from folder menu buttons or from folder
|
|
* menus, as an action procedure on an event specified in translations.
|
|
* In this case, the action will open a folder only if that folder
|
|
* was actually selected from a folder button or menu. If the folder
|
|
* was selected from a folder menu, the menu entry callback procedure,
|
|
* which changes the selected folder, and is invoked by the "notify"
|
|
* action, must have already executed; and the menu entry "unhightlight"
|
|
* action must execute after this action.
|
|
*
|
|
* This action does not execute if invoked as an accelerator whose
|
|
* source widget is a menu button or a folder menu. However, it
|
|
* may be invoked as a keyboard accelerator of any widget other than
|
|
* the folder menu buttons or the folder menus. In that case, it will
|
|
* open the currently selected folder.
|
|
*
|
|
* If given a parameter, it will take it as the name of a folder to
|
|
* select and open.
|
|
*/
|
|
|
|
if (! UserWantsAction(w, scrn)) return;
|
|
if (*num_params) SetCurrentFolderName(scrn, params[0]);
|
|
DoOpenFolder(w, (XtPointer) scrn, (XtPointer) NULL);
|
|
}
|
|
|
|
|
|
/* Compose a new message. */
|
|
|
|
/*ARGSUSED*/
|
|
void DoComposeMessage(
|
|
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(
|
|
Widget w,
|
|
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 widget,
|
|
XtPointer client_data,
|
|
XtPointer call_data)
|
|
{
|
|
Scrn scrn = (Scrn) client_data;
|
|
Toc toc = SelectedToc(scrn);
|
|
if (TocFolderExists(toc)) {
|
|
scrn = CreateNewScrn(STtocAndView);
|
|
TocSetScrn(toc, scrn);
|
|
MapScrn(scrn);
|
|
} else
|
|
PopupError(scrn->parent, "Cannot open selected folder.");
|
|
}
|
|
|
|
|
|
/*ARGSUSED*/
|
|
void XmhOpenFolderInNewWindow(
|
|
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 char *previous_label = NULL;
|
|
/*ARGSUSED*/
|
|
static void CreateFolder(
|
|
Widget widget, /* the okay button of the dialog widget */
|
|
XtPointer client_data, /* the dialog widget */
|
|
XtPointer call_data)
|
|
{
|
|
Toc toc;
|
|
register int i;
|
|
char *name;
|
|
Widget dialog = (Widget) client_data;
|
|
Arg args[3];
|
|
char str[300], *label;
|
|
|
|
name = XawDialogGetValueString(dialog);
|
|
for (i=0 ; name[i] > ' ' ; i++) ;
|
|
name[i] = '\0';
|
|
toc = TocGetNamed(name);
|
|
if ((toc) || (i==0) || (name[0]=='/') || ((toc = TocCreateFolder(name))
|
|
== NULL)) {
|
|
if (toc)
|
|
(void) sprintf(str, "Folder \"%s\" already exists. Try again.",
|
|
name);
|
|
else if (name[0]=='/')
|
|
(void) sprintf(str, "Please specify folders relative to \"%s\".",
|
|
app_resources.mail_path);
|
|
else
|
|
(void) sprintf(str, "Cannot create folder \"%s\". Try again.",
|
|
name);
|
|
label = XtNewString(str);
|
|
XtSetArg(args[0], XtNlabel, label);
|
|
XtSetArg(args[1], XtNvalue, "");
|
|
XtSetValues(dialog, args, TWO);
|
|
if (previous_label)
|
|
XtFree(previous_label);
|
|
previous_label = label;
|
|
return;
|
|
}
|
|
for (i = 0; i < numScrns; i++)
|
|
if (scrnList[i]->folderbuttons) {
|
|
char *c;
|
|
Button button;
|
|
if ((c = strchr(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);
|
|
}
|
|
DestroyPopup(widget, (XtPointer) XtParent(dialog), (XtPointer) NULL);
|
|
}
|
|
|
|
|
|
/* Create a new folder. Requires the user to name the new folder. */
|
|
|
|
/*ARGSUSED*/
|
|
void DoCreateFolder(
|
|
Widget widget, /* unused */
|
|
XtPointer client_data,
|
|
XtPointer call_data) /* unused */
|
|
{
|
|
Scrn scrn = (Scrn) client_data;
|
|
PopupPrompt(scrn->parent, "Create folder named:", CreateFolder);
|
|
}
|
|
|
|
|
|
/*ARGSUSED*/
|
|
void XmhCreateFolder(
|
|
Widget w,
|
|
XEvent *event, /* unused */
|
|
String *params, /* unused */
|
|
Cardinal *num_params) /* unused */
|
|
{
|
|
Scrn scrn = ScrnFromWidget(w);
|
|
DoCreateFolder(w, (XtPointer)scrn, (XtPointer)NULL);
|
|
}
|
|
|
|
|
|
/*ARGSUSED*/
|
|
static void CancelDeleteFolder(
|
|
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*/
|
|
static void CheckAndConfirmDeleteFolder(
|
|
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];
|
|
|
|
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*/
|
|
static void CheckAndDeleteFolder(
|
|
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 ; i<numScrns ; i++)
|
|
if (scrnList[i]->folderbuttons) {
|
|
|
|
if (IsSubfolder(foldername)) {
|
|
char parent_folder[300];
|
|
char *c = strchr( strcpy(parent_folder, foldername), '/');
|
|
*c = '\0';
|
|
|
|
/* Since menus are built upon demand, and are a per-xmh-screen resource,
|
|
* not all xmh 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 xmh screens, but accelerators call action procedures which depend
|
|
* upon being able to get the xmh screen (Scrn) from the widget argument.
|
|
*/
|
|
|
|
DeleteFolderMenuEntry
|
|
( BBoxFindButtonNamed( scrnList[i]->folderbuttons,
|
|
parent_folder),
|
|
foldername);
|
|
}
|
|
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(
|
|
Widget w,
|
|
XtPointer client_data,
|
|
XtPointer call_data)
|
|
{
|
|
Scrn scrn = (Scrn) client_data;
|
|
Toc toc = SelectedToc(scrn);
|
|
DeleteData deleteData;
|
|
|
|
if (! TocFolderExists(toc)) {
|
|
/* Too hard to clean up xmh when the folder doesn't exist anymore. */
|
|
PopupError(scrn->parent,
|
|
"Cannot open selected folder for confirmation to delete.");
|
|
return;
|
|
}
|
|
|
|
/* 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)) {
|
|
PopupError(scrn->parent, "There is a delete pending on this folder.");
|
|
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(
|
|
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(char *name)
|
|
{
|
|
register int i, len;
|
|
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;
|
|
#ifdef S_ISDIR
|
|
return S_ISDIR(buf.st_mode);
|
|
#else
|
|
return (buf.st_mode & S_IFMT) == S_IFDIR;
|
|
#endif
|
|
}
|
|
|
|
|
|
/* menu entry selection callback for folder menus. */
|
|
|
|
/*ARGSUSED*/
|
|
static void DoSelectFolder(
|
|
Widget w, /* the menu entry object */
|
|
XtPointer closure, /* foldername */
|
|
XtPointer data)
|
|
{
|
|
Scrn scrn = ScrnFromWidget(w);
|
|
SetCurrentFolderName(scrn, (char *) closure);
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static void FreeMenuData(
|
|
Widget w,
|
|
XtPointer client_data,
|
|
XtPointer call_data)
|
|
{
|
|
XtFree((char*) client_data);
|
|
}
|
|
|
|
/* 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 button, /* the corresponding menu button */
|
|
char *entryname) /* the new entry, relative to MailDir */
|
|
{
|
|
Arg args[4];
|
|
char * name;
|
|
char * c;
|
|
char tmpname[300];
|
|
char * label;
|
|
static XtCallbackRec callbacks[] = {
|
|
{ DoSelectFolder, (XtPointer) NULL },
|
|
{ (XtCallbackProc) NULL, (XtPointer) NULL}
|
|
};
|
|
static XtCallbackRec destroyCallbacks[] = {
|
|
{ FreeMenuData, (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;
|
|
destroyCallbacks[0].closure = (XtPointer) name;
|
|
XtSetArg(args[0], XtNcallback, callbacks); /* ONE */
|
|
XtSetArg(args[1], XtNdestroyCallback, destroyCallbacks); /* TWO */
|
|
|
|
/* When a subfolder and its parent folder have identical names,
|
|
* the widget name of the subfolder's menu entry must be unique.
|
|
*/
|
|
label = entryname;
|
|
c = strchr( strcpy(tmpname, entryname), '/');
|
|
if (c) {
|
|
*c = '\0';
|
|
label = ++c;
|
|
if (strcmp(tmpname, c) == 0) {
|
|
c--;
|
|
*c = '_';
|
|
}
|
|
name = c;
|
|
}
|
|
XtSetArg(args[2], XtNlabel, label); /* THREE */
|
|
XtCreateManagedWidget(name, smeBSBObjectClass, button->menu,
|
|
args, THREE);
|
|
}
|
|
|
|
|
|
|
|
/* 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)
|
|
{
|
|
char **namelist;
|
|
register int i, n, length;
|
|
char directory[500];
|
|
|
|
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);
|
|
if (n <= 0) {
|
|
/* no subfolders, therefore no menu */
|
|
button->menu = NoMenuForButton;
|
|
return;
|
|
}
|
|
|
|
button->menu = XtCreatePopupShell("menu", simpleMenuWidgetClass,
|
|
button->widget, (ArgList) NULL, ZERO);
|
|
|
|
/* 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]);
|
|
free((char *) namelist[i]);
|
|
AddFolderMenuEntry(button, directory);
|
|
}
|
|
free((char *) namelist);
|
|
}
|
|
|
|
|
|
/* Function: DeleteFolderMenuEntry
|
|
* Description: Remove a subfolder from a menu.
|
|
*/
|
|
|
|
static void DeleteFolderMenuEntry(
|
|
Button button,
|
|
char *foldername)
|
|
{
|
|
char * c;
|
|
Arg args[2];
|
|
char * subfolder;
|
|
int n;
|
|
char tmpname[300];
|
|
Widget entry;
|
|
|
|
if (button == NULL || button->menu == NULL) return;
|
|
XtSetArg(args[0], XtNnumChildren, &n);
|
|
XtSetArg(args[1], XtNlabel, &c);
|
|
XtGetValues(button->menu, args, TWO);
|
|
if ((n <= 3 && c) || n <= 2) {
|
|
XtDestroyWidget(button->menu);
|
|
button->menu = NoMenuForButton;
|
|
return;
|
|
}
|
|
|
|
c = strchr( strcpy(tmpname, foldername), '/');
|
|
if (c) {
|
|
*c = '\0';
|
|
subfolder = ++c;
|
|
if (strcmp(button->name, subfolder) == 0) {
|
|
c--;
|
|
*c = '_';
|
|
subfolder = c;
|
|
}
|
|
if ((entry = XtNameToWidget(button->menu, subfolder)) != NULL)
|
|
XtDestroyWidget(entry);
|
|
}
|
|
}
|
|
|
|
/* 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. When subfolders exist,
|
|
* the menu will popup, using the menu button action PopupMenu.
|
|
*/
|
|
|
|
/*ARGSUSED*/
|
|
void XmhPopupFolderMenu(
|
|
Widget w,
|
|
XEvent *event, /* unused */
|
|
String *vector, /* unused */
|
|
Cardinal *count) /* unused */
|
|
{
|
|
Button button;
|
|
Scrn scrn;
|
|
|
|
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 widgets in their function of folder selection.
|
|
* Therefore, mh folders with no subfolders can be represented
|
|
* by a button instead of a menu with one entry. Sets the currently
|
|
* selected folder.
|
|
*/
|
|
|
|
/*ARGSUSED*/
|
|
void XmhSetCurrentFolder(
|
|
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
|
|
* currently selected folder will be updated if the user has released
|
|
* the mouse button while the mouse pointer was on the same menu button
|
|
* widget that orginally activated the button grab. This mechanism is
|
|
* insured by the XmhPopupFolderMenu action setting LastMenuButtonPressed.
|
|
* The action XmhLeaveFolderButton, and it's translation in the application
|
|
* defaults file, bound to LeaveWindow events, insures that the menu
|
|
* button behaves properly when the user moves the pointer out of the
|
|
* menu button window.
|
|
*
|
|
* This action is for menu button widgets only.
|
|
*/
|
|
|
|
if (w != LastMenuButtonPressed)
|
|
return;
|
|
scrn = ScrnFromWidget(w);
|
|
if ((button = BBoxFindButton(scrn->folderbuttons, w)) == NULL)
|
|
return;
|
|
SetCurrentFolderName(scrn, button->name);
|
|
}
|
|
|
|
|
|
/*ARGSUSED*/
|
|
void XmhLeaveFolderButton(
|
|
Widget w,
|
|
XEvent *event,
|
|
String *vector,
|
|
Cardinal *count)
|
|
{
|
|
LastMenuButtonPressed = NULL;
|
|
}
|
|
|
|
|
|
void Push(
|
|
Stack *stack_ptr,
|
|
char *data)
|
|
{
|
|
Stack new = XtNew(StackRec);
|
|
new->data = data;
|
|
new->next = *stack_ptr;
|
|
*stack_ptr = new;
|
|
}
|
|
|
|
char * Pop(
|
|
Stack *stack_ptr)
|
|
{
|
|
Stack top;
|
|
char *data = NULL;
|
|
|
|
if ((top = *stack_ptr) != NULL) {
|
|
data = top->data;
|
|
*stack_ptr = 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(
|
|
Widget w,
|
|
XEvent *event,
|
|
String *params,
|
|
Cardinal *count)
|
|
{
|
|
Scrn scrn = ScrnFromWidget(w);
|
|
Cardinal i;
|
|
|
|
for (i=0; i < *count; i++)
|
|
Push(&scrn->folder_stack, params[i]);
|
|
|
|
if (*count == 0 && scrn->curfolder)
|
|
Push(&scrn->folder_stack, scrn->curfolder);
|
|
}
|
|
|
|
/* Pop the stack & take that folder to be the currently selected folder. */
|
|
|
|
/*ARGSUSED*/
|
|
void XmhPopFolder(
|
|
Widget w,
|
|
XEvent *event,
|
|
String *params,
|
|
Cardinal *count)
|
|
{
|
|
Scrn scrn = ScrnFromWidget(w);
|
|
char *folder;
|
|
|
|
if ((folder = Pop(&scrn->folder_stack)) != NULL)
|
|
SetCurrentFolderName(scrn, folder);
|
|
}
|
|
|
|
static Boolean InParams(
|
|
String str,
|
|
String *p,
|
|
Cardinal n)
|
|
{
|
|
Cardinal i;
|
|
for (i=0; i < n; p++, i++)
|
|
if (! XmuCompareISOLatin1(*p, str)) return True;
|
|
return False;
|
|
}
|
|
|
|
/* generalized routine for xmh participation in WM protocols */
|
|
|
|
/*ARGSUSED*/
|
|
void XmhWMProtocols(
|
|
Widget w, /* NULL if from checkpoint timer */
|
|
XEvent * event, /* NULL if from checkpoint timer */
|
|
String * params,
|
|
Cardinal * num_params)
|
|
{
|
|
Boolean dw = False; /* will we do delete window? */
|
|
Boolean sy = False; /* will we do save yourself? */
|
|
static char*WM_DELETE_WINDOW = "WM_DELETE_WINDOW";
|
|
static char*WM_SAVE_YOURSELF = "WM_SAVE_YOURSELF";
|
|
|
|
#define DO_DELETE_WINDOW InParams(WM_DELETE_WINDOW, params, *num_params)
|
|
#define DO_SAVE_YOURSELF InParams(WM_SAVE_YOURSELF, params, *num_params)
|
|
|
|
/* Respond to a recognized WM protocol request iff
|
|
* event type is ClientMessage and no parameters are passed, or
|
|
* event type is ClientMessage and event data is matched to parameters, or
|
|
* event type isn't ClientMessage and parameters make a request.
|
|
*/
|
|
|
|
if (event && event->type == ClientMessage) {
|
|
if (event->xclient.message_type == wm_protocols) {
|
|
if (event->xclient.data.l[0] == wm_delete_window &&
|
|
(*num_params == 0 || DO_DELETE_WINDOW))
|
|
dw = True;
|
|
else if (event->xclient.data.l[0] == wm_save_yourself &&
|
|
(*num_params == 0 || DO_SAVE_YOURSELF))
|
|
sy = True;
|
|
}
|
|
} else {
|
|
if (DO_DELETE_WINDOW)
|
|
dw = True;
|
|
if (DO_SAVE_YOURSELF)
|
|
sy = True;
|
|
}
|
|
|
|
#undef DO_DELETE_WINDOW
|
|
#undef DO_SAVE_YOURSELF
|
|
|
|
if (sy) {
|
|
register int i;
|
|
for (i=0; i<numScrns; i++)
|
|
if (scrnList[i]->msg)
|
|
MsgCheckPoint(scrnList[i]->msg);
|
|
if (w) /* don't generate a property notify via the checkpoint timer */
|
|
XChangeProperty(XtDisplay(toplevel), XtWindow(toplevel),
|
|
XA_WM_COMMAND, XA_STRING, 8, PropModeAppend,
|
|
(unsigned char *)"", 0);
|
|
}
|
|
if (dw && w) {
|
|
Scrn scrn;
|
|
|
|
while (w && !XtIsShell(w))
|
|
w = XtParent(w);
|
|
if (XtIsTransientShell(w)) {
|
|
WMDeletePopup(w, event);
|
|
return;
|
|
}
|
|
scrn = ScrnFromWidget(w);
|
|
switch (scrn->kind) {
|
|
case STtocAndView:
|
|
DoClose(w, (XtPointer)scrn, (XtPointer)NULL);
|
|
break;
|
|
case STview:
|
|
case STcomp:
|
|
DoCloseView(w, (XtPointer)scrn, (XtPointer)NULL);
|
|
break;
|
|
case STpick:
|
|
DestroyScrn(scrn);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
typedef struct _InteractMsgTokenRec {
|
|
Scrn scrn;
|
|
XtCheckpointToken cp_token;
|
|
} InteractMsgTokenRec, *InteractMsgToken;
|
|
|
|
static void CommitMsgChanges(
|
|
Widget w, /* unused */
|
|
XtPointer client_data, /* InteractMsgToken */
|
|
XtPointer call_data)
|
|
{
|
|
Cardinal zero = 0;
|
|
InteractMsgToken iToken = (InteractMsgToken) client_data;
|
|
|
|
XmhSave(iToken->scrn->parent, (XEvent*)NULL, (String*)NULL, &zero);
|
|
|
|
if (MsgChanged(iToken->scrn->msg))
|
|
iToken->cp_token->save_success = False;
|
|
|
|
XtSessionReturnToken(iToken->cp_token);
|
|
XtFree((XtPointer)iToken);
|
|
}
|
|
|
|
static void CancelMsgChanges(
|
|
Widget w, /* unused */
|
|
XtPointer client_data, /* InteractMsgToken */
|
|
XtPointer call_data)
|
|
{
|
|
InteractMsgToken iToken = (InteractMsgToken) client_data;
|
|
|
|
/* don't change any msg state now; this is only a checkpoint
|
|
* and the session might be continuing. */
|
|
|
|
MsgCheckPoint(iToken->scrn->msg);
|
|
|
|
XtSessionReturnToken(iToken->cp_token);
|
|
XtFree((XtPointer)iToken);
|
|
}
|
|
|
|
static void CommitMsgInteract(
|
|
Widget w, /* unused */
|
|
XtPointer client_data, /* Scrn */
|
|
XtPointer call_data) /* XtCheckpointToken */
|
|
{
|
|
Scrn scrn = (Scrn) client_data;
|
|
XtCheckpointToken cpToken = (XtCheckpointToken) call_data;
|
|
char str[300];
|
|
InteractMsgToken iToken;
|
|
static XtCallbackRec yes_callbacks[] = {
|
|
{CommitMsgChanges, (XtPointer) NULL},
|
|
{(XtCallbackProc) NULL, (XtPointer) NULL}
|
|
};
|
|
|
|
static XtCallbackRec no_callbacks[] = {
|
|
{CancelMsgChanges, (XtPointer) NULL},
|
|
{(XtCallbackProc) NULL, (XtPointer) NULL}
|
|
};
|
|
|
|
if (cpToken->interact_style != SmInteractStyleAny
|
|
|| cpToken->cancel_shutdown) {
|
|
XtSessionReturnToken(cpToken);
|
|
return;
|
|
}
|
|
|
|
iToken = XtNew(InteractMsgTokenRec);
|
|
|
|
iToken->scrn = scrn;
|
|
iToken->cp_token = cpToken;
|
|
|
|
yes_callbacks[0].closure = no_callbacks[0].closure = (XtPointer) iToken;
|
|
|
|
(void)sprintf(str,"Save changes to message %s?", MsgName(scrn->msg));
|
|
|
|
/* %%% should add cancel button */
|
|
PopupConfirm(scrn->parent, str, yes_callbacks, no_callbacks);
|
|
}
|
|
|
|
|
|
typedef struct _InteractTocTokenRec {
|
|
Toc toc;
|
|
XtCheckpointToken cp_token;
|
|
} InteractTocTokenRec, *InteractTocToken;
|
|
|
|
static void CommitTocChanges(
|
|
Widget w, /* unused */
|
|
XtPointer client_data, /* InteractTocToken */
|
|
XtPointer call_data)
|
|
{
|
|
InteractTocToken iToken = (InteractTocToken) client_data;
|
|
|
|
TocCommitChanges(w, (XtPointer) iToken->toc, (XtPointer) NULL);
|
|
|
|
XtSessionReturnToken(iToken->cp_token);
|
|
XtFree((XtPointer)iToken);
|
|
}
|
|
|
|
static void CancelTocChanges(
|
|
Widget w, /* unused */
|
|
XtPointer client_data, /* InteractTocToken */
|
|
XtPointer call_data)
|
|
{
|
|
InteractTocToken iToken = (InteractTocToken) client_data;
|
|
|
|
/* don't change any folder or msg state now; this is only
|
|
* a checkpoint and the session might be continuing. */
|
|
|
|
XtSessionReturnToken(iToken->cp_token);
|
|
XtFree((XtPointer)iToken);
|
|
}
|
|
|
|
static void CommitTocInteract(
|
|
Widget w, /* unused */
|
|
XtPointer client_data, /* Toc */
|
|
XtPointer call_data) /* XtCheckpointToken */
|
|
{
|
|
Toc toc = (Toc) client_data;
|
|
XtCheckpointToken cpToken = (XtCheckpointToken) call_data;
|
|
char str[300];
|
|
Widget tocwidget;
|
|
Cardinal i;
|
|
InteractTocToken iToken;
|
|
static XtCallbackRec yes_callbacks[] = {
|
|
{CommitTocChanges, (XtPointer) NULL},
|
|
{(XtCallbackProc) NULL, (XtPointer) NULL}
|
|
};
|
|
|
|
static XtCallbackRec no_callbacks[] = {
|
|
{CancelTocChanges, (XtPointer) NULL},
|
|
{(XtCallbackProc) NULL, (XtPointer) NULL}
|
|
};
|
|
|
|
if (cpToken->interact_style != SmInteractStyleAny
|
|
|| cpToken->cancel_shutdown) {
|
|
XtSessionReturnToken(cpToken);
|
|
return;
|
|
}
|
|
|
|
iToken = XtNew(InteractTocTokenRec);
|
|
|
|
iToken->toc = toc;
|
|
iToken->cp_token = cpToken;
|
|
|
|
yes_callbacks[0].closure = no_callbacks[0].closure = (XtPointer) iToken;
|
|
|
|
(void)sprintf(str,"Commit all changes to %s folder?", toc->foldername);
|
|
|
|
tocwidget = NULL;
|
|
for (i=0; i < toc->num_scrns; i++)
|
|
if (toc->scrn[i]->mapped) {
|
|
tocwidget = toc->scrn[i]->tocwidget;
|
|
break;
|
|
}
|
|
|
|
/* %%% should add cancel button */
|
|
PopupConfirm(tocwidget, str, yes_callbacks, no_callbacks);
|
|
}
|
|
|
|
/* Callback for Session Manager SaveYourself */
|
|
|
|
/*ARGSUSED*/
|
|
void DoSaveYourself(
|
|
Widget w, /* unused; s/b toplevel */
|
|
XtPointer client_data, /* unused */
|
|
XtPointer call_data) /* XtCheckpointToken */
|
|
{
|
|
XtCheckpointToken cpToken = (XtCheckpointToken)call_data;
|
|
|
|
{ /* confirm any uncommitted msg changes */
|
|
int i;
|
|
for (i=0 ; i<numScrns ; i++) {
|
|
if (MsgChanged(scrnList[i]->msg)) {
|
|
if (cpToken->interact_style == SmInteractStyleAny)
|
|
XtAddCallback(toplevel, XtNinteractCallback,
|
|
CommitMsgInteract, (XtPointer)scrnList[i]);
|
|
else {
|
|
Cardinal zero = 0;
|
|
XmhSave(scrnList[i]->parent, (XEvent*)NULL,
|
|
(String*)NULL, &zero);
|
|
if (MsgChanged(scrnList[i]->msg)) {
|
|
MsgCheckPoint(scrnList[i]->msg);
|
|
cpToken->save_success = False;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
{ /* confirm any uncommitted folder changes */
|
|
int i;
|
|
for (i = 0; i < numFolders; i++) {
|
|
if (TocHasChanges(folderList[i])) {
|
|
if (cpToken->interact_style == SmInteractStyleAny)
|
|
XtAddCallback(toplevel, XtNinteractCallback,
|
|
CommitTocInteract, (XtPointer)folderList[i]);
|
|
else
|
|
TocCommitChanges(w, (XtPointer)folderList[i],
|
|
(XtPointer) NULL);
|
|
}
|
|
}
|
|
}
|
|
}
|