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 if (highlighted_x >= 0)
121 XawTextSinkDisplayText(queue_text_sink,
122 highlighted_x, highlighted_y,
123 highlighted_start, highlighted_end, 0);
130 /*************************************************
131 * Display the message log *
132 *************************************************/
135 msglogAction(Widget w, XtPointer client_data, XtPointer call_data)
137 Widget text = text_create(US client_data, text_depth);
138 uschar * fname = NULL;
141 /* End up with the split version, so message looks right when non-exist */
143 for (int i = 0; i < (spool_is_split ? 2:1); i++)
145 message_subdir[0] = i != 0 ? (US client_data)[5] : 0;
146 fname = spool_fname(US"msglog", message_subdir, US client_data, US"");
147 if ((f = fopen(CS fname, "r")))
152 text_showf(text, "%s: %s\n", fname, strerror(errno));
156 while (Ufgets(buffer, sizeof(buffer), f) != NULL) text_show(text, buffer);
163 /*************************************************
164 * Display the message body *
165 *************************************************/
168 bodyAction(Widget w, XtPointer client_data, XtPointer call_data)
170 Widget text = text_create(US client_data, text_depth);
173 for (int i = 0; i < (spool_is_split? 2:1); i++)
176 message_subdir[0] = i != 0 ? (US client_data)[5] : 0;
177 fname = spool_fname(US"input", message_subdir, US client_data, US"-D");
178 if ((f = fopen(CS fname, "r")))
183 text_showf(text, "Failed to open file: %s\n", strerror(errno));
189 while (Ufgets(buffer, sizeof(buffer), f) != NULL)
191 text_show(text, buffer);
192 count += Ustrlen(buffer);
193 if (count > body_max)
195 text_show(text, US"\n*** Message length exceeds BODY_MAX ***\n");
205 /*************************************************
206 * Do something to a message *
207 *************************************************/
209 /* The output is not shown in a window for non-delivery actions that succeed,
210 unless action_output is set. We can't, however, tell until we have run
211 the command whether we want the output or not, so the pipe has to be set up in
214 static void ActOnMessage(uschar *id, uschar *action, uschar *address_arg)
218 int delivery = Ustrcmp(action + Ustrlen(action) - 2, "-M") == 0;
219 uschar *quote = US"";
221 uschar *qualify = US"";
226 /* If the address arg is not empty and does not contain @ and there is a
227 qualify domain, qualify it. (But don't qualify '<>'.)*/
229 if (address_arg[0] != 0)
232 if (Ustrchr(address_arg, '@') == NULL &&
233 Ustrcmp(address_arg, "<>") != 0 &&
234 qualify_domain != NULL &&
235 qualify_domain[0] != 0)
238 qualify = qualify_domain;
241 sprintf(CS buffer, "%s %s %s %s %s %s%s%s%s%s", exim_path,
242 (alternate_config == NULL)? US"" : US"-C",
243 (alternate_config == NULL)? US"" : alternate_config,
244 action, id, quote, address_arg, at, qualify, quote);
246 /* If we know we are going to need the window, create it now. */
248 if (action_output || delivery)
250 text = text_create(id, text_depth);
251 text_showf(text, "%s\n", buffer);
254 /* Create the pipe for output. Remember, on most systems pipe[0] is
255 for reading and pipe[1] is for writing! Solaris, with its two-way
258 if (pipe(pipe_fd) != 0)
262 text = text_create(id, text_depth);
263 text_showf(text, "%s\n", buffer);
265 text_show(text, US"*** Failed to create pipe ***\n");
269 if ( fcntl(pipe_fd[0], F_SETFL, O_NONBLOCK)
270 || fcntl(pipe_fd[1], F_SETFL, O_NONBLOCK))
272 perror("set nonblocking on pipe");
276 /* Delivering a message can take some time, and we want to show the
277 output as it goes along. This requires subprocesses and is coded below. For
278 other commands, we can assume an immediate response, and so need not waste
279 resources with subprocesses. If action_output is FALSE, don't show the
285 int save_stdout = dup(1);
286 int save_stderr = dup(2);
295 rc = system(CS buffer);
300 if (action_output || rc != 0)
304 text = text_create(id, text_depth);
305 text_showf(text, "%s\n", buffer);
307 while ((count = read(pipe_fd[0], buffer, 254)) > 0)
310 text_show(text, buffer);
316 dup2(save_stdout, 1);
317 dup2(save_stderr, 2);
321 /* If action was to change the sender, and it succeeded, we have to
322 update the in-store data. */
324 if (rc == 0 && Ustrcmp(action + Ustrlen(action) - 4, "-Mes") == 0)
326 queue_item *q = find_queue(id, queue_noop, 0);
329 if (q->sender) store_free(q->sender);
330 q->sender = store_malloc(Ustrlen(address_arg) + 1);
331 Ustrcpy(q->sender, address_arg);
335 /* If configured, cause a display update and return */
337 if (action_queue_update) tick_queue_accumulator = 999999;
341 /* Message is to be delivered. Ensure that it is marked unfrozen,
342 because nothing will get written to the log to show that this has
343 happened. (Other freezing/unfreezings get logged and picked up from
346 qq = find_queue(id, queue_noop, 0);
347 if (qq != NULL) qq->frozen = FALSE;
349 /* New, asynchronous code runs in a subprocess for commands that
350 will take some time. The main process does not wait. There is a
351 SIGCHLD handler in the main program that cleans up any terminating
354 if ((pid = fork()) == 0)
371 /* Main process - set up an item for the main ticker to watch. */
373 if (pid < 0) text_showf(text, "Failed to fork: %s\n", strerror(errno)); else
375 pipe_item *p = (pipe_item *)store_malloc(sizeof(pipe_item));
379 text_show(text, US"Run out of store\n");
386 p->next = pipe_chain;
396 /*************************************************
397 * Cause a message to be delivered *
398 *************************************************/
400 static void deliverAction(Widget w, XtPointer client_data, XtPointer call_data)
402 ActOnMessage(US client_data, US"-v -M", US"");
407 /*************************************************
408 * Cause a message to be Frozen *
409 *************************************************/
411 static void freezeAction(Widget w, XtPointer client_data, XtPointer call_data)
413 ActOnMessage(US client_data, US"-Mf", US"");
418 /*************************************************
419 * Cause a message to be thawed *
420 *************************************************/
422 static void thawAction(Widget w, XtPointer client_data, XtPointer call_data)
424 ActOnMessage(US client_data, US"-Mt", US"");
429 /*************************************************
430 * Take action using dialog data *
431 *************************************************/
433 /* This function is called after a dialog box has been filled
434 in. It is global because it is set up in the action table at
435 start-up time. If the string is empty, do nothing. */
437 XtActionProc 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. */
462 void create_dialog(uschar *label, uschar *value)
465 Dimension x, y, xx, yy;
466 XtTranslations pop_trans;
469 /* Get the position of a reference widget so the dialog box can be put
472 get_pos_args[0].value = (XtArgVal)(&x);
473 get_pos_args[1].value = (XtArgVal)(&y);
474 XtGetValues(dialog_ref_widget, get_pos_args, 2);
476 /* When this is not a message_specific thing, the position of the reference
477 widget is relative to the window. Get the position of the top level widget and
478 add to the position. */
480 if (dialog_ref_widget != menushell)
482 get_pos_args[0].value = (XtArgVal)(&xx);
483 get_pos_args[1].value = (XtArgVal)(&yy);
484 XtGetValues(toplevel_widget, get_pos_args, 2);
489 /* Create a transient shell for the dialog box. */
491 XtSetArg(warg[0], XtNtransientFor, queue_widget);
492 XtSetArg(warg[1], XtNx, x + 50);
493 XtSetArg(warg[2], XtNy, y + 50);
494 XtSetArg(warg[3], XtNallowShellResize, True);
495 dialog_shell = XtCreatePopupShell("forDialog", transientShellWidgetClass,
496 toplevel_widget, warg, 4);
498 /* Create the dialog box. */
500 dialog_arg[0].value = (XtArgVal)label;
501 dialog_arg[1].value = (XtArgVal)value;
502 dialog_widget = XtCreateManagedWidget("dialog", dialogWidgetClass, dialog_shell,
503 dialog_arg, XtNumber(dialog_arg));
505 /* Get the text widget from within the dialog box, give it the keyboard focus,
506 make it wider than the default, and override its translations to make Return
507 call the dialog action function. */
509 text = XtNameToWidget(dialog_widget, "value");
510 XawTextSetInsertionPoint(text, Ustrlen(value));
511 XtSetKeyboardFocus(dialog_widget, text);
512 xs_SetValues(text, 1, "width", 200);
513 pop_trans = XtParseTranslationTable(
514 "<Key>Return: dialogAction()\n");
515 XtOverrideTranslations(text, pop_trans);
517 /* Pop the thing up. */
519 XtPopup(dialog_shell, XtGrabExclusive);
527 /*************************************************
528 * Cause a recipient to be added *
529 *************************************************/
531 /* This just sets up the dialog box; the action happens when it has been filled
534 static void addrecipAction(Widget w, XtPointer client_data, XtPointer call_data)
536 Ustrncpy(actioned_message, client_data, 24);
537 actioned_message[23] = '\0';
538 action_required = US"-Mar";
539 dialog_ref_widget = menushell;
540 create_dialog(US"Recipient address to add?", US"");
545 /*************************************************
546 * Cause an address to be marked delivered *
547 *************************************************/
549 static void markdelAction(Widget w, XtPointer client_data, XtPointer call_data)
551 Ustrncpy(actioned_message, client_data, 24);
552 actioned_message[23] = '\0';
553 action_required = US"-Mmd";
554 dialog_ref_widget = menushell;
555 create_dialog(US"Recipient address to mark delivered?", US"");
559 /*************************************************
560 * Cause all addresses to be marked delivered *
561 *************************************************/
563 static void markalldelAction(Widget w, XtPointer client_data, XtPointer call_data)
565 ActOnMessage(US client_data, US"-Mmad", US"");
569 /*************************************************
570 * Edit the message's sender *
571 *************************************************/
573 static void editsenderAction(Widget w, XtPointer client_data,
579 Ustrncpy(actioned_message, client_data, 24);
580 actioned_message[23] = '\0';
581 q = find_queue(actioned_message, queue_noop, 0);
582 sender = !q ? US"" : q->sender[0] == 0 ? US"<>" : q->sender;
583 action_required = US"-Mes";
584 dialog_ref_widget = menushell;
585 create_dialog(US"New sender address?", sender);
589 /*************************************************
590 * Cause a message to be returned to sender *
591 *************************************************/
593 static void giveupAction(Widget w, XtPointer client_data, XtPointer call_data)
595 ActOnMessage(US client_data, US"-v -Mg", US"");
600 /*************************************************
601 * Cause a message to be cancelled *
602 *************************************************/
604 static void removeAction(Widget w, XtPointer client_data, XtPointer call_data)
606 ActOnMessage(US client_data, US"-Mrm", US"");
611 /*************************************************
612 * Display a message's headers *
613 *************************************************/
615 static void headersAction(Widget w, XtPointer client_data, XtPointer call_data)
618 header_line *h, *next;
619 Widget text = text_create(US client_data, text_depth);
622 /* Remember the point in the dynamic store so we can recover to it afterwards.
623 Then use Exim's function to read the header. */
625 reset_point = store_mark();
627 sprintf(CS buffer, "%s-H", US client_data);
628 if (spool_read_header(buffer, TRUE, FALSE) != spool_read_OK)
630 if (errno == ERRNO_SPOOLFORMAT)
633 sprintf(CS big_buffer, "%s/input/%s", spool_directory, buffer);
634 if (Ustat(big_buffer, &statbuf) == 0)
635 text_showf(text, "Format error in spool file %s: size=%lu\n", buffer,
636 (unsigned long)statbuf.st_size);
637 else text_showf(text, "Format error in spool file %s\n", buffer);
639 else text_showf(text, "Read error for spool file %s\n", buffer);
640 store_reset(reset_point);
646 text_showf(text, "%s sender: <%s>\n", f.sender_local ? "Local" : "Remote",
653 text_show(text, US"Recipients:\n");
654 for (i = 0; i < recipients_count; i++)
655 text_showf(text, " %s %s\n",
656 (tree_search(tree_nonrecipients, recipients_list[i].address) == NULL)?
657 " ":"*", recipients_list[i].address);
658 text_show(text, US"\n");
661 for (h = header_list; h; h = next)
664 text_showf(text, "%c ", h->type); /* Don't push h->text through a %s */
665 text_show(text, h->text); /* expansion as it may be v large */
668 store_reset(reset_point);
674 /*************************************************
675 * Dismiss a text window *
676 *************************************************/
678 static void dismissAction(Widget w, XtPointer client_data, XtPointer call_data)
680 XtPopdown((Widget)client_data);
681 XtDestroyWidget((Widget)client_data);
683 /* If this is a text widget for a sub-process, clear it out of
684 the chain so that subsequent data doesn't try to use it. We have
685 to search the parents of the saved widget to see if one of them
686 is what we have just destroyed. */
688 for (pipe_item * p = pipe_chain; p; p = p->next)
689 for (Widget pp = p->widget; pp; pp = XtParent(pp))
690 if (pp == (Widget)client_data) { p->widget = NULL; return; }
695 /*************************************************
696 * Set up popup text window *
697 *************************************************/
699 static Widget text_create(uschar *name, int height)
701 Widget textshell, form, text, button;
703 /* Create a popup shell widget to display as an additional
706 textshell = XtCreatePopupShell("textshell", topLevelShellWidgetClass,
707 toplevel_widget, NULL, 0);
708 xs_SetValues(textshell, 4,
714 /* Create a form widget, containing the text widget and the
715 dismiss button widget. */
717 form = XtCreateManagedWidget("textform", formWidgetClass,
719 xs_SetValues(form, 1, "defaultDistance", 8);
721 text = XtCreateManagedWidget("texttext", asciiTextWidgetClass,
722 form, text_arg, XtNumber(text_arg));
723 xs_SetValues(text, 4,
724 "editType", XawtextAppend,
727 "translations", text_trans);
728 XawTextDisplayCaret(text, TRUE);
730 /* Use the same font as for the queue display */
732 if (queue_font != NULL)
734 XFontStruct *f = XLoadQueryFont(X_display, CS queue_font);
735 if (f != NULL) xs_SetValues(text, 1, "font", f);
738 button_arg[0].value = (XtArgVal)text;
739 button = XtCreateManagedWidget("dismiss", commandWidgetClass,
740 form, button_arg, XtNumber(button_arg));
741 XtAddCallback(button, "callback", dismissAction, (XtPointer)textshell);
743 /* Get the toplevel popup displayed, and yield the text widget so
744 that text can be put into it. */
746 XtPopup(textshell, XtGrabNone);
753 /*************************************************
754 * Set up menu in queue window *
755 *************************************************/
757 /* We have added an action table that causes this function to
758 be called, and set up button 2 in the text widgets to call it. */
760 void menu_create(Widget w, XEvent *event, String *actargs, Cardinal *count)
766 Widget src, menu_line, item_1, item_2, item_3, item_4,
767 item_5, item_6, item_7, item_8, item_9, item_10, item_11,
769 XtTranslations menu_trans = XtParseTranslationTable(
770 "<EnterWindow>: highlight()\n\
771 <LeaveWindow>: unhighlight()\n\
772 <BtnMotion>: highlight()\n\
773 <BtnUp>: MenuPopdown()notify()unhighlight()\n\
776 /* Get the sink and source and the current text pointer */
778 queue_get_arg[0].value = (XtArgVal)(&queue_text_sink);
779 queue_get_arg[1].value = (XtArgVal)(&src);
780 queue_get_arg[2].value = (XtArgVal)(&s);
781 XtGetValues(w, queue_get_arg, 3);
783 /* Find the line number of the pointer in the window, and the
784 character offset of the top lefthand of the window. */
786 line = (event->xbutton).y / XawTextSinkMaxHeight(queue_text_sink, 1);
787 p = XawTextTopPosition(w);
789 /* Find the start of the line on which the button was clicked. */
794 while (s[p] != 0 && s[p++] != '\n');
797 /* Now pointing either at 0 or 1st uschar after \n, or very 1st uschar.
798 If 0, the click was beyond the end of the data; just set up a dummy
799 menu. (Not easy to ignore as several actions are specified for the
800 mouse click and it expects this one to set up a menu.) If on a
801 continuation line, move back to the main line. */
805 menushell_arg[0].value = (XtArgVal)"No message selected";
806 menushell = XtCreatePopupShell("menu", simpleMenuWidgetClass,
807 queue_widget, menushell_arg, XtNumber(menushell_arg));
808 XtAddCallback(menushell, "popdownCallback", popdownAction, NULL);
809 xs_SetValues(menushell, 2,
810 "cursor", XCreateFontCursor(X_display, XC_arrow),
811 "translations", menu_trans);
813 /* To keep the widgets in XFree86 happy, we have to create at least one menu
814 item, it seems. (Openwindows doesn't mind a menu with no items.) Otherwise
815 there's a complaint about a zero width menu, and a crash. */
817 menu_line = XtCreateManagedWidget("line", smeLineObjectClass, menushell,
820 item_99_arg[0].value = (XtArgVal)menu_line;
821 (void)XtCreateManagedWidget("item99", smeBSBObjectClass, menushell,
822 item_99_arg, XtNumber(item_99_arg));
828 while (p > 0 && s[p+11] == ' ')
832 while (p > 0 && s[p-1] != '\n') p--;
835 /* Now pointing at first character of a main line. */
837 Ustrncpy(message_id, s+p+11, MESSAGE_ID_LENGTH);
838 message_id[MESSAGE_ID_LENGTH] = 0;
840 /* Highlight the line being menued, and save its parameters so that it
841 can be de-highlighted at popdown. */
843 highlighted_start = highlighted_end = p;
844 while (s[highlighted_end] != '\n') highlighted_end++;
846 highlighted_y = line * XawTextSinkMaxHeight(queue_text_sink, 1) + 2;
848 XawTextSinkDisplayText(queue_text_sink,
849 highlighted_x, highlighted_y,
850 highlighted_start, highlighted_end, 1);
852 /* Create the popup shell and the other widgets that comprise the menu.
853 Set the translations and pointer shape, and add the callback pointers. */
855 menushell_arg[0].value = (XtArgVal)message_id;
856 menushell = XtCreatePopupShell("menu", simpleMenuWidgetClass,
857 queue_widget, menushell_arg, XtNumber(menushell_arg));
858 XtAddCallback(menushell, "popdownCallback", popdownAction, NULL);
860 xs_SetValues(menushell, 2,
861 "cursor", XCreateFontCursor(X_display, XC_arrow),
862 "translations", menu_trans);
864 menu_line = XtCreateManagedWidget("line", smeLineObjectClass, menushell,
867 item_1_arg[0].value = (XtArgVal)menu_line;
868 item_1 = XtCreateManagedWidget("item1", smeBSBObjectClass, menushell,
869 item_1_arg, XtNumber(item_1_arg));
870 XtAddCallback(item_1, "callback", msglogAction, (XtPointer)message_id);
872 item_2_arg[0].value = (XtArgVal)item_1;
873 item_2 = XtCreateManagedWidget("item2", smeBSBObjectClass, menushell,
874 item_2_arg, XtNumber(item_2_arg));
875 XtAddCallback(item_2, "callback", headersAction, (XtPointer)message_id);
877 item_3_arg[0].value = (XtArgVal)item_2;
878 item_3 = XtCreateManagedWidget("item3", smeBSBObjectClass, menushell,
879 item_3_arg, XtNumber(item_3_arg));
880 XtAddCallback(item_3, "callback", bodyAction, (XtPointer)message_id);
882 item_4_arg[0].value = (XtArgVal)item_3;
883 item_4 = XtCreateManagedWidget("item4", smeBSBObjectClass, menushell,
884 item_4_arg, XtNumber(item_4_arg));
885 XtAddCallback(item_4, "callback", deliverAction, (XtPointer)message_id);
887 item_5_arg[0].value = (XtArgVal)item_4;
888 item_5 = XtCreateManagedWidget("item5", smeBSBObjectClass, menushell,
889 item_5_arg, XtNumber(item_5_arg));
890 XtAddCallback(item_5, "callback", freezeAction, (XtPointer)message_id);
892 item_6_arg[0].value = (XtArgVal)item_5;
893 item_6 = XtCreateManagedWidget("item6", smeBSBObjectClass, menushell,
894 item_6_arg, XtNumber(item_6_arg));
895 XtAddCallback(item_6, "callback", thawAction, (XtPointer)message_id);
897 item_7_arg[0].value = (XtArgVal)item_6;
898 item_7 = XtCreateManagedWidget("item7", smeBSBObjectClass, menushell,
899 item_7_arg, XtNumber(item_7_arg));
900 XtAddCallback(item_7, "callback", giveupAction, (XtPointer)message_id);
902 item_8_arg[0].value = (XtArgVal)item_7;
903 item_8 = XtCreateManagedWidget("item8", smeBSBObjectClass, menushell,
904 item_8_arg, XtNumber(item_8_arg));
905 XtAddCallback(item_8, "callback", removeAction, (XtPointer)message_id);
907 item_9_arg[0].value = (XtArgVal)item_8;
908 item_9 = XtCreateManagedWidget("item9", smeBSBObjectClass, menushell,
909 item_9_arg, XtNumber(item_9_arg));
911 item_10_arg[0].value = (XtArgVal)item_9;
912 item_10 = XtCreateManagedWidget("item10", smeBSBObjectClass, menushell,
913 item_10_arg, XtNumber(item_10_arg));
914 XtAddCallback(item_10, "callback", addrecipAction, (XtPointer)message_id);
916 item_11_arg[0].value = (XtArgVal)item_10;
917 item_11 = XtCreateManagedWidget("item11", smeBSBObjectClass, menushell,
918 item_11_arg, XtNumber(item_11_arg));
919 XtAddCallback(item_11, "callback", markdelAction, (XtPointer)message_id);
921 item_12_arg[0].value = (XtArgVal)item_11;
922 item_12 = XtCreateManagedWidget("item12", smeBSBObjectClass, menushell,
923 item_12_arg, XtNumber(item_12_arg));
924 XtAddCallback(item_12, "callback", markalldelAction, (XtPointer)message_id);
926 item_13_arg[0].value = (XtArgVal)item_12;
927 item_13 = XtCreateManagedWidget("item13", smeBSBObjectClass, menushell,
928 item_13_arg, XtNumber(item_13_arg));
929 XtAddCallback(item_13, "callback", editsenderAction, (XtPointer)message_id);
931 /* Arrange that the menu pops up with the first item selected. */
933 xs_SetValues(menushell, 1, "popupOnEntry", item_1);
935 /* Flag that the menu is up to suppress queue updates. */
940 /* End of em_menu.c */