1 /*************************************************
3 *************************************************/
5 /* Copyright (c) University of Cambridge 1995 - 2018 */
6 /* See the file NOTICE for conditions of use and distribution. */
11 /* This module contains code for handling the popup menus. */
13 static Widget menushell;
14 static Widget queue_text_sink;
15 static Widget dialog_shell, dialog_widget;
17 static Widget text_create(uschar *, int);
19 static int highlighted_start, highlighted_end, highlighted_x, highlighted_y;
23 static Arg queue_get_arg[] = {
24 { "textSink", (XtArgVal)NULL },
25 { "textSource", (XtArgVal)NULL },
26 { "string", (XtArgVal)NULL } };
28 static Arg dialog_arg[] = {
29 { "label", (XtArgVal)"dialog" },
30 { "value", (XtArgVal)"value" } };
32 static Arg get_pos_args[] = {
33 {"x", (XtArgVal)NULL },
34 {"y", (XtArgVal)NULL } };
36 static Arg menushell_arg[] = {
37 { "label", (XtArgVal)NULL } };
39 static Arg button_arg[] = {
40 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
41 { XtNlabel, (XtArgVal) " Dismiss " },
42 { "left", XawChainLeft },
43 { "right", XawChainLeft },
44 { "top", XawChainBottom },
45 { "bottom", XawChainBottom } };
47 static Arg text_arg[] = {
48 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
49 { "editType", XawtextEdit },
50 { "string", (XtArgVal)"" }, /* dummy to get it going */
51 { "scrollVertical", XawtextScrollAlways },
52 { "wrap", XawtextWrapWord },
53 { "top", XawChainTop },
54 { "bottom", XawChainBottom } };
56 static Arg item_1_arg[] = {
57 { XtNfromVert, (XtArgVal)NULL }, /* must be first */
58 { "label", (XtArgVal)" Message log" } };
60 static Arg item_2_arg[] = {
61 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
62 { "label", (XtArgVal)" Headers" } };
64 static Arg item_3_arg[] = {
65 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
66 { "label", (XtArgVal)" Body" } };
68 static Arg item_4_arg[] = {
69 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
70 { "label", (XtArgVal)" Deliver message" } };
72 static Arg item_5_arg[] = {
73 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
74 { "label", (XtArgVal)" Freeze message" } };
76 static Arg item_6_arg[] = {
77 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
78 { "label", (XtArgVal)" Thaw message" } };
80 static Arg item_7_arg[] = {
81 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
82 { "label", (XtArgVal)" Give up on msg" } };
84 static Arg item_8_arg[] = {
85 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
86 { "label", (XtArgVal)" Remove message" } };
88 static Arg item_9_arg[] = {
89 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
90 { "label", (XtArgVal)"----------------" } };
92 static Arg item_10_arg[] = {
93 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
94 { "label", (XtArgVal)" Add recipient" } };
96 static Arg item_11_arg[] = {
97 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
98 { "label", (XtArgVal)" Mark delivered" } };
100 static Arg item_12_arg[] = {
101 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
102 { "label", (XtArgVal)" Mark all delivered" } };
104 static Arg item_13_arg[] = {
105 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
106 { "label", (XtArgVal)" Edit sender" } };
108 static Arg item_99_arg[] = {
109 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
110 { "label", (XtArgVal)" " } };
114 /*************************************************
115 * Destroy the menu when popped down *
116 *************************************************/
118 static void popdownAction(Widget w, XtPointer client_data, XtPointer call_data)
120 client_data = client_data; /* Keep picky compilers happy */
121 call_data = call_data;
122 if (highlighted_x >= 0)
123 XawTextSinkDisplayText(queue_text_sink,
124 highlighted_x, highlighted_y,
125 highlighted_start, highlighted_end, 0);
132 /*************************************************
133 * Display the message log *
134 *************************************************/
137 msglogAction(Widget w, XtPointer client_data, XtPointer call_data)
140 Widget text = text_create(US client_data, text_depth);
141 uschar * fname = NULL;
144 w = w; /* Keep picky compilers happy */
145 call_data = call_data;
147 /* End up with the split version, so message looks right when non-exist */
149 for (i = 0; i < (spool_is_split ? 2:1); i++)
151 message_subdir[0] = i != 0 ? (US client_data)[5] : 0;
152 fname = spool_fname(US"msglog", message_subdir, US client_data, US"");
153 if ((f = fopen(CS fname, "r")))
158 text_showf(text, "%s: %s\n", fname, strerror(errno));
162 while (Ufgets(buffer, sizeof(buffer), f) != NULL) text_show(text, buffer);
169 /*************************************************
170 * Display the message body *
171 *************************************************/
174 bodyAction(Widget w, XtPointer client_data, XtPointer call_data)
177 Widget text = text_create(US client_data, text_depth);
180 w = w; /* Keep picky compilers happy */
181 call_data = call_data;
183 for (i = 0; i < (spool_is_split? 2:1); i++)
186 message_subdir[0] = i != 0 ? (US client_data)[5] : 0;
187 fname = spool_fname(US"input", message_subdir, US client_data, US"-D");
188 if ((f = fopen(CS fname, "r")))
193 text_showf(text, "Failed to open file: %s\n", strerror(errno));
199 while (Ufgets(buffer, sizeof(buffer), f) != NULL)
201 text_show(text, buffer);
202 count += Ustrlen(buffer);
203 if (count > body_max)
205 text_show(text, US"\n*** Message length exceeds BODY_MAX ***\n");
215 /*************************************************
216 * Do something to a message *
217 *************************************************/
219 /* The output is not shown in a window for non-delivery actions that succeed,
220 unless action_output is set. We can't, however, tell until we have run
221 the command whether we want the output or not, so the pipe has to be set up in
224 static void ActOnMessage(uschar *id, uschar *action, uschar *address_arg)
228 int delivery = Ustrcmp(action + Ustrlen(action) - 2, "-M") == 0;
229 uschar *quote = US"";
231 uschar *qualify = US"";
236 /* If the address arg is not empty and does not contain @ and there is a
237 qualify domain, qualify it. (But don't qualify '<>'.)*/
239 if (address_arg[0] != 0)
242 if (Ustrchr(address_arg, '@') == NULL &&
243 Ustrcmp(address_arg, "<>") != 0 &&
244 qualify_domain != NULL &&
245 qualify_domain[0] != 0)
248 qualify = qualify_domain;
251 sprintf(CS buffer, "%s %s %s %s %s %s%s%s%s%s", exim_path,
252 (alternate_config == NULL)? US"" : US"-C",
253 (alternate_config == NULL)? US"" : alternate_config,
254 action, id, quote, address_arg, at, qualify, quote);
256 /* If we know we are going to need the window, create it now. */
258 if (action_output || delivery)
260 text = text_create(id, text_depth);
261 text_showf(text, "%s\n", buffer);
264 /* Create the pipe for output. Remember, on most systems pipe[0] is
265 for reading and pipe[1] is for writing! Solaris, with its two-way
268 if (pipe(pipe_fd) != 0)
272 text = text_create(id, text_depth);
273 text_showf(text, "%s\n", buffer);
275 text_show(text, US"*** Failed to create pipe ***\n");
279 if ( fcntl(pipe_fd[0], F_SETFL, O_NONBLOCK)
280 || fcntl(pipe_fd[1], F_SETFL, O_NONBLOCK))
282 perror("set nonblocking on pipe");
286 /* Delivering a message can take some time, and we want to show the
287 output as it goes along. This requires subprocesses and is coded below. For
288 other commands, we can assume an immediate response, and so need not waste
289 resources with subprocesses. If action_output is FALSE, don't show the
295 int save_stdout = dup(1);
296 int save_stderr = dup(2);
305 rc = system(CS buffer);
310 if (action_output || rc != 0)
314 text = text_create(id, text_depth);
315 text_showf(text, "%s\n", buffer);
317 while ((count = read(pipe_fd[0], buffer, 254)) > 0)
320 text_show(text, buffer);
326 dup2(save_stdout, 1);
327 dup2(save_stderr, 2);
331 /* If action was to change the sender, and it succeeded, we have to
332 update the in-store data. */
334 if (rc == 0 && Ustrcmp(action + Ustrlen(action) - 4, "-Mes") == 0)
336 queue_item *q = find_queue(id, queue_noop, 0);
339 if (q->sender) store_free(q->sender);
340 q->sender = store_malloc(Ustrlen(address_arg) + 1);
341 Ustrcpy(q->sender, address_arg);
345 /* If configured, cause a display update and return */
347 if (action_queue_update) tick_queue_accumulator = 999999;
351 /* Message is to be delivered. Ensure that it is marked unfrozen,
352 because nothing will get written to the log to show that this has
353 happened. (Other freezing/unfreezings get logged and picked up from
356 qq = find_queue(id, queue_noop, 0);
357 if (qq != NULL) qq->frozen = FALSE;
359 /* New, asynchronous code runs in a subprocess for commands that
360 will take some time. The main process does not wait. There is a
361 SIGCHLD handler in the main program that cleans up any terminating
364 if ((pid = fork()) == 0)
381 /* Main process - set up an item for the main ticker to watch. */
383 if (pid < 0) text_showf(text, "Failed to fork: %s\n", strerror(errno)); else
385 pipe_item *p = (pipe_item *)store_malloc(sizeof(pipe_item));
389 text_show(text, US"Run out of store\n");
396 p->next = pipe_chain;
406 /*************************************************
407 * Cause a message to be delivered *
408 *************************************************/
410 static void deliverAction(Widget w, XtPointer client_data, XtPointer call_data)
412 w = w; /* Keep picky compilers happy */
413 call_data = call_data;
414 ActOnMessage(US client_data, US"-v -M", US"");
419 /*************************************************
420 * Cause a message to be Frozen *
421 *************************************************/
423 static void freezeAction(Widget w, XtPointer client_data, XtPointer call_data)
425 w = w; /* Keep picky compilers happy */
426 call_data = call_data;
427 ActOnMessage(US client_data, US"-Mf", US"");
432 /*************************************************
433 * Cause a message to be thawed *
434 *************************************************/
436 static void thawAction(Widget w, XtPointer client_data, XtPointer call_data)
438 w = w; /* Keep picky compilers happy */
439 call_data = call_data;
440 ActOnMessage(US client_data, US"-Mt", US"");
445 /*************************************************
446 * Take action using dialog data *
447 *************************************************/
449 /* This function is called after a dialog box has been filled
450 in. It is global because it is set up in the action table at
451 start-up time. If the string is empty, do nothing. */
453 XtActionProc dialogAction(Widget w, XEvent *event, String *ss, Cardinal *c)
455 uschar *s = US XawDialogGetValueString(dialog_widget);
457 w = w; /* Keep picky compilers happy */
462 XtPopdown((Widget)dialog_shell);
463 XtDestroyWidget((Widget)dialog_shell);
464 while (isspace(*s)) s++;
467 if (actioned_message[0] != 0)
468 ActOnMessage(actioned_message, action_required, s);
470 NonMessageDialogue(s); /* When called from somewhere else */
477 /*************************************************
478 * Create a dialog box *
479 *************************************************/
481 /* The focus is grabbed exclusively, so nothing else can
482 be done to the application until the box is filled in. This
483 function is also used by the Hide button handler. */
485 void create_dialog(uschar *label, uschar *value)
488 Dimension x, y, xx, yy;
489 XtTranslations pop_trans;
492 /* Get the position of a reference widget so the dialog box can be put
495 get_pos_args[0].value = (XtArgVal)(&x);
496 get_pos_args[1].value = (XtArgVal)(&y);
497 XtGetValues(dialog_ref_widget, get_pos_args, 2);
499 /* When this is not a message_specific thing, the position of the reference
500 widget is relative to the window. Get the position of the top level widget and
501 add to the position. */
503 if (dialog_ref_widget != menushell)
505 get_pos_args[0].value = (XtArgVal)(&xx);
506 get_pos_args[1].value = (XtArgVal)(&yy);
507 XtGetValues(toplevel_widget, get_pos_args, 2);
512 /* Create a transient shell for the dialog box. */
514 XtSetArg(warg[0], XtNtransientFor, queue_widget);
515 XtSetArg(warg[1], XtNx, x + 50);
516 XtSetArg(warg[2], XtNy, y + 50);
517 XtSetArg(warg[3], XtNallowShellResize, True);
518 dialog_shell = XtCreatePopupShell("forDialog", transientShellWidgetClass,
519 toplevel_widget, warg, 4);
521 /* Create the dialog box. */
523 dialog_arg[0].value = (XtArgVal)label;
524 dialog_arg[1].value = (XtArgVal)value;
525 dialog_widget = XtCreateManagedWidget("dialog", dialogWidgetClass, dialog_shell,
526 dialog_arg, XtNumber(dialog_arg));
528 /* Get the text widget from within the dialog box, give it the keyboard focus,
529 make it wider than the default, and override its translations to make Return
530 call the dialog action function. */
532 text = XtNameToWidget(dialog_widget, "value");
533 XawTextSetInsertionPoint(text, Ustrlen(value));
534 XtSetKeyboardFocus(dialog_widget, text);
535 xs_SetValues(text, 1, "width", 200);
536 pop_trans = XtParseTranslationTable(
537 "<Key>Return: dialogAction()\n");
538 XtOverrideTranslations(text, pop_trans);
540 /* Pop the thing up. */
542 XtPopup(dialog_shell, XtGrabExclusive);
550 /*************************************************
551 * Cause a recipient to be added *
552 *************************************************/
554 /* This just sets up the dialog box; the action happens when it has been filled
557 static void addrecipAction(Widget w, XtPointer client_data, XtPointer call_data)
559 w = w; /* Keep picky compilers happy */
560 call_data = call_data;
561 Ustrncpy(actioned_message, client_data, 24);
562 actioned_message[23] = '\0';
563 action_required = US"-Mar";
564 dialog_ref_widget = menushell;
565 create_dialog(US"Recipient address to add?", US"");
570 /*************************************************
571 * Cause an address to be marked delivered *
572 *************************************************/
574 static void markdelAction(Widget w, XtPointer client_data, XtPointer call_data)
576 w = w; /* Keep picky compilers happy */
577 call_data = call_data;
578 Ustrncpy(actioned_message, client_data, 24);
579 actioned_message[23] = '\0';
580 action_required = US"-Mmd";
581 dialog_ref_widget = menushell;
582 create_dialog(US"Recipient address to mark delivered?", US"");
586 /*************************************************
587 * Cause all addresses to be marked delivered *
588 *************************************************/
590 static void markalldelAction(Widget w, XtPointer client_data, XtPointer call_data)
592 w = w; /* Keep picky compilers happy */
593 call_data = call_data;
594 ActOnMessage(US client_data, US"-Mmad", US"");
598 /*************************************************
599 * Edit the message's sender *
600 *************************************************/
602 static void editsenderAction(Widget w, XtPointer client_data,
607 w = w; /* Keep picky compilers happy */
608 call_data = call_data;
609 Ustrncpy(actioned_message, client_data, 24);
610 actioned_message[23] = '\0';
611 q = find_queue(actioned_message, queue_noop, 0);
612 sender = !q ? US"" : q->sender[0] == 0 ? US"<>" : q->sender;
613 action_required = US"-Mes";
614 dialog_ref_widget = menushell;
615 create_dialog(US"New sender address?", sender);
619 /*************************************************
620 * Cause a message to be returned to sender *
621 *************************************************/
623 static void giveupAction(Widget w, XtPointer client_data, XtPointer call_data)
625 w = w; /* Keep picky compilers happy */
626 call_data = call_data;
627 ActOnMessage(US client_data, US"-v -Mg", US"");
632 /*************************************************
633 * Cause a message to be cancelled *
634 *************************************************/
636 static void removeAction(Widget w, XtPointer client_data, XtPointer call_data)
638 w = w; /* Keep picky compilers happy */
639 call_data = call_data;
640 ActOnMessage(US client_data, US"-Mrm", US"");
645 /*************************************************
646 * Display a message's headers *
647 *************************************************/
649 static void headersAction(Widget w, XtPointer client_data, XtPointer call_data)
652 header_line *h, *next;
653 Widget text = text_create(US client_data, text_depth);
656 w = w; /* Keep picky compilers happy */
657 call_data = call_data;
659 /* Remember the point in the dynamic store so we can recover to it afterwards.
660 Then use Exim's function to read the header. */
662 reset_point = store_mark();
664 sprintf(CS buffer, "%s-H", US client_data);
665 if (spool_read_header(buffer, TRUE, FALSE) != spool_read_OK)
667 if (errno == ERRNO_SPOOLFORMAT)
670 sprintf(CS big_buffer, "%s/input/%s", spool_directory, buffer);
671 if (Ustat(big_buffer, &statbuf) == 0)
672 text_showf(text, "Format error in spool file %s: size=%lu\n", buffer,
673 (unsigned long)statbuf.st_size);
674 else text_showf(text, "Format error in spool file %s\n", buffer);
676 else text_showf(text, "Read error for spool file %s\n", buffer);
677 store_reset(reset_point);
681 if (sender_address != NULL)
683 text_showf(text, "%s sender: <%s>\n", f.sender_local ? "Local" : "Remote",
687 if (recipients_list != NULL)
690 text_show(text, US"Recipients:\n");
691 for (i = 0; i < recipients_count; i++)
693 text_showf(text, " %s %s\n",
694 (tree_search(tree_nonrecipients, recipients_list[i].address) == NULL)?
695 " ":"*", recipients_list[i].address);
697 text_show(text, US"\n");
700 for (h = header_list; h != NULL; h = next)
703 text_showf(text, "%c ", h->type); /* Don't push h->text through a %s */
704 text_show(text, h->text); /* expansion as it may be v large */
707 store_reset(reset_point);
713 /*************************************************
714 * Dismiss a text window *
715 *************************************************/
717 static void dismissAction(Widget w, XtPointer client_data, XtPointer call_data)
719 pipe_item *p = pipe_chain;
721 w = w; /* Keep picky compilers happy */
722 call_data = call_data;
724 XtPopdown((Widget)client_data);
725 XtDestroyWidget((Widget)client_data);
727 /* If this is a text widget for a sub-process, clear it out of
728 the chain so that subsequent data doesn't try to use it. We have
729 to search the parents of the saved widget to see if one of them
730 is what we have just destroyed. */
734 Widget pp = p->widget;
737 if (pp == (Widget)client_data) { p->widget = NULL; return; }
746 /*************************************************
747 * Set up popup text window *
748 *************************************************/
750 static Widget text_create(uschar *name, int height)
752 Widget textshell, form, text, button;
754 /* Create a popup shell widget to display as an additional
757 textshell = XtCreatePopupShell("textshell", topLevelShellWidgetClass,
758 toplevel_widget, NULL, 0);
759 xs_SetValues(textshell, 4,
765 /* Create a form widget, containing the text widget and the
766 dismiss button widget. */
768 form = XtCreateManagedWidget("textform", formWidgetClass,
770 xs_SetValues(form, 1, "defaultDistance", 8);
772 text = XtCreateManagedWidget("texttext", asciiTextWidgetClass,
773 form, text_arg, XtNumber(text_arg));
774 xs_SetValues(text, 4,
775 "editType", XawtextAppend,
778 "translations", text_trans);
779 XawTextDisplayCaret(text, TRUE);
781 /* Use the same font as for the queue display */
783 if (queue_font != NULL)
785 XFontStruct *f = XLoadQueryFont(X_display, CS queue_font);
786 if (f != NULL) xs_SetValues(text, 1, "font", f);
789 button_arg[0].value = (XtArgVal)text;
790 button = XtCreateManagedWidget("dismiss", commandWidgetClass,
791 form, button_arg, XtNumber(button_arg));
792 XtAddCallback(button, "callback", dismissAction, (XtPointer)textshell);
794 /* Get the toplevel popup displayed, and yield the text widget so
795 that text can be put into it. */
797 XtPopup(textshell, XtGrabNone);
804 /*************************************************
805 * Set up menu in queue window *
806 *************************************************/
808 /* We have added an action table that causes this function to
809 be called, and set up button 2 in the text widgets to call it. */
811 void menu_create(Widget w, XEvent *event, String *actargs, Cardinal *count)
817 Widget src, menu_line, item_1, item_2, item_3, item_4,
818 item_5, item_6, item_7, item_8, item_9, item_10, item_11,
820 XtTranslations menu_trans = XtParseTranslationTable(
821 "<EnterWindow>: highlight()\n\
822 <LeaveWindow>: unhighlight()\n\
823 <BtnMotion>: highlight()\n\
824 <BtnUp>: MenuPopdown()notify()unhighlight()\n\
827 actargs = actargs; /* Keep picky compilers happy */
830 /* Get the sink and source and the current text pointer */
832 queue_get_arg[0].value = (XtArgVal)(&queue_text_sink);
833 queue_get_arg[1].value = (XtArgVal)(&src);
834 queue_get_arg[2].value = (XtArgVal)(&s);
835 XtGetValues(w, queue_get_arg, 3);
837 /* Find the line number of the pointer in the window, and the
838 character offset of the top lefthand of the window. */
840 line = (event->xbutton).y / XawTextSinkMaxHeight(queue_text_sink, 1);
841 p = XawTextTopPosition(w);
843 /* Find the start of the line on which the button was clicked. */
848 while (s[p] != 0 && s[p++] != '\n');
851 /* Now pointing either at 0 or 1st uschar after \n, or very 1st uschar.
852 If 0, the click was beyond the end of the data; just set up a dummy
853 menu. (Not easy to ignore as several actions are specified for the
854 mouse click and it expects this one to set up a menu.) If on a
855 continuation line, move back to the main line. */
859 menushell_arg[0].value = (XtArgVal)"No message selected";
860 menushell = XtCreatePopupShell("menu", simpleMenuWidgetClass,
861 queue_widget, menushell_arg, XtNumber(menushell_arg));
862 XtAddCallback(menushell, "popdownCallback", popdownAction, NULL);
863 xs_SetValues(menushell, 2,
864 "cursor", XCreateFontCursor(X_display, XC_arrow),
865 "translations", menu_trans);
867 /* To keep the widgets in XFree86 happy, we have to create at least one menu
868 item, it seems. (Openwindows doesn't mind a menu with no items.) Otherwise
869 there's a complaint about a zero width menu, and a crash. */
871 menu_line = XtCreateManagedWidget("line", smeLineObjectClass, menushell,
874 item_99_arg[0].value = (XtArgVal)menu_line;
875 (void)XtCreateManagedWidget("item99", smeBSBObjectClass, menushell,
876 item_99_arg, XtNumber(item_99_arg));
882 while (p > 0 && s[p+11] == ' ')
886 while (p > 0 && s[p-1] != '\n') p--;
889 /* Now pointing at first character of a main line. */
891 Ustrncpy(message_id, s+p+11, MESSAGE_ID_LENGTH);
892 message_id[MESSAGE_ID_LENGTH] = 0;
894 /* Highlight the line being menued, and save its parameters so that it
895 can be de-highlighted at popdown. */
897 highlighted_start = highlighted_end = p;
898 while (s[highlighted_end] != '\n') highlighted_end++;
900 highlighted_y = line * XawTextSinkMaxHeight(queue_text_sink, 1) + 2;
902 XawTextSinkDisplayText(queue_text_sink,
903 highlighted_x, highlighted_y,
904 highlighted_start, highlighted_end, 1);
906 /* Create the popup shell and the other widgets that comprise the menu.
907 Set the translations and pointer shape, and add the callback pointers. */
909 menushell_arg[0].value = (XtArgVal)message_id;
910 menushell = XtCreatePopupShell("menu", simpleMenuWidgetClass,
911 queue_widget, menushell_arg, XtNumber(menushell_arg));
912 XtAddCallback(menushell, "popdownCallback", popdownAction, NULL);
914 xs_SetValues(menushell, 2,
915 "cursor", XCreateFontCursor(X_display, XC_arrow),
916 "translations", menu_trans);
918 menu_line = XtCreateManagedWidget("line", smeLineObjectClass, menushell,
921 item_1_arg[0].value = (XtArgVal)menu_line;
922 item_1 = XtCreateManagedWidget("item1", smeBSBObjectClass, menushell,
923 item_1_arg, XtNumber(item_1_arg));
924 XtAddCallback(item_1, "callback", msglogAction, (XtPointer)message_id);
926 item_2_arg[0].value = (XtArgVal)item_1;
927 item_2 = XtCreateManagedWidget("item2", smeBSBObjectClass, menushell,
928 item_2_arg, XtNumber(item_2_arg));
929 XtAddCallback(item_2, "callback", headersAction, (XtPointer)message_id);
931 item_3_arg[0].value = (XtArgVal)item_2;
932 item_3 = XtCreateManagedWidget("item3", smeBSBObjectClass, menushell,
933 item_3_arg, XtNumber(item_3_arg));
934 XtAddCallback(item_3, "callback", bodyAction, (XtPointer)message_id);
936 item_4_arg[0].value = (XtArgVal)item_3;
937 item_4 = XtCreateManagedWidget("item4", smeBSBObjectClass, menushell,
938 item_4_arg, XtNumber(item_4_arg));
939 XtAddCallback(item_4, "callback", deliverAction, (XtPointer)message_id);
941 item_5_arg[0].value = (XtArgVal)item_4;
942 item_5 = XtCreateManagedWidget("item5", smeBSBObjectClass, menushell,
943 item_5_arg, XtNumber(item_5_arg));
944 XtAddCallback(item_5, "callback", freezeAction, (XtPointer)message_id);
946 item_6_arg[0].value = (XtArgVal)item_5;
947 item_6 = XtCreateManagedWidget("item6", smeBSBObjectClass, menushell,
948 item_6_arg, XtNumber(item_6_arg));
949 XtAddCallback(item_6, "callback", thawAction, (XtPointer)message_id);
951 item_7_arg[0].value = (XtArgVal)item_6;
952 item_7 = XtCreateManagedWidget("item7", smeBSBObjectClass, menushell,
953 item_7_arg, XtNumber(item_7_arg));
954 XtAddCallback(item_7, "callback", giveupAction, (XtPointer)message_id);
956 item_8_arg[0].value = (XtArgVal)item_7;
957 item_8 = XtCreateManagedWidget("item8", smeBSBObjectClass, menushell,
958 item_8_arg, XtNumber(item_8_arg));
959 XtAddCallback(item_8, "callback", removeAction, (XtPointer)message_id);
961 item_9_arg[0].value = (XtArgVal)item_8;
962 item_9 = XtCreateManagedWidget("item9", smeBSBObjectClass, menushell,
963 item_9_arg, XtNumber(item_9_arg));
965 item_10_arg[0].value = (XtArgVal)item_9;
966 item_10 = XtCreateManagedWidget("item10", smeBSBObjectClass, menushell,
967 item_10_arg, XtNumber(item_10_arg));
968 XtAddCallback(item_10, "callback", addrecipAction, (XtPointer)message_id);
970 item_11_arg[0].value = (XtArgVal)item_10;
971 item_11 = XtCreateManagedWidget("item11", smeBSBObjectClass, menushell,
972 item_11_arg, XtNumber(item_11_arg));
973 XtAddCallback(item_11, "callback", markdelAction, (XtPointer)message_id);
975 item_12_arg[0].value = (XtArgVal)item_11;
976 item_12 = XtCreateManagedWidget("item12", smeBSBObjectClass, menushell,
977 item_12_arg, XtNumber(item_12_arg));
978 XtAddCallback(item_12, "callback", markalldelAction, (XtPointer)message_id);
980 item_13_arg[0].value = (XtArgVal)item_12;
981 item_13 = XtCreateManagedWidget("item13", smeBSBObjectClass, menushell,
982 item_13_arg, XtNumber(item_13_arg));
983 XtAddCallback(item_13, "callback", editsenderAction, (XtPointer)message_id);
985 /* Arrange that the menu pops up with the first item selected. */
987 xs_SetValues(menushell, 1, "popupOnEntry", item_1);
989 /* Flag that the menu is up to suppress queue updates. */
994 /* End of em_menu.c */