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 *************************************************/
119 popdownAction(Widget w, XtPointer client_data, XtPointer call_data)
121 if (highlighted_x >= 0)
122 XawTextSinkDisplayText(queue_text_sink,
123 highlighted_x, highlighted_y,
124 highlighted_start, highlighted_end, 0);
131 /*************************************************
132 * Display the message log *
133 *************************************************/
136 msglogAction(Widget w, XtPointer client_data, XtPointer call_data)
138 Widget text = text_create(US client_data, text_depth);
139 uschar * fname = NULL;
142 /* End up with the split version, so message looks right when non-exist */
144 for (int i = 0; i < (spool_is_split ? 2:1); i++)
146 message_subdir[0] = i != 0 ? (US client_data)[5] : 0;
147 fname = spool_fname(US"msglog", message_subdir, US client_data, US"");
148 if ((f = fopen(CS fname, "r")))
153 text_showf(text, "%s: %s\n", fname, strerror(errno));
157 while (Ufgets(buffer, sizeof(buffer), f) != NULL) text_show(text, buffer);
164 /*************************************************
165 * Display the message body *
166 *************************************************/
169 bodyAction(Widget w, XtPointer client_data, XtPointer call_data)
171 Widget text = text_create(US client_data, text_depth);
174 for (int i = 0; i < (spool_is_split? 2:1); i++)
177 message_subdir[0] = i != 0 ? (US client_data)[5] : 0;
178 fname = spool_fname(US"input", message_subdir, US client_data, US"-D");
179 if ((f = fopen(CS fname, "r")))
184 text_showf(text, "Failed to open file: %s\n", strerror(errno));
190 while (Ufgets(buffer, sizeof(buffer), f) != NULL)
192 text_show(text, buffer);
193 count += Ustrlen(buffer);
194 if (count > body_max)
196 text_show(text, US"\n*** Message length exceeds BODY_MAX ***\n");
206 /*************************************************
207 * Do something to a message *
208 *************************************************/
210 /* The output is not shown in a window for non-delivery actions that succeed,
211 unless action_output is set. We can't, however, tell until we have run
212 the command whether we want the output or not, so the pipe has to be set up in
216 ActOnMessage(uschar *id, uschar *action, uschar *address_arg)
220 int delivery = Ustrcmp(action + Ustrlen(action) - 2, "-M") == 0;
221 uschar *quote = US"";
223 uschar *qualify = US"";
228 /* If the address arg is not empty and does not contain @ and there is a
229 qualify domain, qualify it. (But don't qualify '<>'.)*/
231 if (address_arg[0] != 0)
234 if (Ustrchr(address_arg, '@') == NULL &&
235 Ustrcmp(address_arg, "<>") != 0 &&
236 qualify_domain != NULL &&
237 qualify_domain[0] != 0)
240 qualify = qualify_domain;
243 sprintf(CS buffer, "%s %s %s %s %s %s%s%s%s%s", exim_path,
244 (alternate_config == NULL)? US"" : US"-C",
245 (alternate_config == NULL)? US"" : alternate_config,
246 action, id, quote, address_arg, at, qualify, quote);
248 /* If we know we are going to need the window, create it now. */
250 if (action_output || delivery)
252 text = text_create(id, text_depth);
253 text_showf(text, "%s\n", buffer);
256 /* Create the pipe for output. Remember, on most systems pipe[0] is
257 for reading and pipe[1] is for writing! Solaris, with its two-way
260 if (pipe(pipe_fd) != 0)
264 text = text_create(id, text_depth);
265 text_showf(text, "%s\n", buffer);
267 text_show(text, US"*** Failed to create pipe ***\n");
271 if ( fcntl(pipe_fd[0], F_SETFL, O_NONBLOCK)
272 || fcntl(pipe_fd[1], F_SETFL, O_NONBLOCK))
274 perror("set nonblocking on pipe");
278 /* Delivering a message can take some time, and we want to show the
279 output as it goes along. This requires subprocesses and is coded below. For
280 other commands, we can assume an immediate response, and so need not waste
281 resources with subprocesses. If action_output is FALSE, don't show the
287 int save_stdout = dup(1);
288 int save_stderr = dup(2);
297 rc = system(CS buffer);
302 if (action_output || rc != 0)
306 text = text_create(id, text_depth);
307 text_showf(text, "%s\n", buffer);
309 while ((count = read(pipe_fd[0], buffer, 254)) > 0)
312 text_show(text, buffer);
318 dup2(save_stdout, 1);
319 dup2(save_stderr, 2);
323 /* If action was to change the sender, and it succeeded, we have to
324 update the in-store data. */
326 if (rc == 0 && Ustrcmp(action + Ustrlen(action) - 4, "-Mes") == 0)
328 queue_item *q = find_queue(id, queue_noop, 0);
331 if (q->sender) store_free(q->sender);
332 q->sender = store_malloc(Ustrlen(address_arg) + 1);
333 Ustrcpy(q->sender, address_arg);
337 /* If configured, cause a display update and return */
339 if (action_queue_update) tick_queue_accumulator = 999999;
343 /* Message is to be delivered. Ensure that it is marked unfrozen,
344 because nothing will get written to the log to show that this has
345 happened. (Other freezing/unfreezings get logged and picked up from
348 qq = find_queue(id, queue_noop, 0);
349 if (qq != NULL) qq->frozen = FALSE;
351 /* New, asynchronous code runs in a subprocess for commands that
352 will take some time. The main process does not wait. There is a
353 SIGCHLD handler in the main program that cleans up any terminating
356 if ((pid = fork()) == 0)
373 /* Main process - set up an item for the main ticker to watch. */
375 if (pid < 0) text_showf(text, "Failed to fork: %s\n", strerror(errno)); else
377 pipe_item *p = (pipe_item *)store_malloc(sizeof(pipe_item));
381 text_show(text, US"Run out of store\n");
388 p->next = pipe_chain;
398 /*************************************************
399 * Cause a message to be delivered *
400 *************************************************/
403 deliverAction(Widget w, XtPointer client_data, XtPointer call_data)
405 ActOnMessage(US client_data, US"-v -M", US"");
408 /*************************************************
409 * Cause a message to be Frozen *
410 *************************************************/
413 freezeAction(Widget w, XtPointer client_data, XtPointer call_data)
415 ActOnMessage(US client_data, US"-Mf", US"");
418 /*************************************************
419 * Cause a message to be thawed *
420 *************************************************/
423 thawAction(Widget w, XtPointer client_data, XtPointer call_data)
425 ActOnMessage(US client_data, US"-Mt", US"");
428 /*************************************************
429 * Take action using dialog data *
430 *************************************************/
432 /* This function is called after a dialog box has been filled
433 in. It is global because it is set up in the action table at
434 start-up time. If the string is empty, do nothing. */
437 dialogAction(Widget w, XEvent *event, String *ss, Cardinal *c)
439 uschar *s = US XawDialogGetValueString(dialog_widget);
441 XtPopdown((Widget)dialog_shell);
442 XtDestroyWidget((Widget)dialog_shell);
443 while (isspace(*s)) s++;
445 if (actioned_message[0] != 0)
446 ActOnMessage(actioned_message, action_required, s);
448 NonMessageDialogue(s); /* When called from somewhere else */
454 /*************************************************
455 * Create a dialog box *
456 *************************************************/
458 /* The focus is grabbed exclusively, so nothing else can
459 be done to the application until the box is filled in. This
460 function is also used by the Hide button handler. */
463 create_dialog(uschar *label, uschar *value)
466 Dimension x, y, xx, yy;
467 XtTranslations pop_trans;
470 /* Get the position of a reference widget so the dialog box can be put
473 get_pos_args[0].value = (XtArgVal)(&x);
474 get_pos_args[1].value = (XtArgVal)(&y);
475 XtGetValues(dialog_ref_widget, get_pos_args, 2);
477 /* When this is not a message_specific thing, the position of the reference
478 widget is relative to the window. Get the position of the top level widget and
479 add to the position. */
481 if (dialog_ref_widget != menushell)
483 get_pos_args[0].value = (XtArgVal)(&xx);
484 get_pos_args[1].value = (XtArgVal)(&yy);
485 XtGetValues(toplevel_widget, get_pos_args, 2);
490 /* Create a transient shell for the dialog box. */
492 XtSetArg(warg[0], XtNtransientFor, queue_widget);
493 XtSetArg(warg[1], XtNx, x + 50);
494 XtSetArg(warg[2], XtNy, y + 50);
495 XtSetArg(warg[3], XtNallowShellResize, True);
496 dialog_shell = XtCreatePopupShell("forDialog", transientShellWidgetClass,
497 toplevel_widget, warg, 4);
499 /* Create the dialog box. */
501 dialog_arg[0].value = (XtArgVal)label;
502 dialog_arg[1].value = (XtArgVal)value;
503 dialog_widget = XtCreateManagedWidget("dialog", dialogWidgetClass, dialog_shell,
504 dialog_arg, XtNumber(dialog_arg));
506 /* Get the text widget from within the dialog box, give it the keyboard focus,
507 make it wider than the default, and override its translations to make Return
508 call the dialog action function. */
510 text = XtNameToWidget(dialog_widget, "value");
511 XawTextSetInsertionPoint(text, Ustrlen(value));
512 XtSetKeyboardFocus(dialog_widget, text);
513 xs_SetValues(text, 1, "width", 200);
514 pop_trans = XtParseTranslationTable(
515 "<Key>Return: dialogAction()\n");
516 XtOverrideTranslations(text, pop_trans);
518 /* Pop the thing up. */
520 XtPopup(dialog_shell, XtGrabExclusive);
525 /*************************************************
526 * Cause a recipient to be added *
527 *************************************************/
529 /* This just sets up the dialog box; the action happens when it has been filled
533 addrecipAction(Widget w, XtPointer client_data, XtPointer call_data)
535 Ustrncpy(actioned_message, client_data, 24);
536 actioned_message[23] = '\0';
537 action_required = US"-Mar";
538 dialog_ref_widget = menushell;
539 create_dialog(US"Recipient address to add?", US"");
542 /*************************************************
543 * Cause an address to be marked delivered *
544 *************************************************/
547 markdelAction(Widget w, XtPointer client_data, XtPointer call_data)
549 Ustrncpy(actioned_message, client_data, 24);
550 actioned_message[23] = '\0';
551 action_required = US"-Mmd";
552 dialog_ref_widget = menushell;
553 create_dialog(US"Recipient address to mark delivered?", US"");
556 /*************************************************
557 * Cause all addresses to be marked delivered *
558 *************************************************/
561 markalldelAction(Widget w, XtPointer client_data, XtPointer call_data)
563 ActOnMessage(US client_data, US"-Mmad", US"");
566 /*************************************************
567 * Edit the message's sender *
568 *************************************************/
571 editsenderAction(Widget w, XtPointer client_data, XtPointer call_data)
576 Ustrncpy(actioned_message, client_data, 24);
577 actioned_message[23] = '\0';
578 q = find_queue(actioned_message, queue_noop, 0);
579 sender = !q ? US"" : q->sender[0] == 0 ? US"<>" : q->sender;
580 action_required = US"-Mes";
581 dialog_ref_widget = menushell;
582 create_dialog(US"New sender address?", sender);
585 /*************************************************
586 * Cause a message to be returned to sender *
587 *************************************************/
590 giveupAction(Widget w, XtPointer client_data, XtPointer call_data)
592 ActOnMessage(US client_data, US"-v -Mg", US"");
595 /*************************************************
596 * Cause a message to be cancelled *
597 *************************************************/
600 removeAction(Widget w, XtPointer client_data, XtPointer call_data)
602 ActOnMessage(US client_data, US"-Mrm", US"");
605 /*************************************************
606 * Display a message's headers *
607 *************************************************/
610 headersAction(Widget w, XtPointer client_data, XtPointer call_data)
613 Widget text = text_create(US client_data, text_depth);
616 /* Remember the point in the dynamic store so we can recover to it afterwards.
617 Then use Exim's function to read the header. */
619 reset_point = store_mark();
621 sprintf(CS buffer, "%s-H", US client_data);
622 if (spool_read_header(buffer, TRUE, FALSE) != spool_read_OK)
624 if (errno == ERRNO_SPOOLFORMAT)
627 sprintf(CS big_buffer, "%s/input/%s", spool_directory, buffer);
628 if (Ustat(big_buffer, &statbuf) == 0)
629 text_showf(text, "Format error in spool file %s: size=%lu\n", buffer,
630 (unsigned long)statbuf.st_size);
631 else text_showf(text, "Format error in spool file %s\n", buffer);
633 else text_showf(text, "Read error for spool file %s\n", buffer);
634 store_reset(reset_point);
639 text_showf(text, "%s sender: <%s>\n", f.sender_local ? "Local" : "Remote",
644 text_show(text, US"Recipients:\n");
645 for (int i = 0; i < recipients_count; i++)
646 text_showf(text, " %s %s\n",
647 tree_search(tree_nonrecipients, recipients_list[i].address)
649 recipients_list[i].address);
650 text_show(text, US"\n");
653 for (header_line * next, * h = header_list; h; h = next)
656 text_showf(text, "%c ", h->type); /* Don't push h->text through a %s */
657 text_show(text, h->text); /* expansion as it may be v large */
660 store_reset(reset_point);
663 /*************************************************
664 * Dismiss a text window *
665 *************************************************/
668 dismissAction(Widget w, XtPointer client_data, XtPointer call_data)
670 XtPopdown((Widget)client_data);
671 XtDestroyWidget((Widget)client_data);
673 /* If this is a text widget for a sub-process, clear it out of
674 the chain so that subsequent data doesn't try to use it. We have
675 to search the parents of the saved widget to see if one of them
676 is what we have just destroyed. */
678 for (pipe_item * p = pipe_chain; p; p = p->next)
679 for (Widget pp = p->widget; pp; pp = XtParent(pp))
680 if (pp == (Widget)client_data) { p->widget = NULL; return; }
685 /*************************************************
686 * Set up popup text window *
687 *************************************************/
690 text_create(uschar *name, int height)
692 Widget textshell, form, text, button;
694 /* Create a popup shell widget to display as an additional
697 textshell = XtCreatePopupShell("textshell", topLevelShellWidgetClass,
698 toplevel_widget, NULL, 0);
699 xs_SetValues(textshell, 4,
705 /* Create a form widget, containing the text widget and the
706 dismiss button widget. */
708 form = XtCreateManagedWidget("textform", formWidgetClass,
710 xs_SetValues(form, 1, "defaultDistance", 8);
712 text = XtCreateManagedWidget("texttext", asciiTextWidgetClass,
713 form, text_arg, XtNumber(text_arg));
714 xs_SetValues(text, 4,
715 "editType", XawtextAppend,
718 "translations", text_trans);
719 XawTextDisplayCaret(text, TRUE);
721 /* Use the same font as for the queue display */
723 if (queue_font != NULL)
725 XFontStruct *f = XLoadQueryFont(X_display, CS queue_font);
726 if (f != NULL) xs_SetValues(text, 1, "font", f);
729 button_arg[0].value = (XtArgVal)text;
730 button = XtCreateManagedWidget("dismiss", commandWidgetClass,
731 form, button_arg, XtNumber(button_arg));
732 XtAddCallback(button, "callback", dismissAction, (XtPointer)textshell);
734 /* Get the toplevel popup displayed, and yield the text widget so
735 that text can be put into it. */
737 XtPopup(textshell, XtGrabNone);
741 /*************************************************
742 * Set up menu in queue window *
743 *************************************************/
745 /* We have added an action table that causes this function to
746 be called, and set up button 2 in the text widgets to call it. */
749 menu_create(Widget w, XEvent *event, String *actargs, Cardinal *count)
755 Widget src, menu_line, item_1, item_2, item_3, item_4,
756 item_5, item_6, item_7, item_8, item_9, item_10, item_11,
758 XtTranslations menu_trans = XtParseTranslationTable(
759 "<EnterWindow>: highlight()\n\
760 <LeaveWindow>: unhighlight()\n\
761 <BtnMotion>: highlight()\n\
762 <BtnUp>: MenuPopdown()notify()unhighlight()\n\
765 /* Get the sink and source and the current text pointer */
767 queue_get_arg[0].value = (XtArgVal)(&queue_text_sink);
768 queue_get_arg[1].value = (XtArgVal)(&src);
769 queue_get_arg[2].value = (XtArgVal)(&s);
770 XtGetValues(w, queue_get_arg, 3);
772 /* Find the line number of the pointer in the window, and the
773 character offset of the top lefthand of the window. */
775 line = (event->xbutton).y / XawTextSinkMaxHeight(queue_text_sink, 1);
776 p = XawTextTopPosition(w);
778 /* Find the start of the line on which the button was clicked. */
783 while (s[p] != 0 && s[p++] != '\n');
786 /* Now pointing either at 0 or 1st uschar after \n, or very 1st uschar.
787 If 0, the click was beyond the end of the data; just set up a dummy
788 menu. (Not easy to ignore as several actions are specified for the
789 mouse click and it expects this one to set up a menu.) If on a
790 continuation line, move back to the main line. */
794 menushell_arg[0].value = (XtArgVal)"No message selected";
795 menushell = XtCreatePopupShell("menu", simpleMenuWidgetClass,
796 queue_widget, menushell_arg, XtNumber(menushell_arg));
797 XtAddCallback(menushell, "popdownCallback", popdownAction, NULL);
798 xs_SetValues(menushell, 2,
799 "cursor", XCreateFontCursor(X_display, XC_arrow),
800 "translations", menu_trans);
802 /* To keep the widgets in XFree86 happy, we have to create at least one menu
803 item, it seems. (Openwindows doesn't mind a menu with no items.) Otherwise
804 there's a complaint about a zero width menu, and a crash. */
806 menu_line = XtCreateManagedWidget("line", smeLineObjectClass, menushell,
809 item_99_arg[0].value = (XtArgVal)menu_line;
810 (void)XtCreateManagedWidget("item99", smeBSBObjectClass, menushell,
811 item_99_arg, XtNumber(item_99_arg));
817 while (p > 0 && s[p+11] == ' ')
821 while (p > 0 && s[p-1] != '\n') p--;
824 /* Now pointing at first character of a main line. */
826 Ustrncpy(message_id, s+p+11, MESSAGE_ID_LENGTH);
827 message_id[MESSAGE_ID_LENGTH] = 0;
829 /* Highlight the line being menued, and save its parameters so that it
830 can be de-highlighted at popdown. */
832 highlighted_start = highlighted_end = p;
833 while (s[highlighted_end] != '\n') highlighted_end++;
835 highlighted_y = line * XawTextSinkMaxHeight(queue_text_sink, 1) + 2;
837 XawTextSinkDisplayText(queue_text_sink,
838 highlighted_x, highlighted_y,
839 highlighted_start, highlighted_end, 1);
841 /* Create the popup shell and the other widgets that comprise the menu.
842 Set the translations and pointer shape, and add the callback pointers. */
844 menushell_arg[0].value = (XtArgVal)message_id;
845 menushell = XtCreatePopupShell("menu", simpleMenuWidgetClass,
846 queue_widget, menushell_arg, XtNumber(menushell_arg));
847 XtAddCallback(menushell, "popdownCallback", popdownAction, NULL);
849 xs_SetValues(menushell, 2,
850 "cursor", XCreateFontCursor(X_display, XC_arrow),
851 "translations", menu_trans);
853 menu_line = XtCreateManagedWidget("line", smeLineObjectClass, menushell,
856 item_1_arg[0].value = (XtArgVal)menu_line;
857 item_1 = XtCreateManagedWidget("item1", smeBSBObjectClass, menushell,
858 item_1_arg, XtNumber(item_1_arg));
859 XtAddCallback(item_1, "callback", msglogAction, (XtPointer)message_id);
861 item_2_arg[0].value = (XtArgVal)item_1;
862 item_2 = XtCreateManagedWidget("item2", smeBSBObjectClass, menushell,
863 item_2_arg, XtNumber(item_2_arg));
864 XtAddCallback(item_2, "callback", headersAction, (XtPointer)message_id);
866 item_3_arg[0].value = (XtArgVal)item_2;
867 item_3 = XtCreateManagedWidget("item3", smeBSBObjectClass, menushell,
868 item_3_arg, XtNumber(item_3_arg));
869 XtAddCallback(item_3, "callback", bodyAction, (XtPointer)message_id);
871 item_4_arg[0].value = (XtArgVal)item_3;
872 item_4 = XtCreateManagedWidget("item4", smeBSBObjectClass, menushell,
873 item_4_arg, XtNumber(item_4_arg));
874 XtAddCallback(item_4, "callback", deliverAction, (XtPointer)message_id);
876 item_5_arg[0].value = (XtArgVal)item_4;
877 item_5 = XtCreateManagedWidget("item5", smeBSBObjectClass, menushell,
878 item_5_arg, XtNumber(item_5_arg));
879 XtAddCallback(item_5, "callback", freezeAction, (XtPointer)message_id);
881 item_6_arg[0].value = (XtArgVal)item_5;
882 item_6 = XtCreateManagedWidget("item6", smeBSBObjectClass, menushell,
883 item_6_arg, XtNumber(item_6_arg));
884 XtAddCallback(item_6, "callback", thawAction, (XtPointer)message_id);
886 item_7_arg[0].value = (XtArgVal)item_6;
887 item_7 = XtCreateManagedWidget("item7", smeBSBObjectClass, menushell,
888 item_7_arg, XtNumber(item_7_arg));
889 XtAddCallback(item_7, "callback", giveupAction, (XtPointer)message_id);
891 item_8_arg[0].value = (XtArgVal)item_7;
892 item_8 = XtCreateManagedWidget("item8", smeBSBObjectClass, menushell,
893 item_8_arg, XtNumber(item_8_arg));
894 XtAddCallback(item_8, "callback", removeAction, (XtPointer)message_id);
896 item_9_arg[0].value = (XtArgVal)item_8;
897 item_9 = XtCreateManagedWidget("item9", smeBSBObjectClass, menushell,
898 item_9_arg, XtNumber(item_9_arg));
900 item_10_arg[0].value = (XtArgVal)item_9;
901 item_10 = XtCreateManagedWidget("item10", smeBSBObjectClass, menushell,
902 item_10_arg, XtNumber(item_10_arg));
903 XtAddCallback(item_10, "callback", addrecipAction, (XtPointer)message_id);
905 item_11_arg[0].value = (XtArgVal)item_10;
906 item_11 = XtCreateManagedWidget("item11", smeBSBObjectClass, menushell,
907 item_11_arg, XtNumber(item_11_arg));
908 XtAddCallback(item_11, "callback", markdelAction, (XtPointer)message_id);
910 item_12_arg[0].value = (XtArgVal)item_11;
911 item_12 = XtCreateManagedWidget("item12", smeBSBObjectClass, menushell,
912 item_12_arg, XtNumber(item_12_arg));
913 XtAddCallback(item_12, "callback", markalldelAction, (XtPointer)message_id);
915 item_13_arg[0].value = (XtArgVal)item_12;
916 item_13 = XtCreateManagedWidget("item13", smeBSBObjectClass, menushell,
917 item_13_arg, XtNumber(item_13_arg));
918 XtAddCallback(item_13, "callback", editsenderAction, (XtPointer)message_id);
920 /* Arrange that the menu pops up with the first item selected. */
922 xs_SetValues(menushell, 1, "popupOnEntry", item_1);
924 /* Flag that the menu is up to suppress queue updates. */
929 /* End of em_menu.c */