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