static char rcsid[] = "@(#)$Id: screen.c,v 1.7 1996/03/14 17:29:50 wfp5p Exp $"; /******************************************************************************* * The Elm Mail System - $Revision: 1.7 $ $State: Exp $ * * Copyright (c) 1988-1995 USENET Community Trust * Copyright (c) 1986,1987 Dave Taylor ******************************************************************************* * Bug reports, patches, comments, suggestions should be sent to: * * Bill Pemberton, Elm Coordinator * flash@virginia.edu * ******************************************************************************* * $Log: screen.c,v $ * Revision 1.7 1996/03/14 17:29:50 wfp5p * Alpha 9 * * Revision 1.6 1995/09/29 17:42:26 wfp5p * Alpha 8 (Chip's big changes) * * Revision 1.5 1995/09/11 15:19:30 wfp5p * Alpha 7 * * Revision 1.4 1995/06/15 13:09:33 wfp5p * Changed so the local mlist files adds to the global one instead of * overriding it. (Paul Close ) * * Revision 1.3 1995/05/10 13:34:53 wfp5p * Added mailing list stuff by Paul Close * * Revision 1.2 1995/04/20 21:01:50 wfp5p * Added the showreply feature and emacs key bindings. * * Revision 1.1.1.1 1995/04/19 20:38:38 wfp5p * Initial import of elm 2.4 PL0 as base for elm 2.5. * ******************************************************************************/ /** screen display routines for ELM program **/ #include "elm_defs.h" #include "elm_globals.h" #include "s_elm.h" #define minimum(a,b) ((a) < (b) ? (a) : (b)) char *nameof(), *show_status(); extern char version_buff[]; showscreen() { ClearScreen(); update_title(); last_header_page = -1; /* force a redraw regardless */ show_headers(); if (mini_menu) show_menu(); show_last_error(); } update_title() { /** display a new title line, probably due to new mail arriving **/ char buffer[SLEN], folder_string[SLEN]; static char *folder = NULL, *mailbox = NULL; if (folder == NULL) { folder = catgets(elm_msg_cat, ElmSet, ElmFolder, "Folder"); mailbox = catgets(elm_msg_cat, ElmSet, ElmMailbox, "Mailbox"); } #ifdef DISP_HOST sprintf(folder_string, "%s:%s", host_name, nameof(curr_folder.filename)); #else strcpy(folder_string, nameof(curr_folder.filename)); #endif if (selected) MCsprintf(buffer, catgets(elm_msg_cat, ElmSet, ElmShownWithSelect, "%s is '%s' with %d shown out of %d [ELM %s]"), ((curr_folder.flags & FOLDER_IS_SPOOL) ? mailbox : folder), folder_string, selected, curr_folder.num_mssgs, version_buff); else if (curr_folder.num_mssgs == 1) MCsprintf(buffer, catgets(elm_msg_cat, ElmSet, ElmShownNoSelect, "%s is '%s' with 1 message [ELM %s]"), ((curr_folder.flags & FOLDER_IS_SPOOL) ? mailbox : folder), folder_string, version_buff); else MCsprintf(buffer, catgets(elm_msg_cat, ElmSet, ElmShownNoSelectPlural, "%s is '%s' with %d messages [ELM %s]"), ((curr_folder.flags & FOLDER_IS_SPOOL) ? mailbox : folder), folder_string, curr_folder.num_mssgs, version_buff); ClearLine(1); CenterLine(1, buffer); } show_menu() { /** write main system menu... **/ if (user_level == 0) { /* a rank beginner. Give less options */ CenterLine(LINES-7, catgets(elm_msg_cat, ElmSet, ElmLevel0MenuLine1, "You can use any of the following commands by pressing the first character;")); CenterLine(LINES-6, catgets(elm_msg_cat, ElmSet, ElmLevel0MenuLine2, "d)elete or u)ndelete mail, m)ail a message, r)eply or f)orward mail, q)uit")); CenterLine(LINES-5, catgets(elm_msg_cat, ElmSet, ElmLevel0MenuLine3, "To read a message, press . j = move down, k = move up, ? = help")); } else { CenterLine(LINES-7, catgets(elm_msg_cat, ElmSet, ElmLevel1MenuLine1, "|=pipe, !=shell, ?=help, =set current to n, /=search pattern")); CenterLine(LINES-6, catgets(elm_msg_cat, ElmSet, ElmLevel1MenuLine2, "a)lias, C)opy, c)hange folder, d)elete, e)dit, f)orward, g)roup reply, m)ail,")); CenterLine(LINES-5, catgets(elm_msg_cat, ElmSet, ElmLevel1MenuLine3, "n)ext, o)ptions, p)rint, q)uit, r)eply, s)ave, t)ag, u)ndelete, or e(x)it")); } } int show_headers() { /** Display page of headers (10) if present. First check to ensure that header_page is in bounds, fixing silently if not. If out of bounds, return zero, else return non-zero Modified to only show headers that are "visible" to ze human person using ze program, eh? **/ register int this_msg = 0, line = 4, last = 0, last_line, displayed = 0, using_to; int max, do_standout; char newfrom[SLEN], buffer[SLEN]; max = (inalias ? num_aliases : curr_folder.num_mssgs); headers_per_page = LINES - (mini_menu ? 13 : 8); if (headers_per_page < 1) headers_per_page = 1; if (fix_header_page()) return(FALSE); if (selected) { if ((header_page*headers_per_page) > selected) return(FALSE); /* too far! too far! */ this_msg = visible_to_index(header_page * headers_per_page + 1); displayed = header_page * headers_per_page; last = displayed+headers_per_page; } else { if (header_page == last_header_page) /* nothing to do! */ return(FALSE); /** compute last header to display **/ this_msg = header_page * headers_per_page; last = this_msg + (headers_per_page - 1); } if (last >= max) last = max-1; /** Okay, now let's show the header page! **/ ClearLine(line); /* Clear the top line... */ MoveCursor(line, 0); /* and move back to the top of the page... */ while ((selected && displayed < last) || this_msg <= last) { if (inalias) { if (this_msg == curr_alias-1) build_alias_line(buffer, aliases[this_msg], this_msg+1, TRUE); else build_alias_line(buffer, aliases[this_msg], this_msg+1, FALSE); } else { using_to = tail_of(curr_folder.headers[this_msg]->from, newfrom, curr_folder.headers[this_msg]->to); if (this_msg == curr_folder.curr_mssg-1) build_header_line(buffer, curr_folder.headers[this_msg], this_msg+1, TRUE, newfrom, using_to); else build_header_line(buffer, curr_folder.headers[this_msg], this_msg+1, FALSE, newfrom, using_to); } if (selected) displayed++; if (inalias) do_standout = (this_msg == curr_alias-1 && !arrow_cursor); else do_standout = (this_msg == curr_folder.curr_mssg-1 && !arrow_cursor); if (do_standout) StartStandout(); PutLine0(-1, -1, buffer); if (do_standout) EndStandout(); NewLine(); CleartoEOLN(); line++; /* for clearing up in a sec... */ if (selected) { if ((this_msg = next_message(this_msg, FALSE)) < 0) break; /* GET OUTTA HERE! */ /* the preceeding looks gross because we're using an INDEX variable to pretend to be a "current" counter, and the current counter is always 1 greater than the actual index. Does that make sense?? */ } else this_msg++; /* even dumber... */ } /* clear unused lines */ if (mini_menu) last_line = LINES-8; else last_line = LINES-4; while (line < last_line) { CleartoEOLN(); NewLine(); line++; } display_central_message(); last_current = (inalias ? curr_alias : curr_folder.curr_mssg); last_header_page = header_page; return(TRUE); } show_current() { /** Show the new header, with all the usual checks **/ register int first = 0, last = 0, last_line, new_line, using_to; int curr, max; char newfrom[SLEN], old_buffer[SLEN], new_buffer[SLEN]; if (inalias) { curr = curr_alias; max = num_aliases; } else { curr = curr_folder.curr_mssg; max = curr_folder.num_mssgs; } (void) fix_header_page(); /* Who cares what it does? ;-) */ /** compute the first and last header on this page **/ first = header_page * headers_per_page + 1; last = first + (headers_per_page - 1); /* if not a full page adjust last to be the real last */ if (selected && last > selected) last = selected; if (!selected && last > max) last = max; /** okay, now let's show the pointers... **/ /** have we changed??? **/ if (curr == last_current) return; if (selected) { last_line = ((compute_visible(last_current)-1) % headers_per_page)+4; new_line = ((compute_visible(curr)-1) % headers_per_page)+4; } else { last_line = ((last_current-1) % headers_per_page)+4; new_line = ((curr-1) % headers_per_page)+4; } if (! arrow_cursor) { if (inalias) build_alias_line(new_buffer, aliases[curr_alias-1], curr_alias, TRUE); else { using_to = tail_of(curr_folder.headers[curr_folder.curr_mssg-1]->from, newfrom, curr_folder.headers[curr_folder.curr_mssg-1]->to); build_header_line(new_buffer, curr_folder.headers[curr_folder.curr_mssg-1], curr_folder.curr_mssg, TRUE, newfrom, using_to); } /* clear last current if it's in proper range */ if (last_current > 0 /* not a dummy value */ && compute_visible(last_current) <= last && compute_visible(last_current) >= first) { dprint(5, (debugfile, "\nlast_current = %d ... clearing [1] before we add [2]\n", last_current)); dprint(5, (debugfile, "first = %d, and last = %d\n\n", first, last)); if (inalias) build_alias_line(old_buffer, aliases[last_current-1], last_current, FALSE); else { using_to = tail_of(curr_folder.headers[last_current-1]->from, newfrom, curr_folder.headers[last_current-1]->to); build_header_line(old_buffer, curr_folder.headers[last_current-1], last_current, FALSE, newfrom, using_to); } ClearLine(last_line); PutLine0(last_line, 0, old_buffer); } MoveCursor(new_line, 0); if (Term.status & TERM_CAN_SO) StartStandout(); PutLine0(-1, -1, new_buffer); if (Term.status & TERM_CAN_SO) EndStandout(); } else { if (on_page(last_current-1)) PutLine0(last_line,0," "); /* remove old pointer... */ if (on_page(curr-1)) PutLine0(new_line, 0,"->"); } last_current = curr; } build_header_line(buffer, entry, message_number, highlight, from, really_to) char *buffer; struct header_rec *entry; int message_number, highlight, really_to; char *from; { /** Build in buffer the message header ... entry is the current message entry, 'from' is a modified (displayable) from line, 'highlight' is either TRUE or FALSE, and 'message_number' is the number of the message. **/ /** Note: using 'strncpy' allows us to output as much of the subject line as possible given the dimensions of the screen. The key is that 'strncpy' returns a 'char *' to the string that it is handing to the dummy variable! Neat, eh? **/ int match; int who_width = 18, subj_width, subj_field_width; static int initialized = 0; static int mlist_justf, mlist_width = 0; extern struct addrs patterns; extern struct addrs mlnames; static char *to_me = NULL; static char *to_many = NULL; static char *cc_me = NULL; char *dot = index(from, '.'); char *bang = index(from, '!'); /*if (show_mlists && !initialized) {*/ if (!initialized) { int i; mlist_init(); mlist_width = 12; mlist_justf = -mlist_width; /* positive for right-justified */ /* search for to_me/to_many override */ for (i=0; i < patterns.len; i++) { if (patterns.str[i] == NULL) continue; if (strcmp(patterns.str[i], TO_ME_TOKEN) == 0) { to_me = mlnames.str[i]; } else if (strcmp(patterns.str[i], TO_MANY_TOKEN) == 0) { to_many = mlnames.str[i]; } else if (strcmp(patterns.str[i], CC_ME_TOKEN) == 0) { cc_me = mlnames.str[i]; } } if (to_me == NULL) to_me = TO_ME_DEFAULT; if (to_many == NULL) to_many = TO_MANY_DEFAULT; if (cc_me == NULL) cc_me = CC_ME_DEFAULT; initialized = 1; } subj_field_width = COLS - 46 - (show_mlists? mlist_width: 0); /* truncate 'from' to 18 characters - * this includes the leading "To" if really_to is true. * Note: * 'from' is going to be of three forms * - full name (truncate on the right for readability) * - logname@machine (truncate on the right to preserve * logname over machine name * - machine!logname -- a more complex situation * If this form doesn't fit, either machine * or logname are long. If logname is long, * we can stand to loose part of it, so we * truncate on the right. If machine name is * long, we'd better truncate on the left, * to insure we get the logname. Now if the * machine name is long, it will have "." in * it. * Therfore, we truncate on the left if there is a "." and a "!" * in 'from', else we truncate on the right. */ /* Note that one huge sprintf() is too hard for some compilers. */ make_menu_date(entry); sprintf(buffer, "%s%s%c%-3d %s ", (highlight && arrow_cursor)? "->" : " ", show_status(entry->status), (entry->status & TAGGED? '+' : ' '), message_number, entry->time_menu); if (show_mlists) { mlist_parse_header_rec(entry); match = mlist_match_user(entry); if (match >= 0) { if (entry->ml_to.len == 1 || /* just to me ... */ (entry->ml_to.len == 2 && /* ... or me and sender */ mlist_match_address(entry, entry->allfrom) >= 0)) sprintf(buffer + strlen(buffer), "%*.*s/ ", mlist_justf, mlist_width, to_me); else { if (match < entry->ml_cc_index) /* to me and others */ sprintf(buffer + strlen(buffer), "%*.*s/ ", mlist_justf, mlist_width, to_many); else /* cc'd to me (implies others)*/ sprintf(buffer + strlen(buffer), "%*.*s/ ", mlist_justf, mlist_width, cc_me); } } else { /* not to me at all */ match = addrmatch(&entry->ml_to, &patterns); if (match >= 0) { /* ... found a mlist entry */ sprintf(buffer + strlen(buffer), "%*.*s/ ", mlist_justf, mlist_width, mlnames.str[match]); } else { if (entry->ml_to.len > 0) { /* ... no mlist, try 'to' hdr */ sprintf(buffer + strlen(buffer), "(%*.*s) ", (mlist_justf<0)?mlist_justf+1:mlist_justf-1, mlist_width-1, entry->ml_to.str[0]); } else { /* ... no mlist/'to'? punt! */ sprintf(buffer + strlen(buffer), "%*.*s/ ", mlist_justf, mlist_width, "***"); } } } } else { char *bufend = buffer+strlen(buffer); mlist_parse_header_rec(entry); match = mlist_match_user(entry); if (match >= 0) { if (entry->ml_to.len == 1 || /* just to me ... */ (entry->ml_to.len == 2 && /* ... or me and sender */ mlist_match_address(entry, entry->allfrom) >= 0)) *bufend++ = to_chars[0]; else { if (match < entry->ml_cc_index) /* to me and others */ *bufend++ = to_chars[1]; else /* cc'd to me */ *bufend++ = to_chars[2]; } } else /* not to me at all */ *bufend++ = to_chars[3]; *bufend++ = ' '; *bufend++ = '\0'; } /* show "To " in a way that it can never be truncated. */ if (really_to) { strcat(buffer, "To "); who_width -= 3; } /* truncate 'from' on left if needed. * sprintf will truncate on right afterward if needed. */ if ((strlen(from) > who_width) && dot && bang && (dot < bang)) { from += (strlen(from) - who_width); } /* Set the subject display width. * If it is too long, truncate it to fit. * If it is highlighted but not with the arrow cursor, * expand it to fit so that the reverse video bar extends * aesthetically the full length of the line. */ if ((highlight && !arrow_cursor) || (subj_field_width < (subj_width = strlen(entry->subject)))) subj_width = subj_field_width; /* complete line with sender, length and subject. */ sprintf(buffer + strlen(buffer), "%-*.*s (%d) %s%-*.*s", /* give max and min width parameters for 'from' */ who_width, who_width, from, entry->lines, (entry->lines / 1000 > 0? "" : /* spacing the */ entry->lines / 100 > 0? " " : /* same for the */ entry->lines / 10 > 0? " " : /* lines in () */ " "), /* [wierd] */ subj_width, subj_width, entry->subject); } int fix_header_page() { /** this routine will check and ensure that the current header page being displayed contains messages! It will silently fix 'header-page' if wrong. Returns TRUE if changed. **/ int last_page, old_header, max; old_header = header_page; max = (inalias ? num_aliases : curr_folder.num_mssgs); last_page = (int) ((max-1) / headers_per_page); if (header_page > last_page) header_page = last_page; else if (header_page < 0) header_page = 0; return(old_header != header_page); } int on_page(message) int message; { /** Returns true iff the specified message is on the displayed page. **/ if (selected) message = compute_visible(message); return ((message / headers_per_page) == header_page); } char *show_status(status) int status; { /** This routine returns a pair of characters indicative of the status of this message. The first character represents the interim status of the message (e.g. the status within the mail system): E = Expired message N = New message O = Unread old message dsi mailx emulation addition D = Deleted message _ = (space) default and the second represents the permanent attributes of the message: C = Company Confidential message U = Urgent (or Priority) message P = Private message A = Action associated with message F = Form letter M = MIME compliant Message (only displayed, when metamail is needed) _ = (space) default **/ static char mybuffer[3]; /** the first character, please **/ if (status & DELETED) mybuffer[0] = 'D'; else if (status & EXPIRED) mybuffer[0] = 'E'; else if (status & NEW) mybuffer[0] = 'N'; else if (status & UNREAD) mybuffer[0] = 'O'; else if ( (show_reply) && (status & REPLIED_TO) ) mybuffer[0] = 'r'; else mybuffer[0] = ' '; /** and the second... **/ if (status & CONFIDENTIAL) mybuffer[1] = 'C'; else if (status & URGENT) mybuffer[1] = 'U'; else if (status & PRIVATE) mybuffer[1] = 'P'; else if (status & ACTION) mybuffer[1] = 'A'; else if (status & FORM_LETTER) mybuffer[1] = 'F'; #ifdef MIME_RECV else if ((status & MIME_MESSAGE) && ((status & MIME_NOTPLAIN) || (status & MIME_NEEDDECOD))) mybuffer[1] = 'M'; #endif /* MIME_RECV */ else mybuffer[1] = ' '; mybuffer[2] = '\0'; return( (char *) mybuffer); }