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