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