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