(1) Added $host_lookup_deferred.
[exim.git] / src / exim_monitor / em_menu.c
CommitLineData
c988f1f4 1/* $Cambridge: exim/src/exim_monitor/em_menu.c,v 1.2 2005/01/04 10:00:42 ph10 Exp $ */
059ec3d9
PH
2
3/*************************************************
4* Exim Monitor *
5*************************************************/
6
c988f1f4 7/* Copyright (c) University of Cambridge 1995 - 2005 */
059ec3d9
PH
8/* See the file NOTICE for conditions of use and distribution. */
9
10
11#include "em_hdr.h"
12
13/* This module contains code for handling the popup menus. */
14
15static Widget menushell;
16static Widget queue_text_sink;
17static Widget dialog_shell, dialog_widget;
18
19static Widget text_create(uschar *, int);
20
21static int highlighted_start, highlighted_end, highlighted_x, highlighted_y;
22
23
24
25static Arg queue_get_arg[] = {
26 { "textSink", (XtArgVal)NULL },
27 { "textSource", (XtArgVal)NULL },
28 { "string", (XtArgVal)NULL } };
29
30static Arg dialog_arg[] = {
31 { "label", (XtArgVal)"dialog" },
32 { "value", (XtArgVal)"value" } };
33
34static Arg get_pos_args[] = {
35 {"x", (XtArgVal)NULL },
36 {"y", (XtArgVal)NULL } };
37
38static Arg menushell_arg[] = {
39 { "label", (XtArgVal)NULL } };
40
41static Arg button_arg[] = {
42 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
43 { XtNlabel, (XtArgVal) " Dismiss " },
44 { "left", XawChainLeft },
45 { "right", XawChainLeft },
46 { "top", XawChainBottom },
47 { "bottom", XawChainBottom } };
48
49static Arg text_arg[] = {
50 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
51 { "editType", XawtextEdit },
52 { "string", (XtArgVal)"" }, /* dummy to get it going */
53 { "scrollVertical", XawtextScrollAlways },
54 { "wrap", XawtextWrapWord },
55 { "top", XawChainTop },
56 { "bottom", XawChainBottom } };
57
58static Arg item_1_arg[] = {
59 { XtNfromVert, (XtArgVal)NULL }, /* must be first */
60 { "label", (XtArgVal)" Message log" } };
61
62static Arg item_2_arg[] = {
63 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
64 { "label", (XtArgVal)" Headers" } };
65
66static Arg item_3_arg[] = {
67 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
68 { "label", (XtArgVal)" Body" } };
69
70static Arg item_4_arg[] = {
71 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
72 { "label", (XtArgVal)" Deliver message" } };
73
74static Arg item_5_arg[] = {
75 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
76 { "label", (XtArgVal)" Freeze message" } };
77
78static Arg item_6_arg[] = {
79 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
80 { "label", (XtArgVal)" Thaw message" } };
81
82static Arg item_7_arg[] = {
83 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
84 { "label", (XtArgVal)" Give up on msg" } };
85
86static Arg item_8_arg[] = {
87 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
88 { "label", (XtArgVal)" Remove message" } };
89
90static Arg item_9_arg[] = {
91 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
92 { "label", (XtArgVal)"----------------" } };
93
94static Arg item_10_arg[] = {
95 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
96 { "label", (XtArgVal)" Add recipient" } };
97
98static Arg item_11_arg[] = {
99 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
100 { "label", (XtArgVal)" Mark delivered" } };
101
102static Arg item_12_arg[] = {
103 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
104 { "label", (XtArgVal)" Mark all delivered" } };
105
106static Arg item_13_arg[] = {
107 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
108 { "label", (XtArgVal)" Edit sender" } };
109
110static Arg item_99_arg[] = {
111 { XtNfromVert, (XtArgVal) NULL }, /* must be first */
112 { "label", (XtArgVal)" " } };
113
114
115
116/*************************************************
117* Destroy the menu when popped down *
118*************************************************/
119
120static void popdownAction(Widget w, XtPointer client_data, XtPointer call_data)
121{
122client_data = client_data; /* Keep picky compilers happy */
123call_data = call_data;
124if (highlighted_x >= 0)
125 XawTextSinkDisplayText(queue_text_sink,
126 highlighted_x, highlighted_y,
127 highlighted_start, highlighted_end, 0);
128XtDestroyWidget(w);
129menu_is_up = FALSE;
130}
131
132
133
134/*************************************************
135* Display the message log *
136*************************************************/
137
138static void msglogAction(Widget w, XtPointer client_data, XtPointer call_data)
139{
140int i;
141uschar buffer[256];
142Widget text = text_create((uschar *)client_data, text_depth);
143FILE *f = NULL;
144
145w = w; /* Keep picky compilers happy */
146call_data = call_data;
147
148/* End up with the split version, so message looks right when non-exist */
149
150for (i = 0; i < (spool_is_split? 2:1); i++)
151 {
152 message_subdir[0] = (i != 0)? ((uschar *)client_data)[5] : 0;
153 sprintf(CS buffer, "%s/msglog/%s/%s", spool_directory, message_subdir,
154 (uschar *)client_data);
155 f = fopen(CS buffer, "r");
156 if (f != NULL) break;
157 }
158
159if (f == NULL)
160 text_showf(text, "%s: %s\n", buffer, strerror(errno));
161else
162 {
163 while (Ufgets(buffer, 256, f) != NULL) text_show(text, buffer);
164 fclose(f);
165 }
166}
167
168
169
170/*************************************************
171* Display the message body *
172*************************************************/
173
174static void bodyAction(Widget w, XtPointer client_data, XtPointer call_data)
175{
176int i;
177uschar buffer[256];
178Widget text = text_create((uschar *)client_data, text_depth);
179FILE *f = NULL;
180
181w = w; /* Keep picky compilers happy */
182call_data = call_data;
183
184for (i = 0; i < (spool_is_split? 2:1); i++)
185 {
186 message_subdir[0] = (i != 0)? ((uschar *)client_data)[5] : 0;
187 sprintf(CS buffer, "%s/input/%s/%s-D", spool_directory, message_subdir,
188 (uschar *)client_data);
189 f = fopen(CS buffer, "r");
190 if (f != NULL) break;
191 }
192
193if (f == NULL)
194 text_showf(text, "Failed to open file: %s\n", strerror(errno));
195else
196 {
197 int count = 0;
198 while (Ufgets(buffer, 256, f) != NULL)
199 {
200 text_show(text, buffer);
201 count += Ustrlen(buffer);
202 if (count > body_max)
203 {
204 text_show(text, US"\n*** Message length exceeds BODY_MAX ***\n");
205 break;
206 }
207 }
208 fclose(f);
209 }
210}
211
212
213
214/*************************************************
215* Do something to a message *
216*************************************************/
217
218/* The output is not shown in a window for non-delivery actions that succeed,
219unless action_output is set. We can't, however, tell until we have run
220the command whether we want the output or not, so the pipe has to be set up in
221all cases. */
222
223static void ActOnMessage(uschar *id, uschar *action, uschar *address_arg)
224{
225int pid;
226int pipe_fd[2];
227int delivery = Ustrcmp(action + Ustrlen(action) - 2, "-M") == 0;
228uschar *quote = US"";
229uschar *at = US"";
230uschar *qualify = US"";
231uschar buffer[256];
232queue_item *qq;
233Widget text = NULL;
234
235/* If the address arg is not empty and does not contain @ and there is a
236qualify domain, qualify it. (But don't qualify '<>'.)*/
237
238if (address_arg[0] != 0)
239 {
240 quote = US"\'";
241 if (Ustrchr(address_arg, '@') == NULL &&
242 Ustrcmp(address_arg, "<>") != 0 &&
243 qualify_domain != NULL &&
244 qualify_domain[0] != 0)
245 {
246 at = US"@";
247 qualify = qualify_domain;
248 }
249 }
250sprintf(CS buffer, "%s %s %s %s %s %s%s%s%s%s", exim_path,
251 (alternate_config == NULL)? US"" : US"-C",
252 (alternate_config == NULL)? US"" : alternate_config,
253 action, id, quote, address_arg, at, qualify, quote);
254
255/* If we know we are going to need the window, create it now. */
256
257if (action_output || delivery)
258 {
259 text = text_create(id, text_depth);
260 text_showf(text, "%s\n", buffer);
261 }
262
263/* Create the pipe for output. Remember, on most systems pipe[0] is
264for reading and pipe[1] is for writing! Solaris, with its two-way
265pipes is a trap! */
266
267if (pipe(pipe_fd) != 0)
268 {
269 if (text == NULL)
270 {
271 text = text_create(id, text_depth);
272 text_showf(text, "%s\n", buffer);
273 }
274 text_show(text, US"*** Failed to create pipe ***\n");
275 return;
276 }
277
278fcntl(pipe_fd[0], F_SETFL, O_NONBLOCK);
279fcntl(pipe_fd[1], F_SETFL, O_NONBLOCK);
280
281/* Delivering a message can take some time, and we want to show the
282output as it goes along. This requires subprocesses and is coded below. For
283other commands, we can assume an immediate response, and so need not waste
284resources with subprocesses. If action_output is FALSE, don't show the
285output at all. */
286
287if (!delivery)
288 {
289 int count, rc;
290 int save_stdout = dup(1);
291 int save_stderr = dup(2);
292
293 close(1);
294 close(2);
295
296 dup2(pipe_fd[1], 1);
297 dup2(pipe_fd[1], 2);
298 close(pipe_fd[1]);
299
300 rc = system(CS buffer);
301
302 close(1);
303 close(2);
304
305 if (action_output || rc != 0)
306 {
307 if (text == NULL)
308 {
309 text = text_create(id, text_depth);
310 text_showf(text, "%s\n", buffer);
311 }
312 while ((count = read(pipe_fd[0], buffer, 254)) > 0)
313 {
314 buffer[count] = 0;
315 text_show(text, buffer);
316 }
317 }
318
319 close(pipe_fd[0]);
320
321 dup2(save_stdout, 1);
322 dup2(save_stderr, 2);
323 close(save_stdout);
324 close(save_stderr);
325
326 /* If action was to change the sender, and it succeeded, we have to
327 update the in-store data. */
328
329 if (rc == 0 && Ustrcmp(action + Ustrlen(action) - 4, "-Mes") == 0)
330 {
331 queue_item *q = find_queue(id, queue_noop, 0);
332 if (q != NULL)
333 {
334 if (q->sender != NULL) store_free(q->sender);
335 q->sender = store_malloc(Ustrlen(address_arg) + 1);
336 Ustrcpy(q->sender, address_arg);
337 }
338 }
339
340 /* If configured, cause a display update and return */
341
342 if (action_queue_update) tick_queue_accumulator = 999999;
343 return;
344 }
345
346/* Message is to be delivered. Ensure that it is marked unfrozen,
347because nothing will get written to the log to show that this has
348happened. (Other freezing/unfreezings get logged and picked up from
349there.) */
350
351qq = find_queue(id, queue_noop, 0);
352if (qq != NULL) qq->frozen = FALSE;
353
354/* New, asynchronous code runs in a subprocess for commands that
355will take some time. The main process does not wait. There is a
356SIGCHLD handler in the main program that cleans up any terminating
357sub processes. */
358
359if ((pid = fork()) == 0)
360 {
361 close(1);
362 close(2);
363
364 dup2(pipe_fd[1], 1);
365 dup2(pipe_fd[1], 2);
366 close(pipe_fd[1]);
367
368 system(CS buffer);
369
370 close(1);
371 close(2);
372 close(pipe_fd[0]);
373 _exit(0);
374 }
375
376/* Main process - set up an item for the main ticker to watch. */
377
378if (pid < 0) text_showf(text, "Failed to fork: %s\n", strerror(pid)); else
379 {
380 pipe_item *p = (pipe_item *)store_malloc(sizeof(pipe_item));
381
382 if (p == NULL)
383 {
384 text_show(text, US"Run out of store\n");
385 return;
386 }
387
388 p->widget = text;
389 p->fd = pipe_fd[0];
390
391 p->next = pipe_chain;
392 pipe_chain = p;
393
394 close(pipe_fd[1]);
395 }
396}
397
398
399
400
401/*************************************************
402* Cause a message to be delivered *
403*************************************************/
404
405static void deliverAction(Widget w, XtPointer client_data, XtPointer call_data)
406{
407w = w; /* Keep picky compilers happy */
408call_data = call_data;
409ActOnMessage((uschar *)client_data, US"-v -M", US"");
410}
411
412
413
414/*************************************************
415* Cause a message to be Frozen *
416*************************************************/
417
418static void freezeAction(Widget w, XtPointer client_data, XtPointer call_data)
419{
420w = w; /* Keep picky compilers happy */
421call_data = call_data;
422ActOnMessage((uschar *)client_data, US"-Mf", US"");
423}
424
425
426
427/*************************************************
428* Cause a message to be thawed *
429*************************************************/
430
431static void thawAction(Widget w, XtPointer client_data, XtPointer call_data)
432{
433w = w; /* Keep picky compilers happy */
434call_data = call_data;
435ActOnMessage((uschar *)client_data, US"-Mt", US"");
436}
437
438
439
440/*************************************************
441* Take action using dialog data *
442*************************************************/
443
444/* This function is called after a dialog box has been filled
445in. It is global because it is set up in the action table at
446start-up time. If the string is empty, do nothing. */
447
448XtActionProc dialogAction(Widget w, XEvent *event, String *ss, Cardinal *c)
449{
450uschar *s = US XawDialogGetValueString(dialog_widget);
451
452w = w; /* Keep picky compilers happy */
453event = event;
454ss = ss;
455c = c;
456
457XtPopdown((Widget)dialog_shell);
458XtDestroyWidget((Widget)dialog_shell);
459while (isspace(*s)) s++;
460if (s[0] != 0)
461 {
462 if (actioned_message[0] != 0)
463 ActOnMessage(actioned_message, action_required, s);
464 else
465 NonMessageDialogue(s); /* When called from somewhere else */
466 }
467return NULL;
468}
469
470
471
472/*************************************************
473* Create a dialog box *
474*************************************************/
475
476/* The focus is grabbed exclusively, so nothing else can
477be done to the application until the box is filled in. This
478function is also used by the Hide button handler. */
479
480void create_dialog(uschar *label, uschar *value)
481{
482Arg warg[4];
483Dimension x, y, xx, yy;
484XtTranslations pop_trans;
485Widget text;
486
487/* Get the position of a reference widget so the dialog box can be put
488near to it. */
489
490get_pos_args[0].value = (XtArgVal)(&x);
491get_pos_args[1].value = (XtArgVal)(&y);
492XtGetValues(dialog_ref_widget, get_pos_args, 2);
493
494/* When this is not a message_specific thing, the position of the reference
495widget is relative to the window. Get the position of the top level widget and
496add to the position. */
497
498if (dialog_ref_widget != menushell)
499 {
500 get_pos_args[0].value = (XtArgVal)(&xx);
501 get_pos_args[1].value = (XtArgVal)(&yy);
502 XtGetValues(toplevel_widget, get_pos_args, 2);
503 x += xx;
504 y += yy;
505 }
506
507/* Create a transient shell for the dialog box. */
508
509XtSetArg(warg[0], XtNtransientFor, queue_widget);
510XtSetArg(warg[1], XtNx, x + 50);
511XtSetArg(warg[2], XtNy, y + 50);
512XtSetArg(warg[3], XtNallowShellResize, True);
513dialog_shell = XtCreatePopupShell("forDialog", transientShellWidgetClass,
514 toplevel_widget, warg, 4);
515
516/* Create the dialog box. */
517
518dialog_arg[0].value = (XtArgVal)label;
519dialog_arg[1].value = (XtArgVal)value;
520dialog_widget = XtCreateManagedWidget("dialog", dialogWidgetClass, dialog_shell,
521 dialog_arg, XtNumber(dialog_arg));
522
523/* Get the text widget from within the dialog box, give it the keyboard focus,
524make it wider than the default, and override its translations to make Return
525call the dialog action function. */
526
527text = XtNameToWidget(dialog_widget, "value");
528XawTextSetInsertionPoint(text, Ustrlen(value));
529XtSetKeyboardFocus(dialog_widget, text);
530xs_SetValues(text, 1, "width", 200);
531pop_trans = XtParseTranslationTable(
532 "<Key>Return: dialogAction()\n");
533XtOverrideTranslations(text, pop_trans);
534
535/* Pop the thing up. */
536
537XtPopup(dialog_shell, XtGrabExclusive);
538XFlush(X_display);
539}
540
541
542
543
544
545/*************************************************
546* Cause a recipient to be added *
547*************************************************/
548
549/* This just sets up the dialog box; the action happens when it has been filled
550in. */
551
552static void addrecipAction(Widget w, XtPointer client_data, XtPointer call_data)
553{
554w = w; /* Keep picky compilers happy */
555call_data = call_data;
556Ustrcpy(actioned_message, (uschar *)client_data);
557action_required = US"-Mar";
558dialog_ref_widget = menushell;
559create_dialog(US"Recipient address to add?", US"");
560}
561
562
563
564/*************************************************
565* Cause an address to be marked delivered *
566*************************************************/
567
568static void markdelAction(Widget w, XtPointer client_data, XtPointer call_data)
569{
570w = w; /* Keep picky compilers happy */
571call_data = call_data;
572Ustrcpy(actioned_message, (uschar *)client_data);
573action_required = US"-Mmd";
574dialog_ref_widget = menushell;
575create_dialog(US"Recipient address to mark delivered?", US"");
576}
577
578
579/*************************************************
580* Cause all addresses to be marked delivered *
581*************************************************/
582
583static void markalldelAction(Widget w, XtPointer client_data, XtPointer call_data)
584{
585w = w; /* Keep picky compilers happy */
586call_data = call_data;
587ActOnMessage((uschar *)client_data, US"-Mmad", US"");
588}
589
590
591/*************************************************
592* Edit the message's sender *
593*************************************************/
594
595static void editsenderAction(Widget w, XtPointer client_data,
596 XtPointer call_data)
597{
598queue_item *q;
599uschar *sender;
600w = w; /* Keep picky compilers happy */
601call_data = call_data;
602Ustrcpy(actioned_message, (uschar *)client_data);
603q = find_queue(actioned_message, queue_noop, 0);
604sender = (q == NULL)? US"" : (q->sender[0] == 0)? US"<>" : q->sender;
605action_required = US"-Mes";
606dialog_ref_widget = menushell;
607create_dialog(US"New sender address?", sender);
608}
609
610
611/*************************************************
612* Cause a message to be returned to sender *
613*************************************************/
614
615static void giveupAction(Widget w, XtPointer client_data, XtPointer call_data)
616{
617w = w; /* Keep picky compilers happy */
618call_data = call_data;
619ActOnMessage((uschar *)client_data, US"-v -Mg", US"");
620}
621
622
623
624/*************************************************
625* Cause a message to be cancelled *
626*************************************************/
627
628static void removeAction(Widget w, XtPointer client_data, XtPointer call_data)
629{
630w = w; /* Keep picky compilers happy */
631call_data = call_data;
632ActOnMessage((uschar *)client_data, US"-Mrm", US"");
633}
634
635
636
637/*************************************************
638* Display a message's headers *
639*************************************************/
640
641static void headersAction(Widget w, XtPointer client_data, XtPointer call_data)
642{
643uschar buffer[256];
644header_line *h, *next;
645Widget text = text_create((uschar *)client_data, text_depth);
646void *reset_point;
647
648w = w; /* Keep picky compilers happy */
649call_data = call_data;
650
651/* Remember the point in the dynamic store so we can recover to it afterwards.
652Then use Exim's function to read the header. */
653
654reset_point = store_get(0);
655
656sprintf(CS buffer, "%s-H", (uschar *)client_data);
657if (spool_read_header(buffer, TRUE, FALSE) != spool_read_OK)
658 {
659 if (errno == ERRNO_SPOOLFORMAT)
660 {
661 struct stat statbuf;
662 sprintf(CS big_buffer, "%s/input/%s", spool_directory, buffer);
663 if (Ustat(big_buffer, &statbuf) == 0)
664 text_showf(text, "Format error in spool file %s: size=%d\n", buffer,
665 statbuf.st_size);
666 else text_showf(text, "Format error in spool file %s\n", buffer);
667 }
668 else text_showf(text, "Read error for spool file %s\n", buffer);
669 store_reset(reset_point);
670 return;
671 }
672
673if (sender_address != NULL)
674 {
675 text_showf(text, "%s sender: <%s>\n", sender_local? "Local" : "Remote",
676 sender_address);
677 }
678
679if (recipients_list != NULL)
680 {
681 int i;
682 text_show(text, US"Recipients:\n");
683 for (i = 0; i < recipients_count; i++)
684 {
685 text_showf(text, " %s %s\n",
686 (tree_search(tree_nonrecipients, recipients_list[i].address) == NULL)?
687 " ":"*", recipients_list[i].address);
688 }
689 text_show(text, US"\n");
690 }
691
692for (h = header_list; h != NULL; h = next)
693 {
694 next = h->next;
695 text_showf(text, "%c ", h->type); /* Don't push h->text through a %s */
696 text_show(text, h->text); /* expansion as it may be v large */
697 }
698
699store_reset(reset_point);
700}
701
702
703
704
705/*************************************************
706* Dismiss a text window *
707*************************************************/
708
709static void dismissAction(Widget w, XtPointer client_data, XtPointer call_data)
710{
711pipe_item *p = pipe_chain;
712
713w = w; /* Keep picky compilers happy */
714call_data = call_data;
715
716XtPopdown((Widget)client_data);
717XtDestroyWidget((Widget)client_data);
718
719/* If this is a text widget for a sub-process, clear it out of
720the chain so that subsequent data doesn't try to use it. We have
721to search the parents of the saved widget to see if one of them
722is what we have just destroyed. */
723
724while (p != NULL)
725 {
726 Widget pp = p->widget;
727 while (pp != NULL)
728 {
729 if (pp == (Widget)client_data) { p->widget = NULL; return; }
730 pp = XtParent(pp);
731 }
732 p = p->next;
733 }
734}
735
736
737
738/*************************************************
739* Set up popup text window *
740*************************************************/
741
742static Widget text_create(uschar *name, int height)
743{
744Widget textshell, form, text, button;
745
746/* Create a popup shell widget to display as an additional
747toplevel window. */
748
749textshell = XtCreatePopupShell("textshell", topLevelShellWidgetClass,
750 toplevel_widget, NULL, 0);
751xs_SetValues(textshell, 4,
752 "title", name,
753 "iconName", name,
754 "minWidth", 100,
755 "minHeight", 100);
756
757/* Create a form widget, containing the text widget and the
758dismiss button widget. */
759
760form = XtCreateManagedWidget("textform", formWidgetClass,
761 textshell, NULL, 0);
762xs_SetValues(form, 1, "defaultDistance", 8);
763
764text = XtCreateManagedWidget("texttext", asciiTextWidgetClass,
765 form, text_arg, XtNumber(text_arg));
766xs_SetValues(text, 4,
767 "editType", XawtextAppend,
768 "width", 700,
769 "height", height,
770 "translations", text_trans);
771XawTextDisplayCaret(text, TRUE);
772
773/* Use the same font as for the queue display */
774
775if (queue_font != NULL)
776 {
777 XFontStruct *f = XLoadQueryFont(X_display, CS queue_font);
778 if (f != NULL) xs_SetValues(text, 1, "font", f);
779 }
780
781button_arg[0].value = (XtArgVal)text;
782button = XtCreateManagedWidget("dismiss", commandWidgetClass,
783 form, button_arg, XtNumber(button_arg));
784XtAddCallback(button, "callback", dismissAction, (XtPointer)textshell);
785
786/* Get the toplevel popup displayed, and yield the text widget so
787that text can be put into it. */
788
789XtPopup(textshell, XtGrabNone);
790return text;
791}
792
793
794
795
796/*************************************************
797* Set up menu in queue window *
798*************************************************/
799
800/* We have added an action table that causes this function to
801be called, and set up button 2 in the text widgets to call it. */
802
803void menu_create(Widget w, XEvent *event, String *actargs, Cardinal *count)
804{
805int line;
806int i;
807uschar *s;
808XawTextPosition p;
809Widget src, menu_line, item_1, item_2, item_3, item_4,
810 item_5, item_6, item_7, item_8, item_9, item_10, item_11,
811 item_12, item_13;
812XtTranslations menu_trans = XtParseTranslationTable(
813 "<EnterWindow>: highlight()\n\
814 <LeaveWindow>: unhighlight()\n\
815 <BtnMotion>: highlight()\n\
816 <BtnUp>: MenuPopdown()notify()unhighlight()\n\
817 ");
818
819actargs = actargs; /* Keep picky compilers happy */
820count = count;
821
822/* Get the sink and source and the current text pointer */
823
824queue_get_arg[0].value = (XtArgVal)(&queue_text_sink);
825queue_get_arg[1].value = (XtArgVal)(&src);
826queue_get_arg[2].value = (XtArgVal)(&s);
827XtGetValues(w, queue_get_arg, 3);
828
829/* Find the line number of the pointer in the window, and the
830character offset of the top lefthand of the window. */
831
832line = (event->xbutton).y / XawTextSinkMaxHeight(queue_text_sink, 1);
833p = XawTextTopPosition(w);
834
835/* Find the start of the line on which the button was clicked. */
836
837i = line;
838while (i-- > 0)
839 {
840 while (s[p] != 0 && s[p++] != '\n');
841 }
842
843/* Now pointing either at 0 or 1st uschar after \n, or very 1st uschar.
844If 0, the click was beyond the end of the data; just set up a dummy
845menu. (Not easy to ignore as several actions are specified for the
846mouse click and it expects this one to set up a menu.) If on a
847continuation line, move back to the main line. */
848
849if (s[p] == 0)
850 {
851 menushell_arg[0].value = (XtArgVal)"No message selected";
852 menushell = XtCreatePopupShell("menu", simpleMenuWidgetClass,
853 queue_widget, menushell_arg, XtNumber(menushell_arg));
854 XtAddCallback(menushell, "popdownCallback", popdownAction, NULL);
855 xs_SetValues(menushell, 2,
856 "cursor", XCreateFontCursor(X_display, XC_arrow),
857 "translations", menu_trans);
858
859 /* To keep the widgets in XFree86 happy, we have to create at least one menu
860 item, it seems. (Openwindows doesn't mind a menu with no items.) Otherwise
861 there's a complaint about a zero width menu, and a crash. */
862
863 menu_line = XtCreateManagedWidget("line", smeLineObjectClass, menushell,
864 NULL, 0);
865
866 item_99_arg[0].value = (XtArgVal)menu_line;
867 (void)XtCreateManagedWidget("item99", smeBSBObjectClass, menushell,
868 item_99_arg, XtNumber(item_99_arg));
869
870 highlighted_x = -1;
871 return;
872 }
873
874while (p > 0 && s[p+11] == ' ')
875 {
876 line--;
877 p--;
878 while (p > 0 && s[p-1] != '\n') p--;
879 }
880
881/* Now pointing at first character of a main line. */
882
883Ustrncpy(message_id, s+p+11, MESSAGE_ID_LENGTH);
884message_id[MESSAGE_ID_LENGTH] = 0;
885
886/* Highlight the line being menued, and save its parameters so that it
887can be de-highlighted at popdown. */
888
889highlighted_start = highlighted_end = p;
890while (s[highlighted_end] != '\n') highlighted_end++;
891highlighted_x = 17;
892highlighted_y = line * XawTextSinkMaxHeight(queue_text_sink, 1) + 2;
893
894XawTextSinkDisplayText(queue_text_sink,
895 highlighted_x, highlighted_y,
896 highlighted_start, highlighted_end, 1);
897
898/* Create the popup shell and the other widgets that comprise the menu.
899Set the translations and pointer shape, and add the callback pointers. */
900
901menushell_arg[0].value = (XtArgVal)message_id;
902menushell = XtCreatePopupShell("menu", simpleMenuWidgetClass,
903 queue_widget, menushell_arg, XtNumber(menushell_arg));
904XtAddCallback(menushell, "popdownCallback", popdownAction, NULL);
905
906xs_SetValues(menushell, 2,
907 "cursor", XCreateFontCursor(X_display, XC_arrow),
908 "translations", menu_trans);
909
910menu_line = XtCreateManagedWidget("line", smeLineObjectClass, menushell,
911 NULL, 0);
912
913item_1_arg[0].value = (XtArgVal)menu_line;
914item_1 = XtCreateManagedWidget("item1", smeBSBObjectClass, menushell,
915 item_1_arg, XtNumber(item_1_arg));
916XtAddCallback(item_1, "callback", msglogAction, (XtPointer)message_id);
917
918item_2_arg[0].value = (XtArgVal)item_1;
919item_2 = XtCreateManagedWidget("item2", smeBSBObjectClass, menushell,
920 item_2_arg, XtNumber(item_2_arg));
921XtAddCallback(item_2, "callback", headersAction, (XtPointer)message_id);
922
923item_3_arg[0].value = (XtArgVal)item_2;
924item_3 = XtCreateManagedWidget("item3", smeBSBObjectClass, menushell,
925 item_3_arg, XtNumber(item_3_arg));
926XtAddCallback(item_3, "callback", bodyAction, (XtPointer)message_id);
927
928item_4_arg[0].value = (XtArgVal)item_3;
929item_4 = XtCreateManagedWidget("item4", smeBSBObjectClass, menushell,
930 item_4_arg, XtNumber(item_4_arg));
931XtAddCallback(item_4, "callback", deliverAction, (XtPointer)message_id);
932
933item_5_arg[0].value = (XtArgVal)item_4;
934item_5 = XtCreateManagedWidget("item5", smeBSBObjectClass, menushell,
935 item_5_arg, XtNumber(item_5_arg));
936XtAddCallback(item_5, "callback", freezeAction, (XtPointer)message_id);
937
938item_6_arg[0].value = (XtArgVal)item_5;
939item_6 = XtCreateManagedWidget("item6", smeBSBObjectClass, menushell,
940 item_6_arg, XtNumber(item_6_arg));
941XtAddCallback(item_6, "callback", thawAction, (XtPointer)message_id);
942
943item_7_arg[0].value = (XtArgVal)item_6;
944item_7 = XtCreateManagedWidget("item7", smeBSBObjectClass, menushell,
945 item_7_arg, XtNumber(item_7_arg));
946XtAddCallback(item_7, "callback", giveupAction, (XtPointer)message_id);
947
948item_8_arg[0].value = (XtArgVal)item_7;
949item_8 = XtCreateManagedWidget("item8", smeBSBObjectClass, menushell,
950 item_8_arg, XtNumber(item_8_arg));
951XtAddCallback(item_8, "callback", removeAction, (XtPointer)message_id);
952
953item_9_arg[0].value = (XtArgVal)item_8;
954item_9 = XtCreateManagedWidget("item9", smeBSBObjectClass, menushell,
955 item_9_arg, XtNumber(item_9_arg));
956
957item_10_arg[0].value = (XtArgVal)item_9;
958item_10 = XtCreateManagedWidget("item10", smeBSBObjectClass, menushell,
959 item_10_arg, XtNumber(item_10_arg));
960XtAddCallback(item_10, "callback", addrecipAction, (XtPointer)message_id);
961
962item_11_arg[0].value = (XtArgVal)item_10;
963item_11 = XtCreateManagedWidget("item11", smeBSBObjectClass, menushell,
964 item_11_arg, XtNumber(item_11_arg));
965XtAddCallback(item_11, "callback", markdelAction, (XtPointer)message_id);
966
967item_12_arg[0].value = (XtArgVal)item_11;
968item_12 = XtCreateManagedWidget("item12", smeBSBObjectClass, menushell,
969 item_12_arg, XtNumber(item_12_arg));
970XtAddCallback(item_12, "callback", markalldelAction, (XtPointer)message_id);
971
972item_13_arg[0].value = (XtArgVal)item_12;
973item_13 = XtCreateManagedWidget("item13", smeBSBObjectClass, menushell,
974 item_13_arg, XtNumber(item_13_arg));
975XtAddCallback(item_13, "callback", editsenderAction, (XtPointer)message_id);
976
977/* Arrange that the menu pops up with the first item selected. */
978
979xs_SetValues(menushell, 1, "popupOnEntry", item_1);
980
981/* Flag that the menu is up to suppress queue updates. */
982
983menu_is_up = TRUE;
984}
985
986/* End of em_menu.c */