1 /*************************************************
3 *************************************************/
5 /* Copyright (c) University of Cambridge 1995 - 2018 */
6 /* Copyright (c) The Exim Maintainers 2021 */
7 /* See the file NOTICE for conditions of use and distribution. */
12 /* This module contains code for handling the popup menus. */
14 static Widget menushell;
15 static Widget queue_text_sink;
16 static Widget dialog_shell, dialog_widget;
18 static Widget text_create(uschar *, int);
20 static int highlighted_start, highlighted_end, highlighted_x, highlighted_y;
24 static Arg queue_get_arg[] = {
25 { "textSink", (XtArgVal)NULL },
26 { "textSource", (XtArgVal)NULL },
27 { "string", (XtArgVal)NULL } };
29 static Arg dialog_arg[] = {
30 { "label", (XtArgVal)"dialog" },
31 { "value", (XtArgVal)"value" } };
33 static Arg get_pos_args[] = {
34 {"x", (XtArgVal)NULL },
35 {"y", (XtArgVal)NULL } };
37 static Arg menushell_arg[] = {
38 { "label", (XtArgVal)NULL } };
40 static Arg button_arg[] = {
41 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
42 { XtNlabel, (XtArgVal) " Dismiss " },
43 { "left", XawChainLeft },
44 { "right", XawChainLeft },
45 { "top", XawChainBottom },
46 { "bottom", XawChainBottom } };
48 static Arg text_arg[] = {
49 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
50 { "editType", XawtextEdit },
51 { "string", (XtArgVal)"" }, /* dummy to get it going */
52 { "scrollVertical", XawtextScrollAlways },
53 { "wrap", XawtextWrapWord },
54 { "top", XawChainTop },
55 { "bottom", XawChainBottom } };
57 static Arg item_1_arg[] = {
58 { XtNfromVert, (XtArgVal)NULL }, /* must be first */
59 { "label", (XtArgVal)" Message log" } };
61 static Arg item_2_arg[] = {
62 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
63 { "label", (XtArgVal)" Headers" } };
65 static Arg item_3_arg[] = {
66 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
67 { "label", (XtArgVal)" Body" } };
69 static Arg item_4_arg[] = {
70 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
71 { "label", (XtArgVal)" Deliver message" } };
73 static Arg item_5_arg[] = {
74 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
75 { "label", (XtArgVal)" Freeze message" } };
77 static Arg item_6_arg[] = {
78 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
79 { "label", (XtArgVal)" Thaw message" } };
81 static Arg item_7_arg[] = {
82 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
83 { "label", (XtArgVal)" Give up on msg" } };
85 static Arg item_8_arg[] = {
86 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
87 { "label", (XtArgVal)" Remove message" } };
89 static Arg item_9_arg[] = {
90 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
91 { "label", (XtArgVal)"----------------" } };
93 static Arg item_10_arg[] = {
94 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
95 { "label", (XtArgVal)" Add recipient" } };
97 static Arg item_11_arg[] = {
98 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
99 { "label", (XtArgVal)" Mark delivered" } };
101 static Arg item_12_arg[] = {
102 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
103 { "label", (XtArgVal)" Mark all delivered" } };
105 static Arg item_13_arg[] = {
106 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
107 { "label", (XtArgVal)" Edit sender" } };
109 static Arg item_99_arg[] = {
110 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
111 { "label", (XtArgVal)" " } };
115 /*************************************************
116 * Destroy the menu when popped down *
117 *************************************************/
120 popdownAction(Widget w, XtPointer client_data, XtPointer 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)
139 Widget text = text_create(US client_data, text_depth);
140 uschar * fname = NULL;
143 /* End up with the split version, so message looks right when non-exist */
145 for (int i = 0; i < (spool_is_split ? 2:1); i++)
147 message_subdir[0] = i != 0 ? (US client_data)[5] : 0;
148 fname = spool_fname(US"msglog", message_subdir, US client_data, US"");
149 if ((f = fopen(CS fname, "r")))
154 text_showf(text, "%s: %s\n", fname, strerror(errno));
158 while (Ufgets(buffer, sizeof(buffer), f) != NULL) text_show(text, buffer);
165 /*************************************************
166 * Display the message body *
167 *************************************************/
170 bodyAction(Widget w, XtPointer client_data, XtPointer call_data)
172 Widget text = text_create(US client_data, text_depth);
175 for (int i = 0; i < (spool_is_split? 2:1); i++)
178 message_subdir[0] = i != 0 ? (US client_data)[5] : 0;
179 fname = spool_fname(US"input", message_subdir, US client_data, US"-D");
180 if ((f = fopen(CS fname, "r")))
185 text_showf(text, "Failed to open file: %s\n", strerror(errno));
191 while (Ufgets(buffer, sizeof(buffer), f) != NULL)
193 text_show(text, buffer);
194 count += Ustrlen(buffer);
195 if (count > body_max)
197 text_show(text, US"\n*** Message length exceeds BODY_MAX ***\n");
207 /*************************************************
208 * Do something to a message *
209 *************************************************/
211 /* The output is not shown in a window for non-delivery actions that succeed,
212 unless action_output is set. We can't, however, tell until we have run
213 the command whether we want the output or not, so the pipe has to be set up in
217 ActOnMessage(uschar *id, uschar *action, uschar *address_arg)
221 int delivery = Ustrcmp(action + Ustrlen(action) - 2, "-M") == 0;
222 uschar *quote = US"";
224 uschar *qualify = US"";
229 /* If the address arg is not empty and does not contain @ and there is a
230 qualify domain, qualify it. (But don't qualify '<>'.)*/
232 if (address_arg[0] != 0)
235 if (Ustrchr(address_arg, '@') == NULL &&
236 Ustrcmp(address_arg, "<>") != 0 &&
237 qualify_domain != NULL &&
238 qualify_domain[0] != 0)
241 qualify = qualify_domain;
244 sprintf(CS buffer, "%s %s %s %s %s %s%s%s%s%s", exim_path,
245 (alternate_config == NULL)? US"" : US"-C",
246 (alternate_config == NULL)? US"" : alternate_config,
247 action, id, quote, address_arg, at, qualify, quote);
249 /* If we know we are going to need the window, create it now. */
251 if (action_output || delivery)
253 text = text_create(id, text_depth);
254 text_showf(text, "%s\n", buffer);
257 /* Create the pipe for output. Remember, on most systems pipe[0] is
258 for reading and pipe[1] is for writing! Solaris, with its two-way
261 if (pipe(pipe_fd) != 0)
265 text = text_create(id, text_depth);
266 text_showf(text, "%s\n", buffer);
268 text_show(text, US"*** Failed to create pipe ***\n");
272 if ( fcntl(pipe_fd[0], F_SETFL, O_NONBLOCK)
273 || fcntl(pipe_fd[1], F_SETFL, O_NONBLOCK))
275 perror("set nonblocking on pipe");
279 /* Delivering a message can take some time, and we want to show the
280 output as it goes along. This requires subprocesses and is coded below. For
281 other commands, we can assume an immediate response, and so need not waste
282 resources with subprocesses. If action_output is FALSE, don't show the
288 int save_stdout = dup(1);
289 int save_stderr = dup(2);
298 rc = system(CS buffer);
303 if (action_output || rc != 0)
307 text = text_create(id, text_depth);
308 text_showf(text, "%s\n", buffer);
310 while ((count = read(pipe_fd[0], buffer, 254)) > 0)
313 text_show(text, buffer);
319 dup2(save_stdout, 1);
320 dup2(save_stderr, 2);
324 /* If action was to change the sender, and it succeeded, we have to
325 update the in-store data. */
327 if (rc == 0 && Ustrcmp(action + Ustrlen(action) - 4, "-Mes") == 0)
329 queue_item *q = find_queue(id, queue_noop, 0);
332 if (q->sender) store_free(q->sender);
333 q->sender = store_malloc(Ustrlen(address_arg) + 1);
334 Ustrcpy(q->sender, address_arg);
338 /* If configured, cause a display update and return */
340 if (action_queue_update) tick_queue_accumulator = 999999;
344 /* Message is to be delivered. Ensure that it is marked unfrozen,
345 because nothing will get written to the log to show that this has
346 happened. (Other freezing/unfreezings get logged and picked up from
349 qq = find_queue(id, queue_noop, 0);
350 if (qq != NULL) qq->frozen = FALSE;
352 /* New, asynchronous code runs in a subprocess for commands that
353 will take some time. The main process does not wait. There is a
354 SIGCHLD handler in the main program that cleans up any terminating
357 if ((pid = fork()) == 0)
374 /* Main process - set up an item for the main ticker to watch. */
376 if (pid < 0) text_showf(text, "Failed to fork: %s\n", strerror(errno)); else
378 pipe_item *p = (pipe_item *)store_malloc(sizeof(pipe_item));
382 text_show(text, US"Run out of store\n");
389 p->next = pipe_chain;
399 /*************************************************
400 * Cause a message to be delivered *
401 *************************************************/
404 deliverAction(Widget w, XtPointer client_data, XtPointer call_data)
406 ActOnMessage(US client_data, US"-v -M", US"");
409 /*************************************************
410 * Cause a message to be Frozen *
411 *************************************************/
414 freezeAction(Widget w, XtPointer client_data, XtPointer call_data)
416 ActOnMessage(US client_data, US"-Mf", US"");
419 /*************************************************
420 * Cause a message to be thawed *
421 *************************************************/
424 thawAction(Widget w, XtPointer client_data, XtPointer call_data)
426 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. */
438 dialogAction(Widget w, XEvent *event, String *ss, Cardinal *c)
440 uschar *s = US XawDialogGetValueString(dialog_widget);
442 XtPopdown((Widget)dialog_shell);
443 XtDestroyWidget((Widget)dialog_shell);
444 while (isspace(*s)) s++;
446 if (actioned_message[0] != 0)
447 ActOnMessage(actioned_message, action_required, s);
449 NonMessageDialogue(s); /* When called from somewhere else */
455 /*************************************************
456 * Create a dialog box *
457 *************************************************/
459 /* The focus is grabbed exclusively, so nothing else can
460 be done to the application until the box is filled in. This
461 function is also used by the Hide button handler. */
464 create_dialog(uschar *label, uschar *value)
467 Dimension x, y, xx, yy;
468 XtTranslations pop_trans;
471 /* Get the position of a reference widget so the dialog box can be put
474 get_pos_args[0].value = (XtArgVal)(&x);
475 get_pos_args[1].value = (XtArgVal)(&y);
476 XtGetValues(dialog_ref_widget, get_pos_args, 2);
478 /* When this is not a message_specific thing, the position of the reference
479 widget is relative to the window. Get the position of the top level widget and
480 add to the position. */
482 if (dialog_ref_widget != menushell)
484 get_pos_args[0].value = (XtArgVal)(&xx);
485 get_pos_args[1].value = (XtArgVal)(&yy);
486 XtGetValues(toplevel_widget, get_pos_args, 2);
491 /* Create a transient shell for the dialog box. */
493 XtSetArg(warg[0], XtNtransientFor, queue_widget);
494 XtSetArg(warg[1], XtNx, x + 50);
495 XtSetArg(warg[2], XtNy, y + 50);
496 XtSetArg(warg[3], XtNallowShellResize, True);
497 dialog_shell = XtCreatePopupShell("forDialog", transientShellWidgetClass,
498 toplevel_widget, warg, 4);
500 /* Create the dialog box. */
502 dialog_arg[0].value = (XtArgVal)label;
503 dialog_arg[1].value = (XtArgVal)value;
504 dialog_widget = XtCreateManagedWidget("dialog", dialogWidgetClass, dialog_shell,
505 dialog_arg, XtNumber(dialog_arg));
507 /* Get the text widget from within the dialog box, give it the keyboard focus,
508 make it wider than the default, and override its translations to make Return
509 call the dialog action function. */
511 text = XtNameToWidget(dialog_widget, "value");
512 XawTextSetInsertionPoint(text, Ustrlen(value));
513 XtSetKeyboardFocus(dialog_widget, text);
514 xs_SetValues(text, 1, "width", 200);
515 pop_trans = XtParseTranslationTable(
516 "<Key>Return: dialogAction()\n");
517 XtOverrideTranslations(text, pop_trans);
519 /* Pop the thing up. */
521 XtPopup(dialog_shell, XtGrabExclusive);
526 /*************************************************
527 * Cause a recipient to be added *
528 *************************************************/
530 /* This just sets up the dialog box; the action happens when it has been filled
534 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"");
543 /*************************************************
544 * Cause an address to be marked delivered *
545 *************************************************/
548 markdelAction(Widget w, XtPointer client_data, XtPointer call_data)
550 Ustrncpy(actioned_message, client_data, 24);
551 actioned_message[23] = '\0';
552 action_required = US"-Mmd";
553 dialog_ref_widget = menushell;
554 create_dialog(US"Recipient address to mark delivered?", US"");
557 /*************************************************
558 * Cause all addresses to be marked delivered *
559 *************************************************/
562 markalldelAction(Widget w, XtPointer client_data, XtPointer call_data)
564 ActOnMessage(US client_data, US"-Mmad", US"");
567 /*************************************************
568 * Edit the message's sender *
569 *************************************************/
572 editsenderAction(Widget w, XtPointer client_data, XtPointer call_data)
577 Ustrncpy(actioned_message, client_data, 24);
578 actioned_message[23] = '\0';
579 q = find_queue(actioned_message, queue_noop, 0);
580 sender = !q ? US"" : q->sender[0] == 0 ? US"<>" : q->sender;
581 action_required = US"-Mes";
582 dialog_ref_widget = menushell;
583 create_dialog(US"New sender address?", sender);
586 /*************************************************
587 * Cause a message to be returned to sender *
588 *************************************************/
591 giveupAction(Widget w, XtPointer client_data, XtPointer call_data)
593 ActOnMessage(US client_data, US"-v -Mg", US"");
596 /*************************************************
597 * Cause a message to be cancelled *
598 *************************************************/
601 removeAction(Widget w, XtPointer client_data, XtPointer call_data)
603 ActOnMessage(US client_data, US"-Mrm", US"");
606 /*************************************************
607 * Display a message's headers *
608 *************************************************/
611 headersAction(Widget w, XtPointer client_data, XtPointer call_data)
614 Widget text = text_create(US client_data, text_depth);
617 /* Remember the point in the dynamic store so we can recover to it afterwards.
618 Then use Exim's function to read the header. */
620 reset_point = store_mark();
622 sprintf(CS buffer, "%s-H", US client_data);
623 if (spool_read_header(buffer, TRUE, FALSE) != spool_read_OK)
625 if (errno == ERRNO_SPOOLFORMAT)
628 sprintf(CS big_buffer, "%s/input/%s", spool_directory, buffer);
629 if (Ustat(big_buffer, &statbuf) == 0)
630 text_showf(text, "Format error in spool file %s: size=%lu\n", buffer,
631 (unsigned long)statbuf.st_size);
632 else text_showf(text, "Format error in spool file %s\n", buffer);
634 else text_showf(text, "Read error for spool file %s\n", buffer);
635 store_reset(reset_point);
640 text_showf(text, "%s sender: <%s>\n", f.sender_local ? "Local" : "Remote",
645 text_show(text, US"Recipients:\n");
646 for (int i = 0; i < recipients_count; i++)
647 text_showf(text, " %s %s\n",
648 tree_search(tree_nonrecipients, recipients_list[i].address)
650 recipients_list[i].address);
651 text_show(text, US"\n");
654 for (header_line * next, * h = header_list; h; h = next)
657 text_showf(text, "%c ", h->type); /* Don't push h->text through a %s */
658 text_show(text, h->text); /* expansion as it may be v large */
661 store_reset(reset_point);
664 /*************************************************
665 * Dismiss a text window *
666 *************************************************/
669 dismissAction(Widget w, XtPointer client_data, XtPointer call_data)
671 XtPopdown((Widget)client_data);
672 XtDestroyWidget((Widget)client_data);
674 /* If this is a text widget for a sub-process, clear it out of
675 the chain so that subsequent data doesn't try to use it. We have
676 to search the parents of the saved widget to see if one of them
677 is what we have just destroyed. */
679 for (pipe_item * p = pipe_chain; p; p = p->next)
680 for (Widget pp = p->widget; pp; pp = XtParent(pp))
681 if (pp == (Widget)client_data) { p->widget = NULL; return; }
686 /*************************************************
687 * Set up popup text window *
688 *************************************************/
691 text_create(uschar *name, int height)
693 Widget textshell, form, text, button;
695 /* Create a popup shell widget to display as an additional
698 textshell = XtCreatePopupShell("textshell", topLevelShellWidgetClass,
699 toplevel_widget, NULL, 0);
700 xs_SetValues(textshell, 4,
706 /* Create a form widget, containing the text widget and the
707 dismiss button widget. */
709 form = XtCreateManagedWidget("textform", formWidgetClass,
711 xs_SetValues(form, 1, "defaultDistance", 8);
713 text = XtCreateManagedWidget("texttext", asciiTextWidgetClass,
714 form, text_arg, XtNumber(text_arg));
715 xs_SetValues(text, 4,
716 "editType", XawtextAppend,
719 "translations", text_trans);
720 XawTextDisplayCaret(text, TRUE);
722 /* Use the same font as for the queue display */
724 if (queue_font != NULL)
726 XFontStruct *f = XLoadQueryFont(X_display, CS queue_font);
727 if (f != NULL) xs_SetValues(text, 1, "font", f);
730 button_arg[0].value = (XtArgVal)text;
731 button = XtCreateManagedWidget("dismiss", commandWidgetClass,
732 form, button_arg, XtNumber(button_arg));
733 XtAddCallback(button, "callback", dismissAction, (XtPointer)textshell);
735 /* Get the toplevel popup displayed, and yield the text widget so
736 that text can be put into it. */
738 XtPopup(textshell, XtGrabNone);
742 /*************************************************
743 * Set up menu in queue window *
744 *************************************************/
746 /* We have added an action table that causes this function to
747 be called, and set up button 2 in the text widgets to call it. */
750 menu_create(Widget w, XEvent *event, String *actargs, Cardinal *count)
756 Widget src, menu_line, item_1, item_2, item_3, item_4,
757 item_5, item_6, item_7, item_8, item_9, item_10, item_11,
759 XtTranslations menu_trans = XtParseTranslationTable(
760 "<EnterWindow>: highlight()\n\
761 <LeaveWindow>: unhighlight()\n\
762 <BtnMotion>: highlight()\n\
763 <BtnUp>: MenuPopdown()notify()unhighlight()\n\
766 /* Get the sink and source and the current text pointer */
768 queue_get_arg[0].value = (XtArgVal)(&queue_text_sink);
769 queue_get_arg[1].value = (XtArgVal)(&src);
770 queue_get_arg[2].value = (XtArgVal)(&s);
771 XtGetValues(w, queue_get_arg, 3);
773 /* Find the line number of the pointer in the window, and the
774 character offset of the top lefthand of the window. */
776 line = (event->xbutton).y / XawTextSinkMaxHeight(queue_text_sink, 1);
777 p = XawTextTopPosition(w);
779 /* Find the start of the line on which the button was clicked. */
784 while (s[p] != 0 && s[p++] != '\n');
787 /* Now pointing either at 0 or 1st uschar after \n, or very 1st uschar.
788 If 0, the click was beyond the end of the data; just set up a dummy
789 menu. (Not easy to ignore as several actions are specified for the
790 mouse click and it expects this one to set up a menu.) If on a
791 continuation line, move back to the main line. */
795 menushell_arg[0].value = (XtArgVal)"No message selected";
796 menushell = XtCreatePopupShell("menu", simpleMenuWidgetClass,
797 queue_widget, menushell_arg, XtNumber(menushell_arg));
798 XtAddCallback(menushell, "popdownCallback", popdownAction, NULL);
799 xs_SetValues(menushell, 2,
800 "cursor", XCreateFontCursor(X_display, XC_arrow),
801 "translations", menu_trans);
803 /* To keep the widgets in XFree86 happy, we have to create at least one menu
804 item, it seems. (Openwindows doesn't mind a menu with no items.) Otherwise
805 there's a complaint about a zero width menu, and a crash. */
807 menu_line = XtCreateManagedWidget("line", smeLineObjectClass, menushell,
810 item_99_arg[0].value = (XtArgVal)menu_line;
811 (void)XtCreateManagedWidget("item99", smeBSBObjectClass, menushell,
812 item_99_arg, XtNumber(item_99_arg));
818 while (p > 0 && s[p+11] == ' ')
822 while (p > 0 && s[p-1] != '\n') p--;
825 /* Now pointing at first character of a main line. */
827 Ustrncpy(message_id, s+p+11, MESSAGE_ID_LENGTH);
828 message_id[MESSAGE_ID_LENGTH] = 0;
830 /* Highlight the line being menued, and save its parameters so that it
831 can be de-highlighted at popdown. */
833 highlighted_start = highlighted_end = p;
834 while (s[highlighted_end] != '\n') highlighted_end++;
836 highlighted_y = line * XawTextSinkMaxHeight(queue_text_sink, 1) + 2;
838 XawTextSinkDisplayText(queue_text_sink,
839 highlighted_x, highlighted_y,
840 highlighted_start, highlighted_end, 1);
842 /* Create the popup shell and the other widgets that comprise the menu.
843 Set the translations and pointer shape, and add the callback pointers. */
845 menushell_arg[0].value = (XtArgVal)message_id;
846 menushell = XtCreatePopupShell("menu", simpleMenuWidgetClass,
847 queue_widget, menushell_arg, XtNumber(menushell_arg));
848 XtAddCallback(menushell, "popdownCallback", popdownAction, NULL);
850 xs_SetValues(menushell, 2,
851 "cursor", XCreateFontCursor(X_display, XC_arrow),
852 "translations", menu_trans);
854 menu_line = XtCreateManagedWidget("line", smeLineObjectClass, menushell,
857 item_1_arg[0].value = (XtArgVal)menu_line;
858 item_1 = XtCreateManagedWidget("item1", smeBSBObjectClass, menushell,
859 item_1_arg, XtNumber(item_1_arg));
860 XtAddCallback(item_1, "callback", msglogAction, (XtPointer)message_id);
862 item_2_arg[0].value = (XtArgVal)item_1;
863 item_2 = XtCreateManagedWidget("item2", smeBSBObjectClass, menushell,
864 item_2_arg, XtNumber(item_2_arg));
865 XtAddCallback(item_2, "callback", headersAction, (XtPointer)message_id);
867 item_3_arg[0].value = (XtArgVal)item_2;
868 item_3 = XtCreateManagedWidget("item3", smeBSBObjectClass, menushell,
869 item_3_arg, XtNumber(item_3_arg));
870 XtAddCallback(item_3, "callback", bodyAction, (XtPointer)message_id);
872 item_4_arg[0].value = (XtArgVal)item_3;
873 item_4 = XtCreateManagedWidget("item4", smeBSBObjectClass, menushell,
874 item_4_arg, XtNumber(item_4_arg));
875 XtAddCallback(item_4, "callback", deliverAction, (XtPointer)message_id);
877 item_5_arg[0].value = (XtArgVal)item_4;
878 item_5 = XtCreateManagedWidget("item5", smeBSBObjectClass, menushell,
879 item_5_arg, XtNumber(item_5_arg));
880 XtAddCallback(item_5, "callback", freezeAction, (XtPointer)message_id);
882 item_6_arg[0].value = (XtArgVal)item_5;
883 item_6 = XtCreateManagedWidget("item6", smeBSBObjectClass, menushell,
884 item_6_arg, XtNumber(item_6_arg));
885 XtAddCallback(item_6, "callback", thawAction, (XtPointer)message_id);
887 item_7_arg[0].value = (XtArgVal)item_6;
888 item_7 = XtCreateManagedWidget("item7", smeBSBObjectClass, menushell,
889 item_7_arg, XtNumber(item_7_arg));
890 XtAddCallback(item_7, "callback", giveupAction, (XtPointer)message_id);
892 item_8_arg[0].value = (XtArgVal)item_7;
893 item_8 = XtCreateManagedWidget("item8", smeBSBObjectClass, menushell,
894 item_8_arg, XtNumber(item_8_arg));
895 XtAddCallback(item_8, "callback", removeAction, (XtPointer)message_id);
897 item_9_arg[0].value = (XtArgVal)item_8;
898 item_9 = XtCreateManagedWidget("item9", smeBSBObjectClass, menushell,
899 item_9_arg, XtNumber(item_9_arg));
901 item_10_arg[0].value = (XtArgVal)item_9;
902 item_10 = XtCreateManagedWidget("item10", smeBSBObjectClass, menushell,
903 item_10_arg, XtNumber(item_10_arg));
904 XtAddCallback(item_10, "callback", addrecipAction, (XtPointer)message_id);
906 item_11_arg[0].value = (XtArgVal)item_10;
907 item_11 = XtCreateManagedWidget("item11", smeBSBObjectClass, menushell,
908 item_11_arg, XtNumber(item_11_arg));
909 XtAddCallback(item_11, "callback", markdelAction, (XtPointer)message_id);
911 item_12_arg[0].value = (XtArgVal)item_11;
912 item_12 = XtCreateManagedWidget("item12", smeBSBObjectClass, menushell,
913 item_12_arg, XtNumber(item_12_arg));
914 XtAddCallback(item_12, "callback", markalldelAction, (XtPointer)message_id);
916 item_13_arg[0].value = (XtArgVal)item_12;
917 item_13 = XtCreateManagedWidget("item13", smeBSBObjectClass, menushell,
918 item_13_arg, XtNumber(item_13_arg));
919 XtAddCallback(item_13, "callback", editsenderAction, (XtPointer)message_id);
921 /* Arrange that the menu pops up with the first item selected. */
923 xs_SetValues(menushell, 1, "popupOnEntry", item_1);
925 /* Flag that the menu is up to suppress queue updates. */
930 /* End of em_menu.c */