1 /*************************************************
3 *************************************************/
5 /* Copyright (c) University of Cambridge 1995 - 2009 */
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 *************************************************/
136 static void msglogAction(Widget w
, XtPointer client_data
, XtPointer call_data
)
140 Widget text
= text_create((uschar
*)client_data
, text_depth
);
143 w
= w
; /* Keep picky compilers happy */
144 call_data
= call_data
;
146 /* End up with the split version, so message looks right when non-exist */
148 for (i
= 0; i
< (spool_is_split
? 2:1); i
++)
150 message_subdir
[0] = (i
!= 0)? ((uschar
*)client_data
)[5] : 0;
151 sprintf(CS buffer
, "%s/msglog/%s/%s", spool_directory
, message_subdir
,
152 (uschar
*)client_data
);
153 f
= fopen(CS buffer
, "r");
154 if (f
!= NULL
) break;
158 text_showf(text
, "%s: %s\n", buffer
, strerror(errno
));
161 while (Ufgets(buffer
, 256, f
) != NULL
) text_show(text
, buffer
);
168 /*************************************************
169 * Display the message body *
170 *************************************************/
172 static void bodyAction(Widget w
, XtPointer client_data
, XtPointer call_data
)
176 Widget text
= text_create((uschar
*)client_data
, text_depth
);
179 w
= w
; /* Keep picky compilers happy */
180 call_data
= call_data
;
182 for (i
= 0; i
< (spool_is_split
? 2:1); i
++)
184 message_subdir
[0] = (i
!= 0)? ((uschar
*)client_data
)[5] : 0;
185 sprintf(CS buffer
, "%s/input/%s/%s-D", spool_directory
, message_subdir
,
186 (uschar
*)client_data
);
187 f
= fopen(CS buffer
, "r");
188 if (f
!= NULL
) break;
192 text_showf(text
, "Failed to open file: %s\n", strerror(errno
));
196 while (Ufgets(buffer
, 256, f
) != NULL
)
198 text_show(text
, buffer
);
199 count
+= Ustrlen(buffer
);
200 if (count
> body_max
)
202 text_show(text
, US
"\n*** Message length exceeds BODY_MAX ***\n");
212 /*************************************************
213 * Do something to a message *
214 *************************************************/
216 /* The output is not shown in a window for non-delivery actions that succeed,
217 unless action_output is set. We can't, however, tell until we have run
218 the command whether we want the output or not, so the pipe has to be set up in
221 static void ActOnMessage(uschar
*id
, uschar
*action
, uschar
*address_arg
)
225 int delivery
= Ustrcmp(action
+ Ustrlen(action
) - 2, "-M") == 0;
226 uschar
*quote
= US
"";
228 uschar
*qualify
= US
"";
233 /* If the address arg is not empty and does not contain @ and there is a
234 qualify domain, qualify it. (But don't qualify '<>'.)*/
236 if (address_arg
[0] != 0)
239 if (Ustrchr(address_arg
, '@') == NULL
&&
240 Ustrcmp(address_arg
, "<>") != 0 &&
241 qualify_domain
!= NULL
&&
242 qualify_domain
[0] != 0)
245 qualify
= qualify_domain
;
248 sprintf(CS buffer
, "%s %s %s %s %s %s%s%s%s%s", exim_path
,
249 (alternate_config
== NULL
)? US
"" : US
"-C",
250 (alternate_config
== NULL
)? US
"" : alternate_config
,
251 action
, id
, quote
, address_arg
, at
, qualify
, quote
);
253 /* If we know we are going to need the window, create it now. */
255 if (action_output
|| delivery
)
257 text
= text_create(id
, text_depth
);
258 text_showf(text
, "%s\n", buffer
);
261 /* Create the pipe for output. Remember, on most systems pipe[0] is
262 for reading and pipe[1] is for writing! Solaris, with its two-way
265 if (pipe(pipe_fd
) != 0)
269 text
= text_create(id
, text_depth
);
270 text_showf(text
, "%s\n", buffer
);
272 text_show(text
, US
"*** Failed to create pipe ***\n");
276 fcntl(pipe_fd
[0], F_SETFL
, O_NONBLOCK
);
277 fcntl(pipe_fd
[1], F_SETFL
, O_NONBLOCK
);
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
!= NULL
) 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(pid
)); 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 *************************************************/
403 static void deliverAction(Widget w
, XtPointer client_data
, XtPointer call_data
)
405 w
= w
; /* Keep picky compilers happy */
406 call_data
= call_data
;
407 ActOnMessage((uschar
*)client_data
, US
"-v -M", US
"");
412 /*************************************************
413 * Cause a message to be Frozen *
414 *************************************************/
416 static void freezeAction(Widget w
, XtPointer client_data
, XtPointer call_data
)
418 w
= w
; /* Keep picky compilers happy */
419 call_data
= call_data
;
420 ActOnMessage((uschar
*)client_data
, US
"-Mf", US
"");
425 /*************************************************
426 * Cause a message to be thawed *
427 *************************************************/
429 static void thawAction(Widget w
, XtPointer client_data
, XtPointer call_data
)
431 w
= w
; /* Keep picky compilers happy */
432 call_data
= call_data
;
433 ActOnMessage((uschar
*)client_data
, US
"-Mt", US
"");
438 /*************************************************
439 * Take action using dialog data *
440 *************************************************/
442 /* This function is called after a dialog box has been filled
443 in. It is global because it is set up in the action table at
444 start-up time. If the string is empty, do nothing. */
446 XtActionProc
dialogAction(Widget w
, XEvent
*event
, String
*ss
, Cardinal
*c
)
448 uschar
*s
= US
XawDialogGetValueString(dialog_widget
);
450 w
= w
; /* Keep picky compilers happy */
455 XtPopdown((Widget
)dialog_shell
);
456 XtDestroyWidget((Widget
)dialog_shell
);
457 while (isspace(*s
)) s
++;
460 if (actioned_message
[0] != 0)
461 ActOnMessage(actioned_message
, action_required
, s
);
463 NonMessageDialogue(s
); /* When called from somewhere else */
470 /*************************************************
471 * Create a dialog box *
472 *************************************************/
474 /* The focus is grabbed exclusively, so nothing else can
475 be done to the application until the box is filled in. This
476 function is also used by the Hide button handler. */
478 void create_dialog(uschar
*label
, uschar
*value
)
481 Dimension x
, y
, xx
, yy
;
482 XtTranslations pop_trans
;
485 /* Get the position of a reference widget so the dialog box can be put
488 get_pos_args
[0].value
= (XtArgVal
)(&x
);
489 get_pos_args
[1].value
= (XtArgVal
)(&y
);
490 XtGetValues(dialog_ref_widget
, get_pos_args
, 2);
492 /* When this is not a message_specific thing, the position of the reference
493 widget is relative to the window. Get the position of the top level widget and
494 add to the position. */
496 if (dialog_ref_widget
!= menushell
)
498 get_pos_args
[0].value
= (XtArgVal
)(&xx
);
499 get_pos_args
[1].value
= (XtArgVal
)(&yy
);
500 XtGetValues(toplevel_widget
, get_pos_args
, 2);
505 /* Create a transient shell for the dialog box. */
507 XtSetArg(warg
[0], XtNtransientFor
, queue_widget
);
508 XtSetArg(warg
[1], XtNx
, x
+ 50);
509 XtSetArg(warg
[2], XtNy
, y
+ 50);
510 XtSetArg(warg
[3], XtNallowShellResize
, True
);
511 dialog_shell
= XtCreatePopupShell("forDialog", transientShellWidgetClass
,
512 toplevel_widget
, warg
, 4);
514 /* Create the dialog box. */
516 dialog_arg
[0].value
= (XtArgVal
)label
;
517 dialog_arg
[1].value
= (XtArgVal
)value
;
518 dialog_widget
= XtCreateManagedWidget("dialog", dialogWidgetClass
, dialog_shell
,
519 dialog_arg
, XtNumber(dialog_arg
));
521 /* Get the text widget from within the dialog box, give it the keyboard focus,
522 make it wider than the default, and override its translations to make Return
523 call the dialog action function. */
525 text
= XtNameToWidget(dialog_widget
, "value");
526 XawTextSetInsertionPoint(text
, Ustrlen(value
));
527 XtSetKeyboardFocus(dialog_widget
, text
);
528 xs_SetValues(text
, 1, "width", 200);
529 pop_trans
= XtParseTranslationTable(
530 "<Key>Return: dialogAction()\n");
531 XtOverrideTranslations(text
, pop_trans
);
533 /* Pop the thing up. */
535 XtPopup(dialog_shell
, XtGrabExclusive
);
543 /*************************************************
544 * Cause a recipient to be added *
545 *************************************************/
547 /* This just sets up the dialog box; the action happens when it has been filled
550 static void addrecipAction(Widget w
, XtPointer client_data
, XtPointer call_data
)
552 w
= w
; /* Keep picky compilers happy */
553 call_data
= call_data
;
554 Ustrcpy(actioned_message
, (uschar
*)client_data
);
555 action_required
= US
"-Mar";
556 dialog_ref_widget
= menushell
;
557 create_dialog(US
"Recipient address to add?", US
"");
562 /*************************************************
563 * Cause an address to be marked delivered *
564 *************************************************/
566 static void markdelAction(Widget w
, XtPointer client_data
, XtPointer call_data
)
568 w
= w
; /* Keep picky compilers happy */
569 call_data
= call_data
;
570 Ustrcpy(actioned_message
, (uschar
*)client_data
);
571 action_required
= US
"-Mmd";
572 dialog_ref_widget
= menushell
;
573 create_dialog(US
"Recipient address to mark delivered?", US
"");
577 /*************************************************
578 * Cause all addresses to be marked delivered *
579 *************************************************/
581 static void markalldelAction(Widget w
, XtPointer client_data
, XtPointer call_data
)
583 w
= w
; /* Keep picky compilers happy */
584 call_data
= call_data
;
585 ActOnMessage((uschar
*)client_data
, US
"-Mmad", US
"");
589 /*************************************************
590 * Edit the message's sender *
591 *************************************************/
593 static void editsenderAction(Widget w
, XtPointer client_data
,
598 w
= w
; /* Keep picky compilers happy */
599 call_data
= call_data
;
600 Ustrcpy(actioned_message
, (uschar
*)client_data
);
601 q
= find_queue(actioned_message
, queue_noop
, 0);
602 sender
= (q
== NULL
)? US
"" : (q
->sender
[0] == 0)? US
"<>" : q
->sender
;
603 action_required
= US
"-Mes";
604 dialog_ref_widget
= menushell
;
605 create_dialog(US
"New sender address?", sender
);
609 /*************************************************
610 * Cause a message to be returned to sender *
611 *************************************************/
613 static void giveupAction(Widget w
, XtPointer client_data
, XtPointer call_data
)
615 w
= w
; /* Keep picky compilers happy */
616 call_data
= call_data
;
617 ActOnMessage((uschar
*)client_data
, US
"-v -Mg", US
"");
622 /*************************************************
623 * Cause a message to be cancelled *
624 *************************************************/
626 static void removeAction(Widget w
, XtPointer client_data
, XtPointer call_data
)
628 w
= w
; /* Keep picky compilers happy */
629 call_data
= call_data
;
630 ActOnMessage((uschar
*)client_data
, US
"-Mrm", US
"");
635 /*************************************************
636 * Display a message's headers *
637 *************************************************/
639 static void headersAction(Widget w
, XtPointer client_data
, XtPointer call_data
)
642 header_line
*h
, *next
;
643 Widget text
= text_create((uschar
*)client_data
, text_depth
);
646 w
= w
; /* Keep picky compilers happy */
647 call_data
= call_data
;
649 /* Remember the point in the dynamic store so we can recover to it afterwards.
650 Then use Exim's function to read the header. */
652 reset_point
= store_get(0);
654 sprintf(CS buffer
, "%s-H", (uschar
*)client_data
);
655 if (spool_read_header(buffer
, TRUE
, FALSE
) != spool_read_OK
)
657 if (errno
== ERRNO_SPOOLFORMAT
)
660 sprintf(CS big_buffer
, "%s/input/%s", spool_directory
, buffer
);
661 if (Ustat(big_buffer
, &statbuf
) == 0)
662 text_showf(text
, "Format error in spool file %s: size=%d\n", buffer
,
664 else text_showf(text
, "Format error in spool file %s\n", buffer
);
666 else text_showf(text
, "Read error for spool file %s\n", buffer
);
667 store_reset(reset_point
);
671 if (sender_address
!= NULL
)
673 text_showf(text
, "%s sender: <%s>\n", sender_local
? "Local" : "Remote",
677 if (recipients_list
!= NULL
)
680 text_show(text
, US
"Recipients:\n");
681 for (i
= 0; i
< recipients_count
; i
++)
683 text_showf(text
, " %s %s\n",
684 (tree_search(tree_nonrecipients
, recipients_list
[i
].address
) == NULL
)?
685 " ":"*", recipients_list
[i
].address
);
687 text_show(text
, US
"\n");
690 for (h
= header_list
; h
!= NULL
; h
= next
)
693 text_showf(text
, "%c ", h
->type
); /* Don't push h->text through a %s */
694 text_show(text
, h
->text
); /* expansion as it may be v large */
697 store_reset(reset_point
);
703 /*************************************************
704 * Dismiss a text window *
705 *************************************************/
707 static void dismissAction(Widget w
, XtPointer client_data
, XtPointer call_data
)
709 pipe_item
*p
= pipe_chain
;
711 w
= w
; /* Keep picky compilers happy */
712 call_data
= call_data
;
714 XtPopdown((Widget
)client_data
);
715 XtDestroyWidget((Widget
)client_data
);
717 /* If this is a text widget for a sub-process, clear it out of
718 the chain so that subsequent data doesn't try to use it. We have
719 to search the parents of the saved widget to see if one of them
720 is what we have just destroyed. */
724 Widget pp
= p
->widget
;
727 if (pp
== (Widget
)client_data
) { p
->widget
= NULL
; return; }
736 /*************************************************
737 * Set up popup text window *
738 *************************************************/
740 static Widget
text_create(uschar
*name
, int height
)
742 Widget textshell
, form
, text
, button
;
744 /* Create a popup shell widget to display as an additional
747 textshell
= XtCreatePopupShell("textshell", topLevelShellWidgetClass
,
748 toplevel_widget
, NULL
, 0);
749 xs_SetValues(textshell
, 4,
755 /* Create a form widget, containing the text widget and the
756 dismiss button widget. */
758 form
= XtCreateManagedWidget("textform", formWidgetClass
,
760 xs_SetValues(form
, 1, "defaultDistance", 8);
762 text
= XtCreateManagedWidget("texttext", asciiTextWidgetClass
,
763 form
, text_arg
, XtNumber(text_arg
));
764 xs_SetValues(text
, 4,
765 "editType", XawtextAppend
,
768 "translations", text_trans
);
769 XawTextDisplayCaret(text
, TRUE
);
771 /* Use the same font as for the queue display */
773 if (queue_font
!= NULL
)
775 XFontStruct
*f
= XLoadQueryFont(X_display
, CS queue_font
);
776 if (f
!= NULL
) xs_SetValues(text
, 1, "font", f
);
779 button_arg
[0].value
= (XtArgVal
)text
;
780 button
= XtCreateManagedWidget("dismiss", commandWidgetClass
,
781 form
, button_arg
, XtNumber(button_arg
));
782 XtAddCallback(button
, "callback", dismissAction
, (XtPointer
)textshell
);
784 /* Get the toplevel popup displayed, and yield the text widget so
785 that text can be put into it. */
787 XtPopup(textshell
, XtGrabNone
);
794 /*************************************************
795 * Set up menu in queue window *
796 *************************************************/
798 /* We have added an action table that causes this function to
799 be called, and set up button 2 in the text widgets to call it. */
801 void menu_create(Widget w
, XEvent
*event
, String
*actargs
, Cardinal
*count
)
807 Widget src
, menu_line
, item_1
, item_2
, item_3
, item_4
,
808 item_5
, item_6
, item_7
, item_8
, item_9
, item_10
, item_11
,
810 XtTranslations menu_trans
= XtParseTranslationTable(
811 "<EnterWindow>: highlight()\n\
812 <LeaveWindow>: unhighlight()\n\
813 <BtnMotion>: highlight()\n\
814 <BtnUp>: MenuPopdown()notify()unhighlight()\n\
817 actargs
= actargs
; /* Keep picky compilers happy */
820 /* Get the sink and source and the current text pointer */
822 queue_get_arg
[0].value
= (XtArgVal
)(&queue_text_sink
);
823 queue_get_arg
[1].value
= (XtArgVal
)(&src
);
824 queue_get_arg
[2].value
= (XtArgVal
)(&s
);
825 XtGetValues(w
, queue_get_arg
, 3);
827 /* Find the line number of the pointer in the window, and the
828 character offset of the top lefthand of the window. */
830 line
= (event
->xbutton
).y
/ XawTextSinkMaxHeight(queue_text_sink
, 1);
831 p
= XawTextTopPosition(w
);
833 /* Find the start of the line on which the button was clicked. */
838 while (s
[p
] != 0 && s
[p
++] != '\n');
841 /* Now pointing either at 0 or 1st uschar after \n, or very 1st uschar.
842 If 0, the click was beyond the end of the data; just set up a dummy
843 menu. (Not easy to ignore as several actions are specified for the
844 mouse click and it expects this one to set up a menu.) If on a
845 continuation line, move back to the main line. */
849 menushell_arg
[0].value
= (XtArgVal
)"No message selected";
850 menushell
= XtCreatePopupShell("menu", simpleMenuWidgetClass
,
851 queue_widget
, menushell_arg
, XtNumber(menushell_arg
));
852 XtAddCallback(menushell
, "popdownCallback", popdownAction
, NULL
);
853 xs_SetValues(menushell
, 2,
854 "cursor", XCreateFontCursor(X_display
, XC_arrow
),
855 "translations", menu_trans
);
857 /* To keep the widgets in XFree86 happy, we have to create at least one menu
858 item, it seems. (Openwindows doesn't mind a menu with no items.) Otherwise
859 there's a complaint about a zero width menu, and a crash. */
861 menu_line
= XtCreateManagedWidget("line", smeLineObjectClass
, menushell
,
864 item_99_arg
[0].value
= (XtArgVal
)menu_line
;
865 (void)XtCreateManagedWidget("item99", smeBSBObjectClass
, menushell
,
866 item_99_arg
, XtNumber(item_99_arg
));
872 while (p
> 0 && s
[p
+11] == ' ')
876 while (p
> 0 && s
[p
-1] != '\n') p
--;
879 /* Now pointing at first character of a main line. */
881 Ustrncpy(message_id
, s
+p
+11, MESSAGE_ID_LENGTH
);
882 message_id
[MESSAGE_ID_LENGTH
] = 0;
884 /* Highlight the line being menued, and save its parameters so that it
885 can be de-highlighted at popdown. */
887 highlighted_start
= highlighted_end
= p
;
888 while (s
[highlighted_end
] != '\n') highlighted_end
++;
890 highlighted_y
= line
* XawTextSinkMaxHeight(queue_text_sink
, 1) + 2;
892 XawTextSinkDisplayText(queue_text_sink
,
893 highlighted_x
, highlighted_y
,
894 highlighted_start
, highlighted_end
, 1);
896 /* Create the popup shell and the other widgets that comprise the menu.
897 Set the translations and pointer shape, and add the callback pointers. */
899 menushell_arg
[0].value
= (XtArgVal
)message_id
;
900 menushell
= XtCreatePopupShell("menu", simpleMenuWidgetClass
,
901 queue_widget
, menushell_arg
, XtNumber(menushell_arg
));
902 XtAddCallback(menushell
, "popdownCallback", popdownAction
, NULL
);
904 xs_SetValues(menushell
, 2,
905 "cursor", XCreateFontCursor(X_display
, XC_arrow
),
906 "translations", menu_trans
);
908 menu_line
= XtCreateManagedWidget("line", smeLineObjectClass
, menushell
,
911 item_1_arg
[0].value
= (XtArgVal
)menu_line
;
912 item_1
= XtCreateManagedWidget("item1", smeBSBObjectClass
, menushell
,
913 item_1_arg
, XtNumber(item_1_arg
));
914 XtAddCallback(item_1
, "callback", msglogAction
, (XtPointer
)message_id
);
916 item_2_arg
[0].value
= (XtArgVal
)item_1
;
917 item_2
= XtCreateManagedWidget("item2", smeBSBObjectClass
, menushell
,
918 item_2_arg
, XtNumber(item_2_arg
));
919 XtAddCallback(item_2
, "callback", headersAction
, (XtPointer
)message_id
);
921 item_3_arg
[0].value
= (XtArgVal
)item_2
;
922 item_3
= XtCreateManagedWidget("item3", smeBSBObjectClass
, menushell
,
923 item_3_arg
, XtNumber(item_3_arg
));
924 XtAddCallback(item_3
, "callback", bodyAction
, (XtPointer
)message_id
);
926 item_4_arg
[0].value
= (XtArgVal
)item_3
;
927 item_4
= XtCreateManagedWidget("item4", smeBSBObjectClass
, menushell
,
928 item_4_arg
, XtNumber(item_4_arg
));
929 XtAddCallback(item_4
, "callback", deliverAction
, (XtPointer
)message_id
);
931 item_5_arg
[0].value
= (XtArgVal
)item_4
;
932 item_5
= XtCreateManagedWidget("item5", smeBSBObjectClass
, menushell
,
933 item_5_arg
, XtNumber(item_5_arg
));
934 XtAddCallback(item_5
, "callback", freezeAction
, (XtPointer
)message_id
);
936 item_6_arg
[0].value
= (XtArgVal
)item_5
;
937 item_6
= XtCreateManagedWidget("item6", smeBSBObjectClass
, menushell
,
938 item_6_arg
, XtNumber(item_6_arg
));
939 XtAddCallback(item_6
, "callback", thawAction
, (XtPointer
)message_id
);
941 item_7_arg
[0].value
= (XtArgVal
)item_6
;
942 item_7
= XtCreateManagedWidget("item7", smeBSBObjectClass
, menushell
,
943 item_7_arg
, XtNumber(item_7_arg
));
944 XtAddCallback(item_7
, "callback", giveupAction
, (XtPointer
)message_id
);
946 item_8_arg
[0].value
= (XtArgVal
)item_7
;
947 item_8
= XtCreateManagedWidget("item8", smeBSBObjectClass
, menushell
,
948 item_8_arg
, XtNumber(item_8_arg
));
949 XtAddCallback(item_8
, "callback", removeAction
, (XtPointer
)message_id
);
951 item_9_arg
[0].value
= (XtArgVal
)item_8
;
952 item_9
= XtCreateManagedWidget("item9", smeBSBObjectClass
, menushell
,
953 item_9_arg
, XtNumber(item_9_arg
));
955 item_10_arg
[0].value
= (XtArgVal
)item_9
;
956 item_10
= XtCreateManagedWidget("item10", smeBSBObjectClass
, menushell
,
957 item_10_arg
, XtNumber(item_10_arg
));
958 XtAddCallback(item_10
, "callback", addrecipAction
, (XtPointer
)message_id
);
960 item_11_arg
[0].value
= (XtArgVal
)item_10
;
961 item_11
= XtCreateManagedWidget("item11", smeBSBObjectClass
, menushell
,
962 item_11_arg
, XtNumber(item_11_arg
));
963 XtAddCallback(item_11
, "callback", markdelAction
, (XtPointer
)message_id
);
965 item_12_arg
[0].value
= (XtArgVal
)item_11
;
966 item_12
= XtCreateManagedWidget("item12", smeBSBObjectClass
, menushell
,
967 item_12_arg
, XtNumber(item_12_arg
));
968 XtAddCallback(item_12
, "callback", markalldelAction
, (XtPointer
)message_id
);
970 item_13_arg
[0].value
= (XtArgVal
)item_12
;
971 item_13
= XtCreateManagedWidget("item13", smeBSBObjectClass
, menushell
,
972 item_13_arg
, XtNumber(item_13_arg
));
973 XtAddCallback(item_13
, "callback", editsenderAction
, (XtPointer
)message_id
);
975 /* Arrange that the menu pops up with the first item selected. */
977 xs_SetValues(menushell
, 1, "popupOnEntry", item_1
);
979 /* Flag that the menu is up to suppress queue updates. */
984 /* End of em_menu.c */