1
2 static char rcsid[] = "@(#)$Id: screen.c,v 1.7 1996/03/14 17:29:50 wfp5p Exp $";
3
4 /*******************************************************************************
5 * The Elm Mail System - $Revision: 1.7 $ $State: Exp $
6 *
7 * Copyright (c) 1988-1995 USENET Community Trust
8 * Copyright (c) 1986,1987 Dave Taylor
9 *******************************************************************************
10 * Bug reports, patches, comments, suggestions should be sent to:
11 *
12 * Bill Pemberton, Elm Coordinator
13 * flash@virginia.edu
14 *
15 *******************************************************************************
16 * $Log: screen.c,v $
17 * Revision 1.7 1996/03/14 17:29:50 wfp5p
18 * Alpha 9
19 *
20 * Revision 1.6 1995/09/29 17:42:26 wfp5p
21 * Alpha 8 (Chip's big changes)
22 *
23 * Revision 1.5 1995/09/11 15:19:30 wfp5p
24 * Alpha 7
25 *
26 * Revision 1.4 1995/06/15 13:09:33 wfp5p
27 * Changed so the local mlist files adds to the global one instead of
28 * overriding it. (Paul Close <pdc@sgi.com>)
29 *
30 * Revision 1.3 1995/05/10 13:34:53 wfp5p
31 * Added mailing list stuff by Paul Close <pdc@sgi.com>
32 *
33 * Revision 1.2 1995/04/20 21:01:50 wfp5p
34 * Added the showreply feature and emacs key bindings.
35 *
36 * Revision 1.1.1.1 1995/04/19 20:38:38 wfp5p
37 * Initial import of elm 2.4 PL0 as base for elm 2.5.
38 *
39 ******************************************************************************/
40
41 /** screen display routines for ELM program
42
43 **/
44
45 #include "elm_defs.h"
46 #include "elm_globals.h"
47 #include "s_elm.h"
48
49 #define minimum(a,b) ((a) < (b) ? (a) : (b))
50
51 char *nameof(), *show_status();
52
53 extern char version_buff[];
54
showscreen()55 showscreen()
56 {
57
58 ClearScreen();
59
60 update_title();
61
62 last_header_page = -1; /* force a redraw regardless */
63 show_headers();
64
65 if (mini_menu)
66 show_menu();
67
68 show_last_error();
69 }
70
update_title()71 update_title()
72 {
73 /** display a new title line, probably due to new mail arriving **/
74
75 char buffer[SLEN], folder_string[SLEN];
76 static char *folder = NULL, *mailbox = NULL;
77
78 if (folder == NULL) {
79 folder = catgets(elm_msg_cat, ElmSet, ElmFolder, "Folder");
80 mailbox = catgets(elm_msg_cat, ElmSet, ElmMailbox, "Mailbox");
81 }
82
83 #ifdef DISP_HOST
84 sprintf(folder_string, "%s:%s", host_name, nameof(curr_folder.filename));
85 #else
86 strcpy(folder_string, nameof(curr_folder.filename));
87 #endif
88
89 if (selected)
90 MCsprintf(buffer, catgets(elm_msg_cat, ElmSet, ElmShownWithSelect,
91 "%s is '%s' with %d shown out of %d [ELM %s]"),
92 ((curr_folder.flags & FOLDER_IS_SPOOL) ? mailbox : folder),
93 folder_string, selected, curr_folder.num_mssgs, version_buff);
94 else if (curr_folder.num_mssgs == 1)
95 MCsprintf(buffer, catgets(elm_msg_cat, ElmSet, ElmShownNoSelect,
96 "%s is '%s' with 1 message [ELM %s]"),
97 ((curr_folder.flags & FOLDER_IS_SPOOL) ? mailbox : folder),
98 folder_string, version_buff);
99 else
100 MCsprintf(buffer, catgets(elm_msg_cat, ElmSet, ElmShownNoSelectPlural,
101 "%s is '%s' with %d messages [ELM %s]"),
102 ((curr_folder.flags & FOLDER_IS_SPOOL) ? mailbox : folder),
103 folder_string, curr_folder.num_mssgs, version_buff);
104
105 ClearLine(1);
106 CenterLine(1, buffer);
107 }
108
show_menu()109 show_menu()
110 {
111 /** write main system menu... **/
112
113 if (user_level == 0) { /* a rank beginner. Give less options */
114 CenterLine(LINES-7, catgets(elm_msg_cat, ElmSet, ElmLevel0MenuLine1,
115 "You can use any of the following commands by pressing the first character;"));
116 CenterLine(LINES-6, catgets(elm_msg_cat, ElmSet, ElmLevel0MenuLine2,
117 "d)elete or u)ndelete mail, m)ail a message, r)eply or f)orward mail, q)uit"));
118 CenterLine(LINES-5, catgets(elm_msg_cat, ElmSet, ElmLevel0MenuLine3,
119 "To read a message, press <return>. j = move down, k = move up, ? = help"));
120 } else {
121 CenterLine(LINES-7, catgets(elm_msg_cat, ElmSet, ElmLevel1MenuLine1,
122 "|=pipe, !=shell, ?=help, <n>=set current to n, /=search pattern"));
123 CenterLine(LINES-6, catgets(elm_msg_cat, ElmSet, ElmLevel1MenuLine2,
124 "a)lias, C)opy, c)hange folder, d)elete, e)dit, f)orward, g)roup reply, m)ail,"));
125 CenterLine(LINES-5, catgets(elm_msg_cat, ElmSet, ElmLevel1MenuLine3,
126 "n)ext, o)ptions, p)rint, q)uit, r)eply, s)ave, t)ag, u)ndelete, or e(x)it"));
127 }
128 }
129
130 int
show_headers()131 show_headers()
132 {
133 /** Display page of headers (10) if present. First check to
134 ensure that header_page is in bounds, fixing silently if not.
135 If out of bounds, return zero, else return non-zero
136 Modified to only show headers that are "visible" to ze human
137 person using ze program, eh?
138 **/
139
140 register int this_msg = 0, line = 4, last = 0, last_line,
141 displayed = 0, using_to;
142 int max, do_standout;
143 char newfrom[SLEN], buffer[SLEN];
144
145 max = (inalias ? num_aliases : curr_folder.num_mssgs);
146 headers_per_page = LINES - (mini_menu ? 13 : 8);
147 if (headers_per_page < 1)
148 headers_per_page = 1;
149
150 if (fix_header_page())
151 return(FALSE);
152
153 if (selected) {
154 if ((header_page*headers_per_page) > selected)
155 return(FALSE); /* too far! too far! */
156
157 this_msg = visible_to_index(header_page * headers_per_page + 1);
158 displayed = header_page * headers_per_page;
159
160 last = displayed+headers_per_page;
161
162 }
163 else {
164 if (header_page == last_header_page) /* nothing to do! */
165 return(FALSE);
166
167 /** compute last header to display **/
168
169 this_msg = header_page * headers_per_page;
170 last = this_msg + (headers_per_page - 1);
171 }
172
173 if (last >= max)
174 last = max-1;
175
176 /** Okay, now let's show the header page! **/
177
178 ClearLine(line); /* Clear the top line... */
179
180 MoveCursor(line, 0); /* and move back to the top of the page... */
181
182 while ((selected && displayed < last) || this_msg <= last) {
183 if (inalias) {
184 if (this_msg == curr_alias-1)
185 build_alias_line(buffer, aliases[this_msg], this_msg+1,
186 TRUE);
187 else
188 build_alias_line(buffer, aliases[this_msg], this_msg+1,
189 FALSE);
190 }
191 else {
192 using_to = tail_of(curr_folder.headers[this_msg]->from, newfrom,
193 curr_folder.headers[this_msg]->to);
194
195 if (this_msg == curr_folder.curr_mssg-1)
196 build_header_line(buffer, curr_folder.headers[this_msg], this_msg+1,
197 TRUE, newfrom, using_to);
198 else
199 build_header_line(buffer, curr_folder.headers[this_msg],
200 this_msg+1, FALSE, newfrom, using_to);
201 }
202 if (selected)
203 displayed++;
204
205 if (inalias)
206 do_standout = (this_msg == curr_alias-1 && !arrow_cursor);
207 else
208 do_standout = (this_msg == curr_folder.curr_mssg-1 && !arrow_cursor);
209
210 if (do_standout)
211 StartStandout();
212 PutLine0(-1, -1, buffer);
213 if (do_standout)
214 EndStandout();
215 NewLine();
216
217 CleartoEOLN();
218 line++; /* for clearing up in a sec... */
219
220 if (selected) {
221 if ((this_msg = next_message(this_msg, FALSE)) < 0)
222 break; /* GET OUTTA HERE! */
223
224 /* the preceeding looks gross because we're using an INDEX
225 variable to pretend to be a "current" counter, and the
226 current counter is always 1 greater than the actual
227 index. Does that make sense??
228 */
229 }
230 else
231 this_msg++; /* even dumber... */
232 }
233
234 /* clear unused lines */
235
236 if (mini_menu)
237 last_line = LINES-8;
238 else
239 last_line = LINES-4;
240
241 while (line < last_line) {
242 CleartoEOLN();
243 NewLine();
244 line++;
245 }
246
247 display_central_message();
248
249 last_current = (inalias ? curr_alias : curr_folder.curr_mssg);
250 last_header_page = header_page;
251
252 return(TRUE);
253 }
254
255 void
show_current()256 show_current()
257 {
258 /** Show the new header, with all the usual checks **/
259
260 register int first = 0, last = 0, last_line, new_line, using_to;
261 int curr, max;
262 char newfrom[SLEN], old_buffer[SLEN], new_buffer[SLEN];
263
264 if (inalias) {
265 curr = curr_alias;
266 max = num_aliases;
267 } else {
268 curr = curr_folder.curr_mssg;
269 max = curr_folder.num_mssgs;
270 }
271 (void) fix_header_page(); /* Who cares what it does? ;-) */
272
273 /** compute the first and last header on this page **/
274 first = header_page * headers_per_page + 1;
275 last = first + (headers_per_page - 1);
276
277 /* if not a full page adjust last to be the real last */
278 if (selected && last > selected)
279 last = selected;
280 if (!selected && last > max)
281 last = max;
282
283 /** okay, now let's show the pointers... **/
284
285 /** have we changed??? **/
286 if (curr == last_current)
287 return;
288
289 if (selected) {
290 last_line = ((compute_visible(last_current)-1) %
291 headers_per_page)+4;
292 new_line = ((compute_visible(curr)-1) % headers_per_page)+4;
293 } else {
294 last_line = ((last_current-1) % headers_per_page)+4;
295 new_line = ((curr-1) % headers_per_page)+4;
296 }
297
298 if (! arrow_cursor) {
299
300 if (inalias)
301 build_alias_line(new_buffer, aliases[curr_alias-1], curr_alias,
302 TRUE);
303 else {
304 using_to = tail_of(curr_folder.headers[curr_folder.curr_mssg-1]->from, newfrom,
305 curr_folder.headers[curr_folder.curr_mssg-1]->to);
306 build_header_line(new_buffer, curr_folder.headers[curr_folder.curr_mssg-1],
307 curr_folder.curr_mssg, TRUE, newfrom, using_to);
308 }
309
310 /* clear last current if it's in proper range */
311 if (last_current > 0 /* not a dummy value */
312 && compute_visible(last_current) <= last
313 && compute_visible(last_current) >= first) {
314
315 dprint(5, (debugfile,
316 "\nlast_current = %d ... clearing [1] before we add [2]\n",
317 last_current));
318 dprint(5, (debugfile, "first = %d, and last = %d\n\n",
319 first, last));
320
321 if (inalias)
322 build_alias_line(old_buffer, aliases[last_current-1],
323 last_current, FALSE);
324 else {
325 using_to = tail_of(curr_folder.headers[last_current-1]->from, newfrom,
326 curr_folder.headers[last_current-1]->to);
327 build_header_line(old_buffer, curr_folder.headers[last_current-1],
328 last_current, FALSE, newfrom, using_to);
329 }
330
331 ClearLine(last_line);
332 PutLine0(last_line, 0, old_buffer);
333 }
334 MoveCursor(new_line, 0);
335 if (Term.status & TERM_CAN_SO)
336 StartStandout();
337 PutLine0(-1, -1, new_buffer);
338 if (Term.status & TERM_CAN_SO)
339 EndStandout();
340 }
341 else {
342 if (on_page(last_current-1))
343 PutLine0(last_line,0," "); /* remove old pointer... */
344 if (on_page(curr-1))
345 PutLine0(new_line, 0,"->");
346 }
347
348 last_current = curr;
349 }
350
build_header_line(buffer,entry,message_number,highlight,from,really_to)351 build_header_line(buffer, entry, message_number, highlight, from, really_to)
352 char *buffer;
353 struct header_rec *entry;
354 int message_number, highlight, really_to;
355 char *from;
356 {
357 /** Build in buffer the message header ... entry is the current
358 message entry, 'from' is a modified (displayable) from line,
359 'highlight' is either TRUE or FALSE, and 'message_number'
360 is the number of the message.
361 **/
362
363 /** Note: using 'strncpy' allows us to output as much of the
364 subject line as possible given the dimensions of the screen.
365 The key is that 'strncpy' returns a 'char *' to the string
366 that it is handing to the dummy variable! Neat, eh? **/
367
368 int match;
369 int who_width = 18, subj_width, subj_field_width;
370 static int initialized = 0;
371 static int mlist_justf, mlist_width = 0;
372 extern struct addrs patterns;
373 extern struct addrs mlnames;
374 static char *to_me = NULL;
375 static char *to_many = NULL;
376 static char *cc_me = NULL;
377 char *dot = index(from, '.');
378 char *bang = index(from, '!');
379
380 /*if (show_mlists && !initialized) {*/
381 if (!initialized) {
382 int i;
383 mlist_init();
384 mlist_width = 12;
385 mlist_justf = -mlist_width; /* positive for right-justified */
386 /* search for to_me/to_many override */
387 for (i=0; i < patterns.len; i++) {
388 if (patterns.str[i] == NULL)
389 continue;
390 if (strcmp(patterns.str[i], TO_ME_TOKEN) == 0) {
391 to_me = mlnames.str[i];
392 }
393 else if (strcmp(patterns.str[i], TO_MANY_TOKEN) == 0) {
394 to_many = mlnames.str[i];
395 }
396 else if (strcmp(patterns.str[i], CC_ME_TOKEN) == 0) {
397 cc_me = mlnames.str[i];
398 }
399 }
400 if (to_me == NULL) to_me = TO_ME_DEFAULT;
401 if (to_many == NULL) to_many = TO_MANY_DEFAULT;
402 if (cc_me == NULL) cc_me = CC_ME_DEFAULT;
403 initialized = 1;
404 }
405
406 subj_field_width = COLS - 46 - (show_mlists? mlist_width: 0);
407
408 /* truncate 'from' to 18 characters -
409 * this includes the leading "To" if really_to is true.
410 * Note:
411 * 'from' is going to be of three forms
412 * - full name (truncate on the right for readability)
413 * - logname@machine (truncate on the right to preserve
414 * logname over machine name
415 * - machine!logname -- a more complex situation
416 * If this form doesn't fit, either machine
417 * or logname are long. If logname is long,
418 * we can stand to loose part of it, so we
419 * truncate on the right. If machine name is
420 * long, we'd better truncate on the left,
421 * to insure we get the logname. Now if the
422 * machine name is long, it will have "." in
423 * it.
424 * Therfore, we truncate on the left if there is a "." and a "!"
425 * in 'from', else we truncate on the right.
426 */
427
428 /* Note that one huge sprintf() is too hard for some compilers. */
429
430 make_menu_date(entry);
431
432 sprintf(buffer, "%s%s%c%-3d %s ",
433 (highlight && arrow_cursor)? "->" : " ",
434 show_status(entry->status),
435 (entry->status & TAGGED? '+' : ' '),
436 message_number,
437 entry->time_menu);
438
439 if (show_mlists) {
440 mlist_parse_header_rec(entry);
441 match = mlist_match_user(entry);
442 if (match >= 0) {
443 if (entry->ml_to.len == 1 || /* just to me ... */
444 (entry->ml_to.len == 2 && /* ... or me and sender */
445 mlist_match_address(entry, entry->allfrom) >= 0))
446 sprintf(buffer + strlen(buffer), "%*.*s/ ",
447 mlist_justf, mlist_width, to_me);
448 else {
449 if (match < entry->ml_cc_index) /* to me and others */
450 sprintf(buffer + strlen(buffer), "%*.*s/ ",
451 mlist_justf, mlist_width, to_many);
452 else /* cc'd to me (implies others)*/
453 sprintf(buffer + strlen(buffer), "%*.*s/ ",
454 mlist_justf, mlist_width, cc_me);
455 }
456 } else { /* not to me at all */
457 match = addrmatch(&entry->ml_to, &patterns);
458 if (match >= 0) { /* ... found a mlist entry */
459 sprintf(buffer + strlen(buffer), "%*.*s/ ",
460 mlist_justf, mlist_width, mlnames.str[match]);
461 }
462 else {
463 if (entry->ml_to.len > 0) { /* ... no mlist, try 'to' hdr */
464 sprintf(buffer + strlen(buffer), "(%*.*s) ",
465 (mlist_justf<0)?mlist_justf+1:mlist_justf-1, mlist_width-1,
466 entry->ml_to.str[0]);
467 }
468 else { /* ... no mlist/'to'? punt! */
469 sprintf(buffer + strlen(buffer), "%*.*s/ ",
470 mlist_justf, mlist_width, "***");
471 }
472 }
473 }
474 }
475 else {
476 char *bufend = buffer+strlen(buffer);
477 mlist_parse_header_rec(entry);
478 match = mlist_match_user(entry);
479 if (match >= 0) {
480 if (entry->ml_to.len == 1 || /* just to me ... */
481 (entry->ml_to.len == 2 && /* ... or me and sender */
482 mlist_match_address(entry, entry->allfrom) >= 0))
483 *bufend++ = to_chars[0];
484 else {
485 if (match < entry->ml_cc_index) /* to me and others */
486 *bufend++ = to_chars[1];
487 else /* cc'd to me */
488 *bufend++ = to_chars[2];
489 }
490 } else /* not to me at all */
491 *bufend++ = to_chars[3];
492 *bufend++ = ' ';
493 *bufend++ = '\0';
494 }
495
496 /* show "To " in a way that it can never be truncated. */
497 if (really_to) {
498 strcat(buffer, "To ");
499 who_width -= 3;
500 }
501
502 /* truncate 'from' on left if needed.
503 * sprintf will truncate on right afterward if needed. */
504 if ((strlen(from) > who_width) && dot && bang && (dot < bang)) {
505 from += (strlen(from) - who_width);
506 }
507
508 /* Set the subject display width.
509 * If it is too long, truncate it to fit.
510 * If it is highlighted but not with the arrow cursor,
511 * expand it to fit so that the reverse video bar extends
512 * aesthetically the full length of the line.
513 */
514 if ((highlight && !arrow_cursor)
515 || (subj_field_width < (subj_width = strlen(entry->subject))))
516 subj_width = subj_field_width;
517
518 /* complete line with sender, length and subject. */
519 sprintf(buffer + strlen(buffer), "%-*.*s (%d) %s%-*.*s",
520 /* give max and min width parameters for 'from' */
521 who_width,
522 who_width,
523 from,
524
525 entry->lines,
526 (entry->lines / 1000 > 0? "" : /* spacing the */
527 entry->lines / 100 > 0? " " : /* same for the */
528 entry->lines / 10 > 0? " " : /* lines in () */
529 " "), /* [wierd] */
530
531 subj_width, subj_width, entry->subject);
532 }
533
534 int
fix_header_page()535 fix_header_page()
536 {
537 /** this routine will check and ensure that the current header
538 page being displayed contains messages! It will silently
539 fix 'header-page' if wrong. Returns TRUE if changed. **/
540
541 int last_page, old_header, max;
542
543 old_header = header_page;
544 max = (inalias ? num_aliases : curr_folder.num_mssgs);
545
546 last_page = (int) ((max-1) / headers_per_page);
547
548 if (header_page > last_page)
549 header_page = last_page;
550 else if (header_page < 0)
551 header_page = 0;
552
553 return(old_header != header_page);
554 }
555
556 int
on_page(message)557 on_page(message)
558 int message;
559 {
560 /** Returns true iff the specified message is on the displayed page. **/
561
562 if (selected)
563 message = compute_visible(message);
564
565 return ((message / headers_per_page) == header_page);
566 }
567
show_status(status)568 char *show_status(status)
569 int status;
570 {
571 /** This routine returns a pair of characters indicative of
572 the status of this message. The first character represents
573 the interim status of the message (e.g. the status within
574 the mail system):
575
576 E = Expired message
577 N = New message
578 O = Unread old message dsi mailx emulation addition
579 D = Deleted message
580 _ = (space) default
581
582 and the second represents the permanent attributes of the
583 message:
584
585 C = Company Confidential message
586 U = Urgent (or Priority) message
587 P = Private message
588 A = Action associated with message
589 F = Form letter
590 M = MIME compliant Message
591 (only displayed, when metamail is needed)
592 _ = (space) default
593 **/
594
595 static char mybuffer[3];
596
597 /** the first character, please **/
598
599 if (status & DELETED) mybuffer[0] = 'D';
600 else if (status & EXPIRED) mybuffer[0] = 'E';
601 else if (status & NEW) mybuffer[0] = 'N';
602 else if (status & UNREAD) mybuffer[0] = 'O';
603 else if ( (show_reply) && (status & REPLIED_TO) ) mybuffer[0] = 'r';
604 else mybuffer[0] = ' ';
605
606 /** and the second... **/
607
608 if (status & CONFIDENTIAL) mybuffer[1] = 'C';
609 else if (status & URGENT) mybuffer[1] = 'U';
610 else if (status & PRIVATE) mybuffer[1] = 'P';
611 else if (status & ACTION) mybuffer[1] = 'A';
612 else if (status & FORM_LETTER) mybuffer[1] = 'F';
613 #ifdef MIME_RECV
614 else if ((status & MIME_MESSAGE) &&
615 ((status & MIME_NOTPLAIN) ||
616 (status & MIME_NEEDDECOD))) mybuffer[1] = 'M';
617 #endif /* MIME_RECV */
618 else mybuffer[1] = ' ';
619
620 mybuffer[2] = '\0';
621
622 return( (char *) mybuffer);
623 }
624