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